IO零拷贝技术

news2024/10/5 17:42:45

01背景介绍

        相信不少的网友,在很多的博客文章里面,已经见到过零拷贝这个词,会不禁的发出一些疑问,什么是零拷贝?

       从字面上我们很容易理解出,零拷贝包含两个意思:

  • 拷贝:就是指数据从一个存储区域转移到另一个存储区域。
  • 零:它表示拷贝数据的次数为 0。

合起来理解,零拷贝就是不需要将数据从一个存储区域复制到另一个存储区域。

果真是这样的吗?

最早的零拷贝定义,来源于 Linux 系统的 sendfile 方法逻辑!

在 Linux 2.4 内核中,sendfile 系统调用方法,可以将磁盘数据通过 DMA 拷贝到内核态 Buffer 后,再通过 DMA 拷贝到 NIC Buffer(socket buffer),无需 CPU 拷贝,这个过程被称之为零拷贝。

        从这段描述里面我们可以得知,站在操作系统的角度,零拷贝没有说不需要拷贝数据,而是省掉了 CPU 拷贝环节,减少了不必要的拷贝次数,提升数据拷贝效率。

        要想深度的了解这里面的原理,我们还得从 IO 拷贝机制说起!

02 IO 拷贝机制介绍

2.1、传统数据拷贝流程以客户端从服务器下载文件为例,熟悉服务端开发的同学可能知道,服务端需要做两件事:

  • 第一步:从磁盘中读取文件内容
  • 第二步:将文件内容通过网络传输给客户端

事实上看似简单的操作,里面的流程却没那么简单,例如应用程序从磁盘中读取文件内容的操作,大体会经过以下几个流程:

  • 第一步:用户应用程序调用 read 方法,向操作系统发起 IO 请求,CPU 上下文从用户态转为内核态,完成第一次 CPU 切换
  • 第二步:操作系统通过 DMA 控制器从磁盘中读数据,并把数据存储到内核缓冲区
  • 第三步:CPU 把内核缓冲区的数据,拷贝到用户缓冲区,同时上下文从内核态转为用户态,完成第二次 CPU 切换整个读取数据的过程,完成了 1 次 DMA 拷贝,1 次 CPU 拷贝,2 次 CPU 切换;反之写入数据的过程,也是一样的。

整个拷贝过程,可以用如下流程图来描述!cc9c4a12e26841c690b3276d396a4b01.png

从上图,我们可以得出如下结论,4 次拷贝次数、4 次上下文切换次数。

  • 数据拷贝次数:2 次 DMA 拷贝,2 次 CPU 拷贝
  • CPU 切换次数:4 次用户态和内核态的切换

而实际 IO 读写,有时候需要进行 IO 中断,同时也需要 CPU 响应中断,拷贝次数和切换次数比预期的还要多,以至于当客户端进行资源文件下载的时候,传输速度总是不尽人意。

那有没有好的办法来提升资源拷贝的速度呢?

答案是肯定的,传统的数据拷贝流程还有很大的优化空间。

下面我们一起来看看几种其它的拷贝方式。

2.2 mmap 内存映射拷贝流程

mmap 内存映射的拷贝,指的是将用户应用程序的缓冲区和操作系统的内核缓冲区进行映射处理,数据在内核缓冲区和用户缓冲区之间的 CPU 拷贝将其省略,进而加快资源拷贝效率。

整个拷贝过程,可以用如下流程图来描述!80ef2d017bb84b72871e7dab4326d038.png

 mmap 内存映射拷贝流程,从上图可以得出如下结论:

  • 数据拷贝次数:2 次 DMA 拷贝,1 次 CPU 拷贝
  • CPU 切换次数:4 次用户态和内核态的切换

整个过程省掉了数据在内核缓冲区和用户缓冲区之间的 CPU 拷贝环节,在实际的应用中,对资源的拷贝能提升不少。

2.3 Linux 系统 sendfile 拷贝流程

在 Linux 2.1 内核版本中,引入了一个系统调用方法:sendfile。

当调用 sendfile() 时,DMA 将磁盘数据复制到内核缓冲区 kernel buffer;然后将内核中的 kernel buffer 直接拷贝到 socket buffer;最后利用 DMA 将  socket buffer 通过网卡传输给客户端。

整个拷贝过程,可以用如下流程图来描述!087ed707f8844298ac92bd788dec40bf.png

Linux 系统 sendfile 拷贝流程,从上图可以得出如下结论:

  • 数据拷贝次数:2 次 DMA 拷贝,1 次 CPU 拷贝
  • CPU 切换次数:2 次用户态和内核态的切换

