0%

零拷贝


一、零拷贝技术

零拷贝技术,是相对于传统 IO 的一种技术思想。传统 IO :读写数据是在用户空间和内核空间来回复制,而内核空间的数据是通过操作系统层面的 IO 接口从磁盘读取或写入的,中间也需要多次拷贝,因此效率较低。零拷贝技术,目的是尽可能地减少上下文切换和拷贝次数,提升操作性能。

二、传统 IO 实现原理

当应用服务接收客户端的请求时,传统 IO 通常需要两种系统调用:

1
2
3
4
// 读取
read(file, tmp_buf, len);
// 写入
write(socket, tmp_buf, len);

从细分图中可知,虽然只是简单的读写操作,但内部的流程还是比较复杂的。

一次读写将发生 4 次上下文切换:

  • 读取数据:从用户态切换到内核态;
  • 读取完毕:内核完成数据准备,从内核态切换到用户态;
  • 写入数据:从用户态切换到内核态;
  • 写入完毕:内核完成数据写入,从内核态切换到用户态;

一次读写将发生 4 次数据拷贝 (2 次 DMA 拷贝 + 2 次 CPU 拷贝):

  • 第一次拷贝 (DMA):把磁盘文件数据拷贝到内核缓冲区;
  • 第二次拷贝 (CPU):把内核缓冲区的数据拷贝到用户缓冲区,供应用程序使用;
  • 第三次拷贝 (CPU):把用户缓冲区的数据拷贝到内核 socket 缓冲区;
  • 第四次拷贝 (DMA):把内核 socket 缓冲区的数据拷贝到网卡缓冲区;

虽然一次上下文切换需耗时只有几微秒,但在高并发场景中,这种延迟容易被积累和放大,从而影响整体性能。此外,磁盘和网卡操作速度远远小于内存,而内存操作速度又远远小于 CPU,4 次拷贝将严重拖慢系统性能。因此,提高 IO 性能,需要从减少上下文切换次数和数据拷贝次数两方面入手。

三、零拷贝实现

基于以上的讨论,可知零拷贝技术的设计思路:尽可能地减少上下文切换次数和数据拷贝次数。

零拷贝的具体实现方式有:

  • mmap:将内核空间和用户空间的虚拟地址映射到同一物理地址;
  • sendfile:直接把内核缓冲区的数据拷贝到网卡缓冲区;
  • direct IO:在应用层与磁盘、网卡之间建立直接通道;
3.1 mmap 实现零拷贝

在介绍 mmap() 的作用机制之前,先介绍一个新概念:虚拟内存。虚拟内存是现代操作系统中普遍使用的内存结构,使用虚拟地址代替物理内存,有两点好处:一是多个虚拟地址可以指向同一个物理地址;二是虚拟内存空间远远大于物理内存空间。

在传统 IO 中,read() 调用会把内核缓冲区的数据拷贝到用户缓冲区,耗时又耗力。如果把内核空间和用户空间的虚拟地址映射到同一个物理地址,那么就不需要 CPU 来回复制了。

mmap() 正是利用了虚拟内存的这一特性,取代传统 IO 的 read() 操作,并将内核缓冲区和用户缓冲区地址映射到同一物理内存地址,省去一次 CPU 拷贝的过程,提升 IO 性能。具体过程如下:

  • 应用进程调用 mmap() 后,DMA 会把磁盘文件数据拷贝到内核缓冲区;

  • 应用进程跟操作系统内核共享这个缓冲区;

  • 应用进程再调用 write(),直接将内核缓冲区的数据拷贝到内核 socket 缓冲区;

  • DMA 把内核 socket 缓冲区的数据拷贝到网卡缓冲区;

从调用过程可知,与传统 IO 相比,mmap() + write 只减少了 1 次 CPU 拷贝,仍然要发生 4 次上下文切换和 3 次数据拷贝。

3.2 sendfile() 实现零拷贝

senfile() 是 Linux 提供的,专门用于发送文件的系统调用函数。sendfile() 可以替代传统 IO 的 read()、write() 函数,这意味着将省去 2 次上下文切换。此外,数据拷贝路径也有所优化,具体的优化方案与 Linux 内核版本有关,因为在 2.4 版本之后,Linux 提供了 SG-DMA 技术,它将提供比 DMA 技术更进一步的优化策略。

在 2.4 版本之前,CPU 可以直接把内核缓冲区的数据拷贝到内核 socket 缓冲区,省去拷贝到用户缓冲区这一步,因此还存在 2 次上下文切换和 3 次数据拷贝。

具体执行步骤:

  • DMA 把磁盘文件数据拷贝到内核缓冲区;
  • CPU 把内核缓冲区的数据拷贝到内核 socket 缓冲区;
  • DMA 把内核 socket 缓冲区的数据拷贝到网卡缓冲区;

在 2.4 版本之后,引入了 SG_DMA 技术,如果相应的网卡支持该技术,那么就可以把内核缓冲区的数据直接拷贝到网卡缓冲区,也就是说还存在 2 次上下文切换和 2 次数据拷贝。

具体执行步骤:

  • DMA 把磁盘文件数据拷贝到内核缓冲区;
  • 把内核缓冲区描述符和数据长度传到内核 socket 缓冲区;
  • SG-DMA 直接把内核缓冲区的数据拷贝到网卡缓冲区;

3.3 direct IO

直接 IO 是在用户缓冲区和磁盘、网卡之间建立直接通道的技术设计。直接 IO 在读写数据时,可以绕开内核,减少上下文切换和数据拷贝的次数,从而提高效率。

具体执行步骤:

  • DMA 把磁盘文件数据直接拷贝到用户缓冲区;
  • DMA 把用户缓冲区的数据直接拷贝到网卡缓冲区;

直接 IO 使用直接通道操作数据,由应用层完全管理数据,其优缺点也是很明显的。

优点:

  • 应用层与磁盘、网卡建立直接通道,减少了上下文切换和数据拷贝的次数,速度更快;
  • 数据直接缓存在应用层,应用可以更加灵活得操作数据;

缺点:

  • 在应用层引入直接 IO,需要应用层自主管理,给系统增添了额外的复杂度;
  • 若数据不在应用层缓冲区,那么将直接操作磁盘文件读写,将大大拖慢性能;