细说零拷贝的8种实现方式

1,087 阅读5分钟

参考论文:ZeroCopy: Techniques, Benefits and Pitfalls

Introduction

许多现代操作系统的基础都是很久以前创建的,但现在网络的速度越来越快,需要对操作系统的传统的数据处理的设计进行修订,用于消除不必要的内存访问开销。

零拷贝泛指一类技术的统称,旨在减少不必要的内存访问,一般是通过避免数据拷贝来实现,所以叫零拷贝。

Typical Data Transfer

通常服务器会创建合适大小的缓冲区,从硬盘读取数据到缓冲区并通过网络连接发送给客户端。这里会调用readwrite函数。

从应用的角度看并没有发生任何复制,但从内核的角度看这样做的效率很低,即使硬件支持DMA传输。

DMA允许硬件直接读写系统内存,否则需要CPU负责数据拷贝。

image.png

如图所示,一次读写经历了两次DMA Copy,两次CPU Copy,四次内核态和用户态间的切换,但在此例中数据其实是不需要被传输到应用程序中的。

注:此例中write会在user_to_kernel_copy成功后返回。

ZeroCopy Technique

内核在硬件和应用程序间代理数据传输,相比于应用程序直接访问硬件,多了一次数据复制。此外,硬件和内核之间的通信允许使用DMA来节约CPU资源,但内核和应用程序之间的通信不支持这样的特性,各种零拷贝从这个角度来解决问题。

零拷贝技术大致可以分为三类:

  1. In-Kernel Data Copy
  2. Kernel Bypass
  3. Optimizing Data Transfers Between Usermode and Kernel

In-Kernel Data Copy

mmap

使用内存映射代替读数据,然后在写数据时指定映射缓冲区,如此一来不仅节约了内存,还避免了数据的拷贝。

image.png

相比于传统的数据读写只节省了一次CPU Copy,并且内存映射也是一个昂贵的操作,因此这个方案的性能并不是很好。

sendfile

在基于mmap/write技术的基础上,一些操作系统引入了sendfile系统调用,允许数据从文件内核缓冲区复制到套接字内核缓冲区。

image.png

注:此例中sendfile会在kernel_to_kernel_copy成功后返回。

相比于mmap/write减少了一次系统调用,性能更好。不过sendfile功能有限,通常只用在文件服务器上,其次sendfile的标准化也做得不好。

sendfile with DMA Gather Copy

进一步的改进是消除CPU Copy,但需要硬件支持DMA Gather Copy,Gather表示DMA传输的数据不需要在连续的内存区域中,因此数据不再需要整合传输到socket buffer,而只需要传递一个文件描述符,直接从file buffer获取数据。

image.png

相比于sendfile避免了CPU Copy,没有CPU的参与,Page Cache不会变,但是为了防止缓存不一致,还是需要在DMA之前清空页缓存。

关于DMA导致页缓存不一致的问题:DMA的目的地址有可能在页缓存中,DMA修改了目的地址的数据,但是页缓存没变就会导致不一致。

splice

splice也是操作系统提供的系统调用,功能上和sendfile类似,不同的是splice允许两个任意描述符的连接,而不仅仅是文件到套接字的传输,并且splice在内核缓冲区间的传输并不是CPU Copy,而是利用Linux的Pipe Buffer实现,因此输入输出两个描述符至少有一个是管道的一端,需要结合pipe使用。

Kernel Bypass Techniques

Direct Hardware Control from User Applications

绕过内核,应用程序直接读写硬件,这样做性能很高,但仅适用于集群或网络存储系统的节点间通信等非常特殊的情况,需要专门定制硬件和应用程序,并且可能会带来严重的安全问题。

Kernel-Controlled Direct Data Transfers

一种更好的方案是内核控制应用程序和硬件的直接数据传输,内核可以安排device到user memory的DMA传输,内核不经手数据,这可能需要大量的硬件支持。

实际的实现中需要处理一些重要的问题,例如DMA涉及的用户进程的页面不能被改变,DMA前后Page Cache的一致性等。并且由于用户内存不同于系统内存在物理内存中有固定的位置,DMA的访问可能会受限。此外DMA传输对齐等限制,不允许应用程序指定任意的缓冲区地址。

同步读写在该技术中通常是可以的,但是异步读写是数据必须要在内核缓冲区复制一份,因此该技术在网络IO中不适用,因为在网络传输中,读操作一定是异步的。因此该技术主要用于高速磁盘读写。

Optimizing Data Transfers Between Usermode and Kernel

上面提到的零拷贝技术都是减少user memory和kernel memory之间的数据传输,但普遍问题是适用面较小,应用程序无法处理数据。

内核到硬件可以适用DMA传输,但内核到应用没有这样的技术,但是虚拟内存允许我们在内核和应用间复制和共享内存,虽然粒度较大,为4K或8K字节,本节将讨论两种虚拟内存重映射技术。

Dynamical Remapping with Copy-on-Write

回顾mmap那一小节,write是等待数据传输到socket buffer后返回的,这里可以通过异步写来优化,但write返回后应用可能会修改映射缓冲区中的数据,这里适用Copy-on-Write来解决这个问题。

Copy-on-Write:读时共享,写时复制相应内存页,并修改复制来的内存页。

Buffer Sharing

实现user memory和kernel memory间的快速传输的另一个可能性是fast buffer,一个由内核开发人员提出的框架。

image.png

fbufs为每个用户进程分配一个buffer pool,预分配一些同时映射到user memory和kernel memory的buffers,并且缓存虚拟地址到物理地址的映射。

Summary

上面提到的多数技术都还在理论阶段,本文的重点还是在mmap、sendfile这种In-Kernel Data Copy零拷贝实现上。