零拷贝

DMA

DMA (Direct Memory Access) 是直接内存访问技术,可能这个概念比较抽象,直接先来看看如果没有 DMA,进程一次完整的读磁盘操作的过程:

7

主要关注两个点:上下文切换和拷贝次数。在系统调用read()的完整过程中:

在这个过程中,可以看出 CPU 参与了整个数据的传输:磁盘控制缓冲区 -> Page Cache -> 用户缓冲区。有种杀鸡用牛刀的感觉,因为 CPU 资源是很宝贵的

基于这个问题就发明了 DMA 技术,DMA 可以主动将 I/O 设备中的数据拷贝到 Page Cache 中,而不需要 CPU 的参与,相当于解放了 CPU,在 DMA 拷贝的期间 CPU 可以去运行其它进程

8

同样的,主要关注两个点:上下文切换和拷贝次数。在系统调用read()的完整过程中:

注意:Page Cache 是内核缓冲区

传统文件传输过程

在客户端/服务端架构中,假设服务端需要调用read()读取磁盘中的文件数据,然后调用write()向客户端发送该数据 (这两个函数都是系统调用)。基于上一部分介绍的 DMA 技术来看看这个过程:

9

同样的,主要关注两个点:上下文切换和拷贝次数。在整个过程中:

从上面的过程可以看出开销还挺大,每次读数据➕写数据都要经历四次上下文切换和四次数据拷贝,而且其中有两次数据拷贝只是将内存中同一份数据拷贝到两个不同的地方而已 (两次 CPU 拷贝)

所以关于这个过程的优化也主要是从上下文切换和拷贝次数入手!!

mmap

mmap (Memory Map) 是内存映射,直接让用户空间和内核空间共享同一份数据,避免了用户空间和内核空间的数据来回拷贝

10

由于用户缓冲区和 Page Cache 共享了同一份缓存数据,所以就不需要「从 Page Cache 拷贝到用户缓冲区」和「从用户缓冲区拷贝到 socket 缓冲区」这两个过程。在整个过程中:

相比于传统文件传输,减少了一次 CPU 拷贝的过程~

sendfile

mmap 是从减少拷贝次数的角度优化,而本部分介绍的 sendfile 是从减少上下文切换的角度优化。在传统文件传输的过程中,使用read()write()这两个系统调用;

sendfile()系统调用将读写两个功能合二为一:

11

和 mmap 内存映射不同在于:调用sendfile()时,数据对用户空间完全不可见,这是一次真正意义上的数据传输过程!

sendfile + SG-DMA

本部分要介绍的 sendfile + SG-DMA 有一个前提,必须要求网卡支持 SG-DMA (The Scatter-Gather Direct Memory Access) 技术,它进一步减少了从 Page Cache 拷贝到 socket 缓冲区过程,而直接可以从 Page Cache 拷贝到网卡 (DMA 拷贝)

12

这才算是真正意义上的零拷贝,也就是 CPU 完全不参与拷贝,全部交由 DMA 完成!!

对比

方式CPU 拷贝DMA 拷贝上下文切换
传统方式224
mmap124
sendfile122
sendfile + SC-DMA022

扩展:大文件传输

对于大文件传输,如果使用零拷贝技术,就会用 Page Cache,而 Page Cache 主要用来存放热点数据提高缓存命中率,所以如果 Page Cache 被大文件占据,将会大大降低命中率

大文件传输一般使用异步的方式,即不需要等到完全传输完成才返回,而是发起 I/O 请求后立刻返回,当 I/O 请求执行完后会通过回调函数通知 CPU

注意:异步传输不会使用 Cahce Page,而是直接从磁盘控制器缓冲区拷贝到用户缓冲区中