详解零拷贝技术

2021-12-17 17:48:08   最后更新: 2021-12-17 17:48:08   访问数量:289




 

上一篇文章中,我们介绍了在计算机系统中,CPU 是如何与外围硬件交互的:

 

CPU 是如何与外围设备通信的

 

我们看到,通过 DMA 芯片进行的硬盘读写过程需要进行四次特权级切换和四次拷贝操作。

 

 

 

那么,是否可以对上述过程进行优化,实现对 CPU 性能的提升呢?

 

 

如果能够减少这些特权级切换和拷贝操作,系统性能必然会大幅提升。

 

从这一思路出发,“零拷贝”技术就这样诞生了,主要有以下三个思路:

 

  1. 用户态可以直接操作读写,从而避免特权级切换;
  2. 减少交互过程的拷贝次数;
  3. 写时复制,需要写操作的时候再执行拷贝操作,读数据过程不拷贝。

 

 

如上所述,用户态直接 IO 可以避免特权级切换,最常见的用户态 IO 的例子就是异步 IO 的实现,让用户态进程无需进行特权级切换就可以完成 IO 操作。

 

可以参看异步 IO 两种实现的介绍:

 

POSIX AIO -- glibc 版本异步 IO 简介

 

linux AIO -- libaio 实现的异步 IO 简介及实现原理

 

在追求高性能的数据库、web 服务器等组件通常都会使用异步 IO 来实现性能的提升。

 

 

 

 

此前我们对 linux 下的内存映射 IO 的用法做过详细的介绍:

 

存储映射IO

 

那么,内存映射 IO 究竟是如何实现的呢?

 

4.1 mmap 的执行流程

 

  1. 用户进程执行 mmap 函数后,会在内存虚拟地址空间中分配一段连续的地址空间;
  2. 当用户进行文件操作时,会触发一个特殊的缺页中断;
  3. 内核发起调页请求,将数据从磁盘写入到内存中;
  4. 如果脏页有过修改,经过一段时间后,或执行 msync 函数后,会触发将内存中的脏页写入到文件中。

 

4.2 mmap 的性能

 

内存映射 IO 并没有减少每次磁盘读写过程中的 DMA 拷贝,但却让 CPU 的拷贝减少了,因为 CPU 无需再将数据从内核缓冲区拷贝到用户缓冲区。

 

虚拟地址空间中分配的共享空间成为了一层磁盘的缓存,从而有效提升 IO 性能,尽管会导致一部分碎片空间的浪费,与文件写入的不及时,但在此之后,对所有已被载入到内存的文件内容的读取,都再也无需进行拷贝操作,可以有效提升 IO 效率。

 

 

 

 

5.1 sendfile 零拷贝技术

 

另一种零拷贝技术就是 sendfile 函数,它通过直接从内核缓冲区向 socket 缓冲区拷贝数据,减少了 CPU 将数据从内核缓冲区拷贝到用户缓冲区的过程,也无需进行系统特权级的切换,从而有效提升 IO 效率。

 

但 sendfile 函数的缺点也显而易见,那就是用户态程序无法对文件数据进行任何修改,对于数据库、消息队列等直接读取文件的组件来说,他们并不需要对文件进行任何修改,采用 sendfile 函数来提升 IO 效率是非常合适的。

 

 

 

5.2 sendfile + DMA gather copy

 

sendfile 函数的优势在于对 CPU 拷贝的去除,从而有效提升 IO 性能,但 CPU 仍然要进行从内核缓冲区到 socket 缓冲区的拷贝操作,既然在整个过程中,文件都没有被修改,是否可以进一步省去这一步的拷贝操作呢?

 

答案是可以的,那就是 DMA gather copy 操作。

 

在硬件实现上,DMA 可以直接读取内存的数据。在这样的硬件系统中,操作系统可以将一部分内核缓冲区暴露给 DMA 芯片,从而在 sendfile 实现上让 DMA 芯片直接将内核缓冲区的数据拷贝到网卡缓冲区中,从而让 CPU 在 sendfile 的实现中得以彻底解放,从而实现性能的大幅提升。

 

 

 

5.3 sendfile 的内部实现 -- splice 系统调用

 

由于 DMA gather copy 依赖于硬件实现,这就限制了 sendfile 在不同硬件环境下的表现,那么,是否有什么办法,能够让不支持 DMA gather copy 的硬件实现中也支持这样高性能的零拷贝呢?

 

答案当然也是有的,那就是 splice 系统调用。

 

我们知道,进程间通信的一个高效的方法就是通过管道 pipe,所谓的“管道”实际上是一个 FIFO 缓冲区,这个缓冲区的存在实现了位于管道两端的两个进程之间的高效通信。

 

splice 借鉴了管道的设计思想,它在通信的两端之间创建了一个中间缓冲区,让两端在这个 FIFO 缓冲区中直接进行读写,从而实现对性能的提升。

 

在 linux 内核中,sendfile 在不支持 DMA gather 的硬件环境下,便是通过 splice 系统调用来实现的,它通过一个中间的 FIFO 缓冲连通了内核缓冲区与 socket 缓冲区,从而无需再进行内核缓冲区到 socket 缓冲区的数据拷贝,实现对 CPU 拷贝过程的解放。

 

 

 

 

 

 

欢迎关注微信公众号,以技术为主,涉及历史、人文等多领域的学习与感悟,每周三到七篇推文,只有全部原创,只有干货没有鸡汤

 

实现一个操作系统






操作系统      io      硬盘      零拷贝      sendfile     


京ICP备2021035038号