0x100 零拷贝技术
复习时思考:
- 什么是零拷贝?
- 为什么需要零拷贝?
- 零拷贝的实现方法?
什么是零拷贝
零拷贝字面意思上可以拆解为“零”和“拷贝”
- “零”:次数为零,表示拷贝的次数为零。
- “拷贝”:指数据从一个存储区域转移到另一个存储区域。
合起来就是一份数据不需要从一个区域复制到另一个区域。
零拷贝是指在计算机在执行IO操作时不需要将数据从一个区域拷贝到另一个区域。
==注意:==零拷贝技术不是没有了拷贝的IO操作,是指在内核区和用户区之间没有了拷贝的IO操作+【CPU没有参与数据拷贝的IO操作(CPU可能只负责传递了一些参数)】。
传统IO的执行流程
做服务器端开发的,文件下载功能应该都实现过吧,如果你实现的是一个web程序,前段请求过来,服务器端的任务就是:将服务器主机磁盘中的文件通过已连接的socket发送到前端。核心代码如下:
while((n = read(diskfd, buf, BUF_SIZE)) > 0)
write(sockfd, buf , n);
传统IO流程包括read和write两个流程
- read:把数据从磁盘先读取到内核缓冲区,在拷贝到用户缓冲区
- write:把数据从用户缓冲区写入到socket缓冲区
[[0x101 为什么要区分内核区和用户区]]
数据经历这个内核缓冲区的目的是什么 ?
数据经历内核缓冲区的目的涉及到提高数据处理效率、减少CPU使用率、以及改善系统的整体性能。以下是内核缓冲区的几个主要目的和好处:
- 提高数据处理效率:内核缓冲区作为用户空间和硬件之间的中间层,可以暂存数据。这意味着当一个程序需要读写数据时,它不需要每次都直接与慢速的硬件交互。通过批量处理数据传输,可以减少访问硬件的次数,从而提高数据处理的效率。
- 减少CPU使用率:如果每个小的数据读写操作都直接涉及到硬件,CPU将花费大量时间在等待数据传输完成上,这将极大地降低系统效率。使用内核缓冲区可以让这些操作异步化,CPU在等待数据操作完成时可以执行其他任务,从而提高了CPU的使用效率。
- 减少数据传输的延迟:内核缓冲区可以累积小块的数据,直到有足够的数据量进行一次有效的传输,这减少了因频繁访问硬件导致的延迟。
- 提高系统稳定性和数据完整性:内核缓冲区还可以作为数据传输的一个缓冲层,帮助处理数据传输中的错误和不一致性。例如,在网络传输中,内核缓冲区可以用来处理网络延迟和包重传,确保数据的完整性和正确性。
- 支持异步操作:内核缓冲区支持数据的异步读写。这意味着应用程序可以在不等待IO操作完成的情况下继续执行,直到数据真正需要被处理。这种方式可以显著提高应用程序的响应性和性能。
- 提供统一的数据访问接口:通过内核缓冲区,操作系统可以为不同的硬件设备提供统一的数据访问接口,简化了应用程序对硬件的操作,使得开发更加高效和简单。
流程图如下:

- 用户进程调用read函数,向操作系统发起IO请求,此时上下文从用户态切换到内核态;
- DMA控制器将数据从硬盘中读取到内核缓冲区;
- CPU把数据从内核缓冲区拷贝到用户缓冲区,上下文从内核态切换到用户态,read函数返回;
- 用户程序调用write函数,向操作系统发起IO请求,上下文从用户态切换到内核态;
- CPU把数据从用户缓冲区写入到Socket缓冲区;
- DMA将Socket中的数据写入到网卡中,上下文从内核态切换到用户态,write函数返回;
从流程图中可以看出传统的IO操作发生了4次上下文切换和4次数据拷贝(两次CPU拷贝和两次DMA拷贝)。
#question 给每个进程分配内存空间时是如何进行分配的?一次性分配还是用多少分配多少,随着不使用就释放?内存的管理策略是什么样的?
零拷贝相关的知识点回顾
- [[0x102 DMA数据拷贝]]
- [[0x103 虚拟内存]]
零拷贝实现的几种方式
零拷贝并不是没有拷贝数据,而是减少用户态/内核态的切换次数以及CPU拷贝的次数。零拷贝实现有多种方式,分别是
- mmap+write
- sendfile
- 带MDA功能的sendfile
核心处理逻辑就是数据一定要从磁盘中读取到缓冲区中,然后优化后面的流程。
[!QUESTION] 为什么CPU操作数据一定要把数据加载到内存中?
将数据加载到内存中是计算机高效运行的关键。这种设计允许快速访问数据,提高了处理速度,使得计算机可以同时运行多个程序而不至于显著降低性能。
mmap + write
在前面的知识点中,虚拟内存可以将内核空间和用户空间的虚拟地址映射到同一个物理地址中,mmap就是使用了这个特点,从而减少数据拷贝次数。让所有的IO操作都在内核中完成。
流程图:

