零拷贝

92 阅读4分钟

零拷贝

前言

如果对IO流程还不太熟悉的话可以试试看这篇文章IO读写流程或其他文章,在这之后,对零拷贝会有一个更全面的理解。

栗子

举个栗子,从磁盘读取一个文件的内容,然后经由程序处理,最后发送给客户端,这个过程可以抽象为如下流程:

在这里插入图片描述

  1. 内核模式下,从磁盘读取文件内容到read缓冲区
  2. CPU从内核模式切换会用户模式
  3. 在用户空间下,处理完内容后,将内容写到socket缓冲区,此时促发CPU再次从用户模式切回到内核模式
  4. 内容写到socket缓冲区完成后,促发CPU再次从内核模式切回到用户模式,通过DMA引擎(或其他方式)将内核缓冲区中的数据传拷贝到网卡接口。

通过上述流程,我们不难发现,有个过程是比较累赘的。如果有一种方式,可以让内核和用户空间共享某一块“区域”,那数据的拷贝不就可以减少一次拷贝吗,即2过程。

答案是有的,那就是我们的零拷贝技术,它可以帮我们减少数据拷贝的步骤或CPU在内核模式和用户模式间反复横跳的次数。

MMAP

零拷贝技术之一,MMAP,一种内存映射文件的方法,通过调用MMAP方式的IO流程如图所示:

在这里插入图片描述

  1. 发出mmap系统调用,用户空间到内核空间的上下文切换。内核模式下,通过DMA引擎将磁盘文件中的内容拷贝到内核空间缓冲区中。
  2. mmap系统调用返回,导致内核空间到用户空间的上下文切换。接着用户空间和内核空间共享这个缓冲区,而不需要将数据从内核空间拷贝到用户空间
  3. 发出write系统调用,用户空间到内核空间的第三次上下文切换。将数据从内核空间缓冲区拷贝到socket缓冲区。
  4. write系统调用返回,内核空间到用户空间的第四次上下文切换。通过DMA引擎(或其他方式)将内核缓冲区中的数据传拷贝到网卡接口。

感谢MMAP,帮我们省去了一次数据拷贝~!

sendfile

如果我们不需要操作数据,只想直接把文件发送出去呢,上述流程是否还可以优化呢?此时,我们可以发现2、3过程并不太需要。如果能够我发起一个调用,剩下的直接在内核模式下完成就好了。这就是零拷贝技术的第二种方式:sendfile

在这里插入图片描述

  1. 发出sendfile系统调用,用户空间到内核空间的第一次上下文切换。
  2. 内核模式下,通过DMA引擎将磁盘文件中的内容拷贝到内核空间缓冲区中。
  3. 不再将数据拷贝到socket缓冲区而是将相应的描述符信息拷贝到相应的socket缓冲区当中。(如数据在缓冲区的的内存地址、偏移量等)
  4. sendfile系统调用返回, 内核空间到用户空间的第二次上下文切换。
  5. 通过DMA引擎根据socket缓冲区中描述符提供的位置和偏移量信息,将内核空间缓冲区中的数据拷贝到网卡接口上。

注意第3步骤

  • Linux2.4内核中做了改进,将Kernel buffer中对应的数据描述信息(内存地址,偏移量)记录到相应的socket缓冲区当中,这样连内核空间中的一次cpu copy也省掉了,在这之前还是需要将内容拷贝到socket缓冲区的。

感谢sendfile~

应用

  • RocketMQ采用零拷贝MMAP的方式来回应Consumer的请求。
  • kafka将大量的网络数据持久化到磁盘和磁盘文件时通过网络发送的过程,使用了sendfile零拷贝方式。
  • Java NIO(MappedByteBuffer、DirectByteBuffer)、Netty。