什么是零拷贝
零拷贝描述了一种计算机中的操作,即CPU在执行某项任务时不需要先将数据
从内存中的一个位置移动到另一个位置就可以完成操作,从而节省了CPU时钟
周期和内存带宽。
从上面的描述可以看出,其实只要是节省了一次或多次数据的复制就可以称之为零拷贝了,这其实是一种广义的定义。
什么是CPU拷贝、DMA拷贝
-
硬盘到内核空间一般通过DMA的方式进行内存拷贝
-
内核空间到用户空间的拷贝一般通过CPU进行。
所以零拷贝的目标就是减少使用CPU执行拷贝操作,让CPU执行其它操作;另外也减少系统的上下文切换
。
传统I/O拷贝
传统I/O中通过InputStream从源文件中获取数据到缓冲区,然后将缓冲区的数据输入到OutputStream中,最后通过OutputStream写入数据。
流程如下:
- JVM发出读请求,系统进行一次上下文切换到内核模式(第一次上下文切换)
- 然后将数据通过DMA(直接内存拷贝,不使用CPU)的方式从硬盘拷贝到内核空间缓冲区(第一次拷贝:硬盘---->内核空间缓冲区)
- 操作系统将数据拷贝到用户空间缓冲区(第二次拷贝:内核空间缓冲区------->用户空间缓冲区),到此为止读取命令已结束,系统将从内核态切换为用户态(第二次上下文切换)
- JVM开始写入请求,系统从执行上下文切换从用户态切换为内核态(第三次上下文切换),并且将数据从用户空间缓冲区拷贝到内核空间缓冲区(第三次数据拷贝:用户空间缓冲区------->内核空间缓冲区(socket相关缓冲区))
- 将数据从内核空间缓冲区拷贝到硬盘中(第四次拷贝:内核空间缓冲区------->网卡),写入请求结束,系统从内核态切换为用户态(第四次上下文切换)
可以看到传统的方式发生了4次拷贝和4次上线文切换
mmap优化I/O
从以上流程中可以看到传统I/O中进行了四次数据拷贝和四次上下文切换,从流程中可以看到将数据从内核空间缓冲区拷贝到用户空间缓冲区完全没有必要,因为拷贝到用户空间缓冲区的数据并没有做任何事情,所以优化方案是减少从内核态到用户态的拷贝次数
mmap是内存映射的意思,也就是系统通过内存映射将文件映射到内核空间缓冲区,用户空间可以共享内核空间缓冲区,这样在进行网络传输时就减少了内核空间和用户空间的拷贝次数
。
加入了mmap后的流程图
流程如下:
- JVM发出读请求,系统进行一次上下文切换到内核模式(第一次上下文切换)
- 然后将数据通过DMA(直接内存拷贝,不使用CPU)的方式从硬盘拷贝到内核空间缓冲区(第一次拷贝:硬盘---->内核空间缓冲区)
内核缓冲区共享给用户空间,而不需要将数据从内核空间拷贝到用户空间,所以用户就可以直接操作内核空间缓冲区的数据
,到此为止读取命令已结束,系统将从内核态切换为用户态(第二次上下文切换)- JVM开始写入请求,系统从执行上下文切换从用户态切换为内核态(第三次上下文切换),
不需要再从用户空间缓冲区拷贝数据到内核空间缓冲区
而是将数据从内核空间缓冲区拷贝到Socket相关的缓冲区中(第二次拷贝:内核空间缓冲区-------->socket相关缓冲区)。
5.通过DMA将数据从socket缓冲区拷贝到网卡中(第三次拷贝:socket缓冲区------->网卡)。写入请求结束,系统从内核态切换为用户态(第四次上下文切换)
从以上流程中可以看到mmap执行了四次上下文切换和三次拷贝,该方式比传统I/O少了一次拷贝。通过mmap的方式可以将数据从映射到内存中,用户可以直接对内存中的文件进行操作,从而提高I/O操作。
sendFile优化I/O
Linux内核 在2.4版本:sendfile() 系统调用的过程发生了点变化,DMA 控制器就可以直接将内核缓存中的数据拷贝到网卡的缓冲区里
,此过程不需要将数据从操作系统内核缓冲区拷贝到 socket 缓冲区中,这样就减少了一次数据拷贝(Linux2.4)
;
流程如下:
- JVM发出读请求,系统进行一次上下文切换到内核模式(第一次上下文切换)
- 通过DMA将数据从硬盘拷贝到内核空间缓冲区(第一次拷贝:硬盘------>内核空间缓冲区)
- 将数据从内核空间缓冲区拷贝到Socket相关的缓冲区中(第二次拷贝:内核空间缓冲区------->socket相关缓冲区)(
但是在Linux2.4中避免了从内核空间缓冲区拷贝到Socket缓冲区,而是直接拷贝到协议栈中
)。 - sendFile请求结束,系统从内核态切换到用户态,然后通过DMA将Socket缓冲区的数据拷贝到协议栈中(第三次拷贝:socket缓冲区------->协议栈)。
因为我们没有在内存层面去拷贝数据,也就是说全程没有通过 CPU 来搬运数据,所有的数据都是通过 DMA 来进行传输的,总体来看,零拷贝技术可以把文件传输的性能提高至少一倍以上。