相比 mmap 内存映射方式,Linux 2.1 内核版本中 sendfile 拷贝流程省掉了 2 次用户态和内核态的切换,同时内核缓冲区和用户缓冲区也无需建立内存映射,对资源的拷贝能提升不少。

2.4 sendfile With DMA scatter/gather 拷贝流程

在 Linux 2.4 内核版本中,对 sendfile 系统方法做了优化升级,引入 SG-DMA 技术,需要 DMA 控制器支持。

其实就是对 DMA 拷贝加入了 scatter/gather 操作,它可以直接从内核空间缓冲区中将数据读取到网卡。使用这个特点来实现数据拷贝,可以多省去一次 CPU 拷贝。

整个拷贝过程,可以用如下流程图来描述!

c914382d5529412eb02786f660a05728.pngLinux 系统 sendfile With DMA scatter/gather 拷贝流程,从上图可以得出如下结论:

  • 数据拷贝次数:2 次 DMA 拷贝,0 次 CPU 拷贝
  • CPU 切换次数:2 次用户态和内核态的切换

可以发现,sendfile With DMA scatter/gather 实现的拷贝,其中 2 次数据拷贝都是 DMA 拷贝,全程都没有通过 CPU 来拷贝数据,所有的数据都是通过 DMA 来进行传输的,这就是操作系统真正意义上的零拷贝(Zero-copy) 技术,相比其他拷贝方式,传输效率最佳。

2.5 Linux 系统 splice 零拷贝流程

在 Linux 2.6.17 内核版本中,引入了 splice 系统调用方法,和 sendfile 方法不同的是,splice 不需要硬件支持。

它将数据从磁盘读取到 OS 内核缓冲区后,内核缓冲区和 socket 缓冲区之间建立管道来传输数据,避免了两者之间的 CPU 拷贝操作。

整个拷贝过程,可以用如下流程图来描述!eec45803a06b450eab3d6d6239c91a90.png

Linux 系统 splice 拷贝流程,从上图可以得出如下结论:

  • 数据拷贝次数:2 次 DMA 拷贝,0 次 CPU 拷贝
  • CPU 切换次数:2 次用户态和内核态的切换

Linux 系统 splice 方法逻辑拷贝,也是操作系统真正意义上的零拷贝。

03 IO 拷贝机制

对比从上面的 IO 拷贝机制可以看出,无论是传统 IO 方式,还是引入零拷贝之后,2次 DMA copy 是都少不了的,唯一的区别就是省掉 CPU 参与环节的方式不同。

以 Linux 系统为例,拷贝机制对比结果如下!0a9bbc3cb8dc498690fce30f3448b6b4.png

需要注意的地方是,零拷贝所有的方式,都需要操作系统支持,具体采用哪种方式,由操作系统来决定。

04 相关概念说

上文中我们提到了几个很少见的名词,比如 DMA,用户空间,内核空间等。它们到底是什么意思呢?

4.1、DMA 介绍

DMA,英文全称是 Direct Memory Access,即直接内存访问。DMA 本质上是一块主板上独立的芯片,允许外设设备和内存存储器之间直接进行 IO 数据传输,其过程不需要 CPU 的参与。

4.2、内核空间和用户空间介绍

操作系统的核心是内核,与普通的应用程序不同,它可以访问受保护的内存空间,也有访问底层硬件设备的权限。

为了避免用户进程直接操作内核,保证内核安全,操作系统将虚拟内存划分为两部分,一部分是内核空间(Kernel-space),一部分是用户空间(User-space)。在 Linux 系统中,内核模块运行在内核空间,对应的进程处于内核态;而用户程序运行在用户空间,对应的进程处于用户态。

内核空间总是驻留在内存中,它是为操作系统的内核保留的。应用程序是不允许直接在该区域进行读写或直接调用内核代码定义的函数。

当启动某个应用程序时,操作系统会给应用程序分配一个单独的用户空间,其实就是一个用户独享的虚拟内存,每个普通的用户进程之间的用户空间是完全隔离的、不共享的,当用户进程结束的时候,用户空间的虚拟内存也会随之释放。

同时处于用户态的进程不能访问内核空间中的数据,也不能直接调用内核函数的,如果要调用系统资源,就要将进程切换到内核态,由内核程序来进行操作。

05 Java 零拷贝实现介绍

Linux 提供的零拷贝技术,Java 并不是全部支持,目前只支持以下 2 种。

  • mmap(内存映射)
  • sendfile

5.1 Java NIO 对 mmap 的支持

Java NIO 有一个MappedByteBuffer的类,可以用来实现内存映射。它的底层是调用了 Linux 内核的mmap的 API。

