目录
零拷贝(Zero-Copy)技术是一个思想,是一种 I/O 操作优化技术,可以快速高效地将数据在文件系统移动和网络接口之间传输数据,而不需要将其从内核空间复制到用户空间。
但零拷贝并不代表一次数据复制都没有,而是尽最大可能的减少。
实际上Zero-Copy中有一项核心技术即DMA,在IO操作中扮演十分重要的角色,具体原理不是本文范围,可自行查阅资料。
先看一个常规的IO操作,需要从磁盘中读取数据,通过网络传输出去。
补充一个技术点
💡 DMA技术是Direct Memory Access的缩写。其意思是“存储器直接访问”。它是指一种高速的数据传输操作,允许在外部设备和存储器之间直接读写数据,既不通过CPU,也不需要CPU干预。(但注意一点:同一设备间数据copy需要CPU进行?)
有了DMA技术之后,过程如下:

还是 4 次数据拷贝,其中两次是 DMA 的拷贝,另外两次则是通过 CPU 拷贝:
第一次拷贝,把磁盘上的数据拷贝到操作系统内核的缓冲区里,这个拷贝的过程是通过 DMA 搬运的。第二次拷贝,把内核缓冲区的数据拷贝到用户的缓冲区里,于是我们应用程序就可以使用这部分数据了,这个拷贝到过程是由 CPU 完成的。第三次拷贝,把刚才拷贝到用户的缓冲区里的数据,再拷贝到内核的 socket 的缓冲区里,这个过程依然还是由 CPU 搬运的。第四次拷贝,把内核的 socket 缓冲区里的数据,拷贝到网卡的缓冲区里,这个过程又是由 DMA 搬运的。我把零拷贝技术分为两大类,分类依据是,是否依托OS的PageCache。
1.基于PageCache:sendfile,mmap,splice
2.脱离PageCache:Direct I/O
需要硬件支持,磁盘到socket之间直接传数据,不做数据处理。
ssize_t sendfile(int out_fd, int in_fd, off_t *offset, size_t count);
sendfile技术有两个发展阶段:
1.在Linux 2.1-2.4的版本中:1次CPU copy,2次dma copy,2次上下文切换(两次系统调用)。

2.Linux 2.4版本后,sendfile + DMA gather copy技术组合实现2次dma copy,2次上下文切换。
只适用于将数据从文件拷贝到 socket 套接字上的传输过程。这种技术需要硬件和驱动的支持,

典型运用场景:kafka消费数据时,消息数据从磁盘传输到网络。
Linux 在 2.6.17 版本引入 splice 系统调用,不仅不需要硬件支持,还实现了两个文件描述符之间的数据零拷贝。在 Linux 2.6.23 版本中, sendfile 机制的实现已经没有了,但是其 API 及相应的功能还在,只不过 API 及相应的功能是利用了 splice 机制来实现的。
与sendfile不同的是,splice允许任意两个文件互相连接,而并不只是文件与socket进行数据传输。对于从一个文件描述符发送数据到socket这种特例来说,一直都是使用sendfile系统调用,而splice一直以来就只是一种机制,它并不仅限于sendfile的功能。

将内核空间地址映射为用户空间地址,rw直接作用内核,保留OS的缓冲能力。
void *mmap(void *start, size_t length, int prot, int flags, int fd, off_t offsize);

2次上下文切换(两次系统调用),2次DMA copy,1次CPU copy。
典型场景:Kafka生产消息落盘时,从socket直接将数据写入内核buffer。
用户空间读取的文件直接与磁盘交互,没有中间 page cache 层。虽然文件的数据本身没有使用任何缓存,但是文件的元数据仍然需要缓存;MySQL 的 O_DIRECT 与 O_DIRECT_NO_FSYNC 配置是一个具体案例。
如何使用:
int open(const char *pathname, int flags, ... /*, mode_t mode */ );
flags指定O_DIRECT。

典型应用案例:数据库管理系统是,缓存完全自治。
总结各类提升IO效能技术对比如下:
| CPU拷贝 | DMA拷贝 | 系统调用 | 上下文切换 | |
|---|---|---|---|---|
| 传统方法 | 2 | 2 | read/write | 4 |
| mmap | 1 | 2 | mmap/write | 4 |
| sendfile | 1 | 2 | sendfile | 2 |
| scatter/gather copy | 0 | 2 | sendfile | 2 |
| splice | 0 | 2 | splice | 2 |
| direct I/O | 0 | 2 | open(O_DIRECT) |
DMA的2次copy都少不了,零拷贝带来是CPU copy的减少和上下文切换的减少。