在传统的 Linux 程序中,如果需要读取外设中数据,发生的过程为:
DMA 将数据拷贝到内核缓冲区
CPU 将数据拷贝到用户空间。用户读取到数据
如果用户需要将数据写到设备中,发生的过程为
CPU 将数据从用户空间拷贝到设备缓冲区
DMA 从设备缓冲区读取数据并写到设备中
之所以出现数据从内核到用户空间的拷贝,根本原因是用户态进程没有对外设的直接访问能力。CPU 参与的拷贝过程会降低系统的吞吐量。零拷贝技术就是为了减少 CPU 拷贝。
mmap
首先出现的是 mmap,mmap 有两个步骤:
DMA 将数据拷贝到内核缓冲区
内核将此缓冲区共享给用户态进程
从这个方面来看,mmap 相比 read 减少了一次 CPU 拷贝。但是对写数据没有帮助。
sendfile
如果只是用来发送文件,可以使用 sendfile 系统调用。其接口为
#include <sys/socket.h>
ssize_t sendfile(int out_fd, int in_fd, off_t *offset);
相比 read + write 的方式,其工作方式为:
DMA 将数据写到内核缓冲区
CPU 将数据拷贝到套接字缓冲区
DMA 将数据冲套接字缓冲区发送到网线
尽管 CPU 依然需要拷贝一次,但是由于只使用了一个系统调用,因此只陷入了一次内核
如果网卡支持 SG-DMA(The Scatter-Gather Direct Memory Access)技术,可以进一步提高性能。在此种情况下,sendfile 的工作方式为:
DMA 将数据写到内核缓冲区
将缓冲区描述符和数据长度写到 socket 缓冲区中
网卡的 SG-DMA 控制器直接将内核缓存中的数据拷贝到网卡的缓冲区并发送
此种情况下,陷入内核一次,CPU 参与拷贝零次
和 Linux 类似,Windows 也有类似的 transmitFile 函数 |