实例代码如下:

public static void main(String[] args) {
    try {
        FileChannel readChannel = FileChannel.open(Paths.get("a.txt"), StandardOpenOption.READ);
        // 建立内存文件映射
        MappedByteBuffer data = readChannel.map(FileChannel.MapMode.READ_ONLY, 0, 1024 * 1024 * 40);
        FileChannel writeChannel = FileChannel.open(Paths.get("b.txt"), StandardOpenOption.WRITE, StandardOpenOption.CREATE);
        // 拷贝数据
        writeChannel.write(data);

        // 关闭通道
        readChannel.close();
        writeChannel.close();
    }catch (Exception e){
        System.out.println(e.getMessage());
    }
}

其中MappedByteBuffer的作用,就是将内核缓冲区的内存和用户缓冲区的内存做了一个地址映射,读取小文件,效率并不高;但是读取大文件,效率很高。

5.2、Java NIO 对 sendfile 的支持

Java NIO 中的FileChannel.transferTo方法,底层调用的就是 Linux 内核的sendfile系统调用方法。Kafka 这个开源项目就用到它,平时面试的时候,如果面试官问起你为什么这么快,就可以提到sendfile零拷贝系统调用方法来回答。

实例代码如下:

public static void main(String[] args) {
    try {
        // 原始文件
        FileChannel srcChannel = FileChannel.open(Paths.get("a.txt"), StandardOpenOption.READ);
        // 目标文件
        FileChannel destChannel = FileChannel.open(Paths.get("b.txt"), StandardOpenOption.WRITE, StandardOpenOption.CREATE);
        // 拷贝数据
        srcChannel.transferTo(0, srcChannel.size(), destChannel);

        // 关闭通道
        srcChannel.close();
        destChannel.close();
    } catch (Exception e) {
        System.out.println(e.getMessage());
    }
}

Java NIO 提供的FileChannel.transferTo并不保证一定能使用零拷贝。实际上是否能使用零拷贝与操作系统相关,如果操作系统提供sendfile这样的零拷贝系统调用方法,那么会充分利用sendfile零拷贝的优势,否则并不能实现零拷贝

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2190259.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Lesson1 - 操作系统概述与硬件视角

文章目录 什么是操作系统操作系统的形成 从程序看OS提出问题:从hello world文件开始目前编译器帮我们解决了诸多问题gcc的编译过程 CPU的运作CPU对任务的切换 什么是操作系统 操作系统 Operating System 是一组控制和管理计算机 硬件 和 软件 资源合理地对各类作业…

深入理解NumPy库:常用函数详解与数组操作指南

在数据科学和数值计算领域,NumPy无疑是一个强大的工具,它为Python提供了高效的多维数 组处理能力。无论是进行数据分析、构建机器学习模型,还是进行复杂的科学计算,NumPy都是 不可或缺的核心库之一。 numpy.array 是 NumPy 库中…

Python 从入门到实战34(实例2:绘制蟒蛇)

我们的目标是:通过这一套资料学习下来,通过熟练掌握python基础,然后结合经典实例、实践相结合,使我们完全掌握python,并做到独立完成项目开发的能力。 上篇文章我们讨论了数据库MySQL操作的相关知识。今天学习一个使用…

C语言指针plus版练习

上期我们讲了进阶的指针,本期内容我们来强化一下上期学的内容 一、字符串左旋 实现一个函数,可以左旋字符串中的k个字符。 1.1 分析题目 假设字符串为abcde,左旋一个以后就变成bcdea,就是把第一个字符移到一个新的变量里面&#…

5G NR BWP 简介

文章目录 BWP介绍BWP 分类BWP相关总结 BWP介绍 5G NR 系统带宽比4G LTE 大了很多,4G LTE 最大支持带宽为20MHz, 而5G NR 的FR1 最大支持带宽为100MH在, FR2 最大支持带宽为 400MH在。带宽越大,意味了终端功耗越多。为了减少终端的…

哪款百元头戴式耳机性价比高?四款大火爆全网的机型盘点推荐!

在繁忙的生活节奏中,寻找一片属于自己的宁静空间,成为了许多人的内心渴望。头戴式降噪耳机,正是那把打开音乐世界的钥匙。它不仅能够隔绝外界的喧嚣,还能将您带入一个纯净无瑕的音乐世界。无论是沉浸在古典乐的悠扬旋律中&#xf…

数据服务-存储服务(NFS)

