1.零拷贝的作用
这里的拷贝指的是数据在内核缓冲区和应用程序缓冲区直接的传输,并非指进程空间中的内存拷贝(当然这方面也可以实现零拷贝,如传引用和 C 中 move 操作)。
现在假设我们有个服务,提供用户下载某个文件,当请求到来时,我们把服务器磁盘上的数据发送到网络中,这个流程伪代码如下:
代码语言:javascript复制filefd = open(...); //打开文件
sockfd = socket(...); //打开socket
buffer = new buffer(...); //创建buffer
read(filefd, buffer); //从文件内容读到buffer中
write(sockfd, buffer); //将buffer中的内容发送到网络
数据拷贝流程如下图:
上图中绿色箭头表示 DMA copy,DMA(Direct Memory Access)即直接存储器存取,是一种快速传送数据的机制,指外部设备不通过 CPU 而直接与系统内存交换数据的接口技术。红色箭头表示 CPU copy。即使在有 DMA 技术的情况下还是存在 4 次拷贝,DMA copy 和 CPU copy 各 2 次。
2.内存映射
内存映射将用户空间的一段内存区域映射到内核空间,用户对这段内存区域的修改可以直接反映到内核空间,同样,内核空间对这段区域的修改也直接反映到用户空间。
简单来说就是用户空间共享这个内核缓冲区。
使用内存映射来改写后的伪代码如下:
代码语言:javascript复制filefd = open(...); //打开文件
sockfd = socket(...); //打开socket
buffer = mmap(filefd); //将文件映射到进程空间
write(sockfd, buffer); //将buffer中的内容发送到网络
使用内存映射后数据拷贝流如下图所示:
从图中可以看出,采用内存映射后数据拷贝减少为 3 次,不再经过应用程序直接将内核缓冲区中的数据拷贝到 Socket 缓冲区中。
RocketMQ 为了消息存储高性能,就使用了内存映射机制,将存储文件分割成多个大小固定的文件,基于内存映射执行顺序写。
3.零拷贝
零拷贝就是一种避免 CPU 将数据从一块存储拷贝到另外一块存储,从而有效地提高数据传输效率的技术。Linux 内核 2.4 以后,支持带有 DMA 收集拷贝功能的传输,将内核页缓存中的数据直接打包发到网络上,伪代码如下:
代码语言:javascript复制filefd = open(...); //打开文件
sockfd = socket(...); //打开socket
sendfile(sockfd, filefd); //将文件内容发送到网络
使用零拷贝后流程如下图:
零拷贝的步骤为:
(1)DMA 将数据拷贝到 DMA 引擎的内核缓冲区中。 (2)将数据的位置和长度的信息的描述符加到套接字缓冲区。 (3)DMA 引擎直接将数据从内核缓冲区传递到协议引擎。
可以看出,零拷贝并非真正的没有拷贝,还是有 2 次内核缓冲区的 DMA 拷贝,只是消除了内核缓冲区和用户缓冲区之间的 CPU 拷贝。
Linux 中主要的零拷贝系统函数有 sendfile、splice、tee 等。零拷贝比普通传输会快很多,如 Kafka 也使用零拷贝技术。
下图是来住 IBM 官网上普通传输和零拷贝传输的性能对比,可以看出零拷贝比普通传输快了 3 倍左右。
参考文献
mmap详解