一、前言
磁盘可以说是计算机系统最慢的硬件之一,读写速度相差内存 10 倍以上。所以为了提高系统吞吐量,减少磁盘访问次数,有很多优化措施,比如直接IO、异步IO,但其实还有一种优化策略,那就是——零拷贝!
现实情况中,大部分系统在由小变大的过程中,最先出现瓶颈的就是I/O 操作会导致数据在操作系统内核地址空间的缓冲区和用户进程地址空间定义的缓冲区之间进行传输。设置缓冲区最大的好处是可以减少磁盘 I/O 的操作,如果所请求的数据已经存放在操作系统的高速缓冲存储器中,那么就不需要再进行实际的物理磁盘 I/O 操作;然而传统的 Linux I/O 在数据传输过程中的数据拷贝操作深度依赖 CPU,也就是说 I/O 过程需要 CPU 去执行数据拷贝的操作,因此导致了极大的系统开销,限制了操作系统有效进行数据传输操作的能力。
零拷贝技术可以减少CPU搬运数据的次数,在网络系统中起到很重要的优化作用。
二、相关名词
2.1 DMA
DMA,全称 Direct Memory Access,即直接存储器访问,是为了避免 CPU 在磁盘操作时承担过多的中断负载而设计的;在磁盘操作中,CPU 可将总线控制权交给 DMA 控制器,由 DMA 输出读写命令,直接控制 RAM 与 I/O 接口进行 DMA 传输,无需 CPU 直接控制传输,也没有中断处理方式那样保留现场和恢复现场过程,使得 CPU 的效率大大提高。
CPU 不再参与将数据从磁盘控制器缓冲区搬运到内核空间的工作,这部分工作全程由 DMA 完成。但是 CPU 在这个过程中也是必不可少的,因为传输什么数据,从哪里传输到哪里,都需要 CPU 来告诉 DMA 控制器。
2.2 MMU
-
Memory Management Unit—内存管理单元,主要实现:
-
竞争访问保护管理需求:需要严格的访问保护,动态管理哪些内存页/段或区,为哪些应用程序所用。这属于资源的竞争访问管理需求;
-
高效的翻译转换管理需求:需要实现快速高效的映射翻译转换,否则系统的运行效率将会低下;
-
高效的虚实内存交换需求:需要在实际的虚拟内存与物理内存进行内存页/段交换过程中快速高效。
-
2.3 Page Cache
为了避免每次读写文件时,都需要对硬盘进行读写操作,Linux 内核使用页缓存(Page Cache)
机制来对文件中的数据进行缓存。
此外,由于读取磁盘数据的时候,需要找到数据所在的位置,但是对于机械磁盘来说,就是通过磁头旋转到数据所在的扇区,再开始「顺序」读取数据,但是旋转磁头这个物理动作是非常耗时的,为了降低它的影响,PageCache 使用了「预读功能」。
比如,假设 read 方法每次只会读 32 KB
的字节,虽然 read 刚开始只会读 0 ~ 32 KB 的字节,但内核会把其后面的 32 ~ 64 KB 也读取到 PageCache,这样后面读取 32 ~ 64 KB 的成本就很低,如果在 32 ~ 64 KB 淘汰出 PageCache 前,有进程读取到它了,收益就非常大。
三、传统文件传输
可以看到有四次上下文切换,因为要用到read/write这两个系统调用。同时会产生两次DMA拷贝,两次CPU拷贝。
传统的文件传输方式会历经 4 次数据拷贝,而且这里面,「从内核的读缓冲区拷贝到用户的缓冲区里,再从用户的缓冲区里拷贝到 socket 的缓冲区里」,这个过程是没有必要的。
因为文件传输的应用场景中,在用户空间我们并不会对数据「再加工」,所以数据实际上可以不用搬运到用户空间,因此用户的缓冲区是没有必要存在的。
四、如何实现零拷贝
mmap + write
mmap系统调用函数会直接把内核缓冲区里的数据「映射」到用户空间,这样,操作系统内核与用户空间就不需要再进行任何的数据拷贝操作。
mmap分配的区域是内存模型中的文件映射区,可以unmap取消用用户空间和内核空间的映射
sendfile
sendfile 系统调用函数可以替代前面的 read()
和 write()
这两个系统调用,这样就可以减少一次系统调用,也就减少了 2 次上下文切换的开销。
其次,该系统调用,可以直接把内核缓冲区里的数据拷贝到 socket 缓冲区里,不再拷贝到用户态,这样就只有 2 次上下文切换,和 3 次数据拷贝。
但是这还不是真正的零拷贝技术,如果网卡支持 SG-DMA(The Scatter-Gather Direct Memory Access)技术(和普通的 DMA 有所不同),我们可以进一步减少通过 CPU 把内核缓冲区里的数据拷贝到 socket 缓冲区的过程。
这就是所谓的零拷贝(Zero-copy)技术,因为我们没有在内存层面去拷贝数据,也就是说全程没有通过 CPU 来搬运数据,所有的数据都是通过 DMA 来进行传输的。。
零拷贝技术的文件传输方式相比传统文件传输的方式,减少了 2 次上下文切换和数据拷贝次数,只需要 2 次上下文切换和数据拷贝次数,就可以完成文件的传输,而且 2 次的数据拷贝过程,都不需要通过 CPU,2 次都是由 DMA 来搬运。
五、零拷贝的思路
大多项目可以通过这些系统调用实现,其实主要可以建立起内核空间和用户空间的映射,比如知道缓冲区的虚拟地址和物理地址,就能实现零拷贝技术。