youyichannel

志于道,据于德,依于仁,游于艺!

0%

零拷贝

什么是零拷贝

维基百科的解释:"Zero-copy" describes computer operations in which the CPU does not perform the task of copying data from one memory area to another or in which unnecessary data copies are avoided. This is frequently used to save CPU cycles and memory bandwidth in many time consuming tasks, such as when transmitting a file at high speed over a network, etc., thus improving the performance of programs (processes) executed by a computer

简单来说,就是数据搬运过程不需要通过 CPU,所有的数据都是通过 DMA 来进行传输。

因此,零拷贝不是说不拷贝,而是 CPU 不参与数据拷贝的过程。

那如何实现零拷贝呢?

如何实现零拷贝?

零拷贝的实现方式通常有2种:

  1. mmap + write
  2. sendfile

mmap + write

buf = mmap(file, len);
write(sockfd, buf, len);

mmap() 系统调用会直接把内核缓冲区中的数据映射到用户空间,这样一来,操作系统内核和用户空间就不需要再进行任何的数据拷贝操作。

  1. 应用进程调用 mmap(),DMA 会将磁盘文件的数据拷贝到内核缓冲区中,然后,应用进程和操作系统内核共享这个缓冲区;
  2. 应用进程调用 write(),操作系统直接将内核缓冲区的数据拷贝到 Socket 缓冲区中,这一个过程发生在内核态,由 CPU 来搬运数据;
  3. DMA 将 Socket 缓冲区里的数据拷贝到网卡的缓冲区中。

可以看出,这个方式相比于 read + write 的方式减少了一次数据拷贝的过程。但是,这还不是理论上的零拷贝,因为这个过程仍然需要通过 CPU 将内核缓冲区的数据拷贝到 Socket 缓冲区中,而且仍然需要4次上下文切换。

sendfile

Linux/2.1 版本提供了一个专门用于发送文件到系统调用函数 sendfile

/*
out_fd: 目的端文件描述符
in_fd:源端文件描述符
offset:源端的偏移量
count:复制数据的长度
@return: 实际复制数据的长度
*/
ssize_t sendfile(int out_fd, int in_fd, off_t *offset, size_t count);

使用该系统调用可以替代 read + write 两个系统调用,这样就可以减少一次系统调用,也就减少了两次上下文切换到开销。其次,该系统调用也不需要将内核缓冲区的数据拷贝到Socket缓冲区,这样一来,整个过程只需要2次上下文切换,3次数据拷贝。

但是呢,这还不是真正意义上的零拷贝技术,如果网卡支持 SG-DMA,可以通过 ethtool -k eth0 | grep scatter-gather 命令查看网卡是否支持 scatter-gather 特性。

从 Linux/2.4 版本开始起,对于支持网卡支持 SG-DMA 技术的情况下, sendfile() 系统调用的过程发生了点变化:

  1. 通过 DMA 将磁盘上的数据拷贝到内核缓冲区里;
  2. DMA 会将缓冲区描述符和数据长度传递到 Socket 缓冲区,这样一来,网卡到 SG-DMA 控制器可以直接将内核缓冲区中的数据拷贝到网卡的缓冲区中,此过程就不需要将数据从操作系统的内核缓冲区拷贝到 Socket 的缓冲区呢,减少了一次数据拷贝。

可以看出,整个过程,只有2次上下文切换、2次数据拷贝就可以完成数据传输,不需要通过 CPU,2次数据拷贝都是 DMA 来搬。

关于零拷贝性能,可以参考 IBM 的文章。

Apache Kafka 和 Nginx 等知名项目都有用到零拷贝技术。