Linux下的零拷贝

Reference:  https://segmentfault.com/a/1190000011989008segmentfault

 

零拷贝是什么?

维基百科对“零拷贝”是这样描述的:缓存

"Zero-copy" describes computer operations in which the CPU does not perform the task of copying data from one memory area to another. This is frequently used to save CPU cycles and memory bandwidth when transmitting a file over a network.网络

“零拷贝” 描述的是CPU不执行拷贝数据从一块内存区域到另外一块区域的任务的计算机操做。它一般用于在网络上传输文件时节省CPU周期和内存带宽。简单来讲,零拷贝就是一种避免 CPU 将数据从一块存储拷贝到另一块存储的技术。异步

为何须要零拷贝技术?

一般咱们会有这样的需求:将本地磁盘上的一个文件经过网络发送给远端的另外一个服务。在传统的I/O中,咱们经过一张图来看一下操做系统都会发生什么:
图片描述socket

  1. 发出read()系统调用,这时处理器会从用户空间切换至内核空间;性能

  2. 向磁盘请求数据;spa

  3. 经过DMA将文件从磁盘上读取到内核空间缓冲区;操作系统

  4. read()系统调用返回,将数据从内核空间缓冲区拷贝至用户空间缓冲区,这时候处理器会从内核空间切换至用户空间;orm

  5. 发出write()系统调用,并将数据从用户空间缓冲区拷贝至目标socket 在内核空间的缓冲区,这时候处理器会从用户空间切换至内核空间;blog

  6. write()调用返回;

  7. 经过DMA将数据从内核空间缓冲区中拷贝至协议引擎(该操做是独立且异步的)。

总的来讲:传统的I/O操做在整个过程当中将会产生4次上下文切换和4次数据拷贝。

Q:有人可能会问, 为何write()调用会先返回,难道他会在数据传输前返回?
A:事实上调用的返回并不保证数据被传输,甚至他并不保证传输的开始,只是意味着以太网驱动程序在其传输队列中有空位,并已经接受咱们的将要传输的数据。在咱们以前极可能还有不少数据包在排除。除非驱动程序或硬件实现优先级环或队列,不然数据都将以先进先出的方式被传输。

了解了传统I/O的操做后,咱们再来观察一下整个过程,咱们会注意到第二次和第三次数据拷贝是彻底没有意义的,应用程序仅仅缓存了一下数据就又原封不动的把它发送给了目标socket 缓冲区。并且这两次拷贝是须要CPU全程参与的,从操做系统的角度来讲,若是 CPU 一直被占用着去执行这项简单的任务,那么这将会是很浪费资源的;若是有其余比较简单的系统部件能够代劳这件事情,从而使得 CPU 解脱出来能够作其余的事情,那么系统资源的利用则会更加有效。

“零拷贝”正是经过消除这些多余的拷贝来提高性能的。在数据传输的过程当中,避免数据在内核空间缓冲区和用户空间缓冲区之间进行拷贝,以及数据在内核空间缓冲区内的CPU拷贝。


零拷贝的实现机制

Linux 中提供相似的系统调用主要有 sendfile()、mmap() 和splice()(本文对该系统调用暂不作讨论)。

经过sendfile()实现的零拷贝

sendfile系统调用在内核版本2.1中被引入,目的是简化经过网络在两个本地文件之间进行的数据传输过程。sendfile系统调用的引入,不只减小了数据复制,还减小了上下文切换的次数。为了更好的说明,请看下图:
图片描述

  1. 发出sendfile()系统调用,这时处理器会从用户空间切换至内核空间;

  2. 向磁盘请求数据;

  3. 经过DMA将文件从磁盘上读取到内核空间缓冲区;

  4. 将数据从内核空间缓冲区拷贝到目标socket缓冲区;

  5. Sendfile()返回,这时处理器从内核空间切换至用户空间;

  6. 经过DMA将数据从目标socket缓冲区拷贝至协议引擎。

总结一下这种实现,整个过程产生了2次上下文切换和3次数据拷贝(其中2次DMA拷贝和1次CPU拷贝)。

该实现虽然减小了2次上下文切换,但仍然还有1次CPU拷贝。那此次拷贝是否是也能够省掉呢?答案是确定的。可是须要底层操做系统的一些支持。那就是带有DMA收集功能的sendfile实现的零拷贝。

带有DMA收集功能的sendfile实现的零拷贝

从Linux2.4开始,操做系统底层提供了带有scatter/gather的DMA来从内核空间缓冲区中将数据读取到协议引擎中。这就意味着等待传输的数据不须要在连续存储器中,它能够分散在不一样的内存位置。那这样一来,从文件中读出的数据就没必要拷贝至目标socket的缓冲区中,只须要将缓冲区描述符添加到目标socket的缓冲区中,DMA收集操做会根据缓冲区描述符中的信息将内核空间缓冲区中的数据读取到协议引擎。这种方法不只减小了上下文切换、还减小了由CPU参与的数据拷贝。为了更好的理解这种方法所涉及的操做,请看下图:

图片描述

  1. 发出sendfile()系统调用,处理器从用户空间切换至内核空间;

  2. 经过DMA将数据copy至内核空间缓冲区;

  3. 将数据在内核空间缓冲区的地址和偏移量拷贝至目标socket的缓冲区;

  4. Sendfile()返回,处理器从内核空间切换至用户空间。

  5. 带有scatter/gather 功能的DMA将数据直接从内核缓冲区读取到协议引擎,从而消除了最后一次CPU拷贝。

总结一下,这种方法产生了2次上下文切换和2次数据拷贝。

这时有人可能会问,若是我把数据从磁盘上读出来后,再编辑一下,再发送出去,以上所说的零拷贝岂不是不能实现?
对于该问题,Linux提供了mmap来实现。

经过mmap实现的零拷贝

mmap(内存映射):mmap操做提供了一种机制,让用户程序直接访问设备内存,这种机制,相比较在用户空间和内核空间互相拷贝数据,效率更高。

图片描述

  1. 发出mmap()系统调用,处理器从用户空间切换至内核空间。

  2. 向磁盘请求数据;

  3. 经过DMA将数据从磁盘拷贝至内核空间缓冲区;

  4. mmap()调用返回,这时候用户程序和操做系统共享这个缓冲区,不须要再将数据从kernel buffer 拷贝至 user buffer,处理器从内核空间切换至用户空间;

  5. 用户逻辑处理;

  6. 发出write()系统调用,将数据从内核空间缓冲区拷贝至目标socket缓冲区,这时处理器从用户空间切换至内核空间;

  7. write()调用返回,处理器从内核空间切换至用户空间;

  8. 经过DMA将数据拷贝至协议引擎。

总结一下:这种方法将产生4次上下文切换和3次数据拷贝。

至此,零拷贝技术就介绍完了。本文所说起的零拷贝技术都是须要底层操做系统支持的,同时,零拷贝技术一直是在不断地发展和完善当中的,本文并无涵盖 Linux 上出现的全部零拷贝技术。

相关文章
相关标签/搜索