什么是零拷贝机制(Zero Copy) ?

要理解零拷贝机制,首先需要了解它所要解决的问题,试想一个场景:我们需要从磁盘读取一个文件通过网络输出到一个客户端。

服务端的步骤一般是这样的:

read(file, tmp_buf, len);
write(socket, tmp_buf, len);

虽然只有两个步骤:从磁盘读取文件,将文件写入到socket,但是在操作系统内部经历了一个较为复杂的过程,这个过程如下图所示:

上面部分显示,这个过程经历了四次上下文切换。下面部分显示,经历了四次数据拷贝过程:

  1. 数据从磁盘复制到内核缓冲区
  2. 从内核缓冲区复制到用户空间缓冲区
  3. 从用户缓冲区复制到内核的socket缓冲区
  4. 从socket缓冲区复制到协议引擎(这里是网卡驱动)

这里 要把数据从磁盘复制到内核缓冲区是必须的,因为系统需要读取数据输出给网卡嘛。但是为啥还要从内核复制一份到用户空间呢?应用程序直接使用内核缓冲区的数据不就行了吗?这是因为对于操作系统来说,可能有多个应用程序会同时使用这些数据,并有可能进行修改,如果让大家都使用同一份内核空间的数据就会产生冲突。因此,操作系统设计为:每个应用程序想使用这些数据都必须复制一份到自己的用户空间,这样就不会互相影响了。所以这个机制在碰到数据不需要做修改的场景时就产生了浪费,数据本来可以呆在内核缓冲区不动,没必要再多此一举拷贝一次到用户空间。

为了避免这种浪费,人们一开始采用了mmap调用的方式来进行优化。即应用程序不再调用read,而是采用mmap,mmap会从磁盘复制数据到内核缓冲区,然后与用户进程共享该内核缓冲区,这样就不再需要从内核缓冲区复制到用户缓冲区了,这样就比之前少了一次数据复制过程。不过,这种改进方式存在一些问题,比如在mmap一个文件时,文件被另一个进程截断将会产生错误等。当然,人们也发明了一些方法来解决这类问题,但不管怎样都使得这个过程变得很复杂不易使用。

随着linux内核的发展,在2.1版本中引入了一个叫 sendfile的系统调用。sendfile相当于封装了(mmap,write)的过程,自动处理了文件被截断等问题,除此之外,sendfile还减少了上下文切换次数。sendfile的过程是这样的:

  1. 从磁盘读取文件内容到内核缓冲区
  2. 直接从内核缓冲区复制数据到socket缓冲区
  3. 从socket缓冲区复制到协议引擎(这里是网卡驱动)

这种方式虽好,但是仍然存在一次从内核缓冲区到内核socket缓冲区的复制行为,也就是从内存的一个区域复制到内存的另一个区域的行为,这个可以避免吗?后来人们又对硬件进行了改进,在硬件的帮助下来消除内存之间的数据复制。这种硬件需要支持一种叫“收集”操作的接口,它支持从内存中不同位置收集数据,也就是不再限定于只从内核socket缓冲区来收集数据,而是可以从内核缓冲区去收集。

在linux内核版本2.4中对sendfile调用做了一系列优化来适应这个需求,对于应用程序来说,sendfile的调用方式不需要做任何修改,但是它底层的机制有了一定的改进,如下图所示:

如图所示:数据复制到内核缓冲区以后,不再需要整个拷贝到socket缓冲区,而是只需要将数据的位置和长度信息(append dscr)传输到socket缓冲区,这样DMA引擎会根据这些信息直接从内核缓存区复制数据给协议引擎。

最终,数据只需要从磁盘复制到内存,再从内存复制到协议引擎,跟最开始相比减少了从内核到用户空间,从用户空间到socket缓冲两次复制。但是明明还有两次数据的复制,为什么要叫“零拷贝”呢?这是因为从操作系统的角度来说,数据没有从内存复制到内存的过程,也就没有了CPU参与的过程, 所以对于操作系统来说就是零拷贝了。查看wiki对零拷贝的定义如下:

"Zero-copy" describes computer operations in which the CPU does not perform the task of copying data from one memory area to another.

从定义我们看到,零拷贝是指不需要cpu参与在内存之间复制数据的操作。那这个过程为啥不需要cpu参与呢?

仔细看上面的图示,你会发现:从磁盘复制到内核缓冲区是通过DMA引擎来做而不是cpu,同样的,从socket缓冲区到协议引擎也是由 DMA引擎来做,这样就节省了cpu的工作。而这整个过程都在内核中完成也减少了操作系统的上下文切换开销。

总结一下:

  • 存在用户空间和内核空间的交互行为就会产生上下文切换,所以即使用户空间与内核空间共享同一份缓冲区也一样。
  • 利用硬件支持的DMA引擎可以减少对cpu的使用,磁盘,网卡都有此引擎。
  • 利用硬件的“收集”接口功能可以将数据位置和长度直接传输给硬件相关的引擎,从而让硬件引擎直接从相应的内存区域读取数据。
  • 零拷贝是用于对不变的数据做传输,如果应用程序需要修改数据那势必就不能用到零拷贝了,所以零拷贝可不是万能的。实际上,零拷贝还有其它的一些限制条件,可以参考相关的资料。

除了上面所列出的零拷贝机制,linux中零拷贝技术还有以下几种:

  1. 直接IO:应用程序直接访问硬件存储,内核只辅助数据传输,不进行页缓存。
  2. 写时复制技术:当应用程序不需要修改数据时只保存在内核缓冲区,不复制到用户空间。(这类方法没有dma,需要cpu的全程参与)
  3.  splice:是与sendfile类似的一种方法,适用于将数据从一个地方传送到另一个地方不需要经过应用程序的处理。splice可以在内核空间整块地移动数据,并用可以通过异步方式进行。splice允许任意两个文件之间互相连接,而sendfile只是文件到socket之间,所以sendfile只是splice的了个子集。在2.6.23版本内核中,sendfile的机制已经没有了,api相应的功能换成了splice机制来实现。

除了操作系统的零拷贝机制,在netty里还有一种称之为用户空间的零拷贝机制,但那完全是另一种原理以及解决完全不同问题的一种机制。

 

【参考文献】

https://www.linuxjournal.com/article/6345

https://www.ibm.com/developerworks/cn/linux/l-cn-zerocopy1/index.html

https://www.ibm.com/developerworks/cn/linux/l-cn-zerocopy2/index.html

转载于:https://my.oschina.net/u/150599/blog/3020027

  • 4
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值