- 用户进程通过
mmap方法,向操作系统内核发起IO调用,上下文从用户态切换到内核态,操作系统执行mmap方法,进行内存映射。 - CPU利用DMA控制器将数据从硬件中拷贝到内核缓冲区。
- 上下文从内核态切换到用户态,mmap方法返回。
- 用户进程发起write请求,向操作系统内核发起IO调用,上下文从用户态切换到内核态。
- CPU将数据从内核缓冲区拷贝到Socket缓冲区。
- CPU利用DMA控制器将数据从Socket缓冲区拷贝到网卡,上下文从内核态切换到用户态,write方法返回。
可以发现,mmap+write 实现的零拷贝,I/O发生了4次用户空间与内核空间的上下文切换,以及3次数据拷贝。其中3次数据拷贝中,包括了2次DMA拷贝和1次CPU拷贝。
mmap是将读缓冲区的地址和用户缓冲区的地址进行映射,内核缓冲区和应用缓冲区共享,所以节省了一次CPU拷贝‘’并且用户进程内存是虚拟的,只是映射到内核的读缓冲区,可以节省一半的内存空间。
sendfile
sendfile 是一种专门用于在两个文件描述符之间高效传输数据的系统调用,通常用于将数据从文件系统传输到网络套接字,它完全是在操作系统内核中进行操作的,避免了数据从内核缓冲区到用户缓冲区的拷贝操作,所以可以用来实现零拷贝。
#question Linux数据在硬盘中和在内存中都有对应的文件描述符吗 ?系统进程也是文件描述符?
流程图:

- 用户进程调用sendfile方法像操作系统内核发起IO调用,上下文从用户态切换到内核态
- CPU利用DMA控制器将数据从硬件拷贝到内核缓冲区
- CPU将数据从内核缓冲区拷贝到Socket缓冲区
- CPU利用DMA控制器将数据从Socket缓冲区拷贝到网卡中
- 上下文从内核态切换到用户态,sendfile方法返回
sendfile实现的零拷贝,I/O发生了2次用户空间与内核空间的上下文切换,以及3次数据拷贝。其中3次数据拷贝中,包括了2次DMA拷贝和1次CPU拷贝。那能不能把CPU拷贝的次数减少到0次呢?有的,即带有DMA收集拷贝功能的sendfile!
DMA Scatter/gather+sendfile实现零拷贝
[!NOTE] DMA Scatter/gather
DMA scatter/gather是一种高级DMA模式,它允许单个DMA操作从内存中的多个非连续区域(scatter)读取数据,或向内存中的多个非连续区域(gather)写入数据。这种模式特别适用于处理分散的内存布局,而无需事先将数据聚合到一个连续的内存区域。
- Scatter操作:在写操作中,数据从一个连续的源传输到多个分散的目标内存地址。这种情况下,数据“散布”到不同的内存位置。
- Gather操作:在读操作中,数据从多个分散的源内存地址收集到一个连续的目标地址中。这里,数据从多个位置“聚集”到一个地方。
流程图:

- 用户进程调用
sendfile方法发起操作系统IO调用,上下文从用户态切换到内核态 - CPU利用DMA控制器将数据从硬件拷贝到内核缓冲区(当直接从硬盘到网络接口卡(NIC)的DMA传输不被支持或者效率不高时会这样做,如果效率比较高就会直接从硬件拷贝到NIC中)
- CPU把内核缓冲区中的文件描述符信息(包括内核缓冲区的内存地址和偏移量)发送给socket缓冲区。
- DMA控制器根据描述符信息将数据从内核缓冲区拷贝到网卡中
- 上下文从内核态切换到用户态,sendfile方法返回。
该流程进行了两次上下文切换和两次数据拷贝,全程没有CPU参与进行数据拷贝。
使用零拷贝技术的前提和条件
- 硬件支持:零拷贝技术的实现往往需要其他技术的支持,比如DMA技术,没有对应的硬件支持是无法实现零拷贝技术的。
- 操作系统支持:操作系统需要提供相应的API和内核机制来支持零拷贝操作,比如“mmap”,“sendfile”,"splice"等系统调用
- 对数据无修改:零拷贝技术最适合对数据无修改的操作,比如对数据无修改的网络数据转发,文件传输。
[!question] 如果我需要对硬盘数据进行操作的话必须要拷贝到用户缓冲区吗可以拷贝到内核缓冲区就开始操作吗?
针对是否必须将硬盘数据拷贝到用户缓冲区才能进行操作的问题,答案是不必须。实际上,可以利用内核缓冲区来完成许多类型的数据操作。例如,使用内核缓冲区(kernel buffer)进行数据处理后,再利用零拷贝技术将数据传输到另一个硬件设备,这样可以减少数据在用户空间和内核空间之间的来回拷贝,从而提高效率。
[!QUESTION] 数据为什么要从磁盘先拷贝到内核缓冲区,在拷贝到用户缓冲区?那为什么零拷贝就可以了呢?零拷贝操作数据了吗?
- 因为为了安全性,效率,稳定性,抽象接口的原因所以区分了内核区和用户区,用户进程想要操作数据必须经过一次内核区。
- 零拷贝操作时数据没有经过用户区。
- 零拷贝拷贝数据了。
#question 为什么要使用StringBuffer和StringBuilder?StringBuffer和StringBuilder是如何处理数据的?
#question 如何在内核缓冲区中进行数据操作,这个是由开发者决定还是由操作系统决定?