上架学姐 8 张图,就可以搞懂「零拷贝」了,原来( 三 )


最后 , 把内核的socket缓冲区里的数据 , 拷贝到网卡的缓冲区里 , 这个过程是由DMA搬运的 。
我们可以得知 , 通过使用mmap来代替read , 可以减少一次数据拷贝的过程 。
但这还不是最理想的零拷贝 , 因为仍然需要通过CPU把内核缓冲区的数据拷贝到socket缓冲区里 , 而且仍然需要4次上下文切换 , 因为系统调用还是2次 。
在Linux内核版本2.1中 , 提供了一个专门发送文件的系统调用函数sendfile , 函数形式如下:
#include<sys/socket.h>ssize_tsendfile(intout_fd,intin_fd,off_t*offset,size_tcount);
它的前两个参数分别是目的端和源端的文件描述符 , 后面两个参数是源端的偏移量和复制数据的长度 , 返回值是实际复制数据的长度 。
首先 , 它可以替代前面的read和write这两个系统调用 , 这样就可以减少一次系统调用 , 也就减少了2次上下文切换的开销 。
其次 , 该系统调用 , 可以直接把内核缓冲区里的数据拷贝到socket缓冲区里 , 不再拷贝到用户态 , 这样就只有2次上下文切换 , 和3次数据拷贝 。 如下图:
上架学姐 8 张图,就可以搞懂「零拷贝」了,原来
文章图片
但是这还不是真正的零拷贝技术 , 如果网卡支持SG-DMA(TheScatter-GatherDirectMemoryAccess)技术(和普通的DMA有所不同) , 我们可以进一步减少通过CPU把内核缓冲区里的数据拷贝到socket缓冲区的过程 。
你可以在你的Linux系统通过下面这个命令 , 查看网卡是否支持scatter-gather特性:
$ethtool-keth0|grepscatter-gatherscatter-gather:on
于是 , 从Linux内核2.4版本开始起 , 对于支持网卡支持SG-DMA技术的情况下 , sendfile系统调用的过程发生了点变化 , 具体过程如下:
第一步 , 通过DMA将磁盘上的数据拷贝到内核缓冲区里;
第二步 , 缓冲区描述符和数据长度传到socket缓冲区 , 这样网卡的SG-DMA控制器就可以直接将内核缓存中的数据拷贝到网卡的缓冲区里 , 此过程不需要将数据从操作系统内核缓冲区拷贝到socket缓冲区中 , 这样就减少了一次数据拷贝;
所以 , 这个过程之中 , 只进行了2次数据拷贝 , 如下图:
上架学姐 8 张图,就可以搞懂「零拷贝」了,原来
文章图片
这就是所谓的零拷贝(Zero-copy)技术 , 因为我们没有在内存层面去拷贝数据 , 也就是说全程没有通过CPU来搬运数据 , 所有的数据都是通过DMA来进行传输的 。
零拷贝技术的文件传输方式相比传统文件传输的方式 , 减少了2次上下文切换和数据拷贝次数 , 只需要2次上下文切换和数据拷贝次数 , 就可以完成文件的传输 , 而且2次的数据拷贝过程 , 都不需要通过CPU , 2次都是由DMA来搬运 。
所以 , 总体来看 , 零拷贝技术可以把文件传输的性能提高至少一倍以上 。
事实上 , Kafka这个开源项目 , 就利用了「零拷贝」技术 , 从而大幅提升了I/O的吞吐率 , 这也是Kafka在处理海量数据为什么这么快的原因之一 。
如果你追溯Kafka文件传输的代码 , 你会发现 , 最终它调用了JavaNIO库里的transferTo方法:
@OverridepubliclongtransferFrom(FileChannelfileChannel,longposition,longcount)throwsIOException{returnfileChannel.transferTo(position,count,socketChannel);}
如果Linux系统支持sendfile系统调用 , 那么transferTo实际上最后就会使用到sendfile系统调用函数 。
曾经有大佬专门写过程序测试过 , 在同样的硬件条件下 , 传统文件传输和零拷拷贝文件传输的性能差异 , 你可以看到下面这张测试数据图 , 使用了零拷贝能够缩短65%的时间 , 大幅度提升了机器传输数据的吞吐量 。
上架学姐 8 张图,就可以搞懂「零拷贝」了,原来
文章图片


推荐阅读