1.概述 存储: 用于存放用户上传的内容(数据),一般应用在网站集群中 如果不使用存储,用户上传的数据就直接存放在网站服务器上了,用户下次访问就可能找不到 如果使用存储,用户上传的内容存放在存储上面,用户访问就会访问存储. 位置: 网站后排. 2. 存储分类 分类说明硬件存储硬件…

【高等数学学习记录】函数的极限

一、知识点 (一)知识结构 #mermaid-svg-Dz0Ns0FflWSBWY50 {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-Dz0Ns0FflWSBWY50 .error-icon{fill:#552222;}#mermaid-svg-Dz0Ns0FflWSBWY50 .erro…

KiCad 综合笔记

开窗 选中顶层或者底层 Mask 层,然后进行覆铜: 四层板 KiCad Tutorial - How to make a 4 layer PCB https://bbs.elecfans.com/jishu_2365544_1_1.html 虽然在“电路板设置”中,可以选择铜层的类型,但如果选择了“电源层”,除了用于告诉“Free router”布线器在自动布…

vue3+vite@4+ts+elementplus创建项目详解

1、第一步创建项目cnpm init vite4 2、设置vue3.2局域网可访问配置: 找到项目路径下的package.json目录下找到script对象下面添加一下代码: "serve": "vite --host 0.0.0.0" 启动项目命令不在是dev而是:cnpm run serve 3…

Linux中的进程信号

目录 进程信号 kill/raise/abort 硬件异常产生信号 由软件条件产生信号 信号在内核中的表示示意图 pending: block: 信号集操作函数 sigprocmask 进程信号 信号量和信号不同 信号量的本质是计数器,计数器可以被多进程同时看到。可以对资源进行预定。 所有进…

OpenJudge | 置换选择排序

总时间限制: 1000ms 内存限制: 65536kB 描述 给定初始整数顺串,以及大小固定并且初始元素已知的二叉最小堆(为完全二叉树或类似完全二叉树,且父元素键值总小于等于任何一个子结点的键值),要求利用堆实现置换选择排序&a…

程序员如何在 AI 时代保持核心竞争力

前言 随着 AIGC 大语言模型的不断涌现,AI 辅助编程工具的普及正在深刻改变程序员的工作方式。在这一趋势下,程序员面临着新的挑战与机遇,需要思考如何应对以保持并提升自身的核心竞争力。 目录 一、AI 对编程工作的影响 (一&…

一、走进新语言

走进新语言 介绍环境配置JDK配置Kotlin配置 开发工具代码基本结构程序注释 介绍 Kotlin是一种现代但已经成熟的编程语言,旨在让开发人员更快乐。它简洁、安全、可与Java和其他语言互操作,并提供了许多在多个平台之间重用代码的方法。它由JetBrains公司于…

2.JUC

掌握 Java 并发编程,熟悉线程池、并发容器以及锁的机制,如 sychronized、ReentrantLock、AQS等。 2.0 并发基础 线程等待唤醒机制LockSupport (3种) 方式一:使用Object中的wait()方法让线程等待,使用Object中的notify()方法唤醒…

数据结构 ——— 单链表oj题:链表分割(带哨兵位单向不循环链表实现)

目录 题目要求 手搓简易单链表 代码实现 题目要求 现有一链表的头指针 ListNode* head ,给一定值 x ,编写一段代码将所有小于 x 的节点排在其余节点之前,且不能改变原来的数据顺序,返回重新排列后的链表的头节点 举例说明&a…

【包教包会】2D图片实现3D透视效果(支持3.x、支持原生、可合批)

将去年写的SpriteFlipper从2.x升级到3.x。 如果需要2.x版本或需要了解算法思路,请移步:https://blog.csdn.net/weixin_42714632/article/details/136745051 优化功能:可同时绕X轴和Y轴旋转,两者效果会叠加。 完美适配Web、原生…

typescript使用webpack打包编译问题

解决方案:在webpack.config.js中的mdule.exports中设置mode。 再次运行npm run start即可。

Python基本库的使用--urllib

开篇 本篇文章旨在总结urlib基本库的一些常用用法。 相关方法 urlopen 用户打开和读取URL。 import urllib.requestresponse urllib.request.urlopen(https://www.python.org) print(response.read().decode(utf-8))带data参数 import urllib.parse import urllib.requestda…

【计算复杂性理论】P可归约(归约,P-reducible)与P、NP、NP-Hard、NP-Complete问题

1 问题背景 如果想要了解P问题、NP问题、NP-Hard问题、NP-Complete问题之间的关系,那就需要从了解NP-complete问题和归约概念开始。上一篇文章中,我们介绍了计算复杂性理论的奠基之作《The Complexity of Theorem-Proving Procedures》,在这篇…