写点什么

细细阅读,3 张图带你理解,零拷贝,mmap 和 sendFile

发布于: 2021 年 06 月 16 日

零拷贝说白了,其实就是传统 IO 的性能实在有点拉胯,所以搞出来一个零拷贝机制提升一下效率。要了解零拷贝的话,首先得先了解一下传统 IO 的执行流程,这里举个例子,通过传统的 IO 进行网络传输来传输一个文件。


相关参考文章:

尽情阅读,技术进阶,详解mmap原理

一文带你,彻底了解,零拷贝Zero-Copy技术

相关学习视频:

c/c++Linux后台服务器开发高级架构师

(免费订阅报名,每晚 20 点大佬直播公开课)

手写用户态协议栈,udpipeth数据包的封装,零拷贝的实现,柔性数组

PS:视频相关学习文档,点击获取


先上一张图,这张图就代表了传统 IO 传输文件的流程。读取文件的时候,会从用户态切换为内核态,同时基于 DMA 引擎将磁盘文件拷贝到内核缓冲区。

看到这里,可能你就已经懵逼了,什么是用户态和内核态,什么是 DMA 拷贝,我用大白话解释一下:首先用户态其实就是 CPU 在执行你的代码,而内核态呢,其实就是你没有那个权限去操作硬件,所以只能交给系统去调用,这个时候就是内核态。

举个例子,你的女朋友需要你修个电脑(醒醒,但凡有一粒花生米也不至于喝成这样),我换个说法,假如你同班的女同学想让你修个电脑,但是宿管阿姨不肯放你进女生宿舍,这个时候你就是用户态,你不能进女生宿舍,所以你只能让宿管阿姨(内核态)来帮你把电脑取出来。

那什么是 DMA 拷贝呢,DMA(DirectMemoryAccess,直接内存存取)其实就是因为 CPU 老哥太累了,所以找了个小弟,就是 DMA 替他完成一部分的拷贝工作,这样 CPU 就能去做其他事情了。

讲完了内核态和用户态还有 DMA 的大概意思,我们接着回到刚才的 IO 流程中,第一步我们将文件从磁盘文件读到了用户缓冲区,此时经历了一次上下文切换和一次拷贝。

由内核态切换为用户态,基于 CPU 把内核缓冲区的数据拷贝到用户缓冲区。

调用 socket 的输出流的 write 方法的话,此时会从用户态切换到内核态,同时基于 CPU 把用户缓冲区里的数据拷贝到 Socket 缓冲区里去,接着会有一个异步化的过程,基于 DMA 引擎从 Socket 缓冲区里把数据拷贝到网络协议引擎里发送出去。

当 IO 操作完成之后,又从内核态切换为用户态。通过上面的步骤可以发现传统的 IO 操作执行,有 4 次上下文的切换和 4 次拷贝,是不是很繁琐。零拷贝的话,一般有 mmap 和 sendFile 两种,一个一个来说。

mmap

mmap 是一种内存映射技术,mmap 相比于传统的 IO 来说,其实就是少了 1 次 CPU 拷贝而已,上图。

传统 IO 里面从内核缓冲区到用户缓冲区有一次 CPU 拷贝,从用户缓冲区到 Socket 缓冲区又有一次 CPU 拷贝。mmap 则一步到位,直接基于 CPU 将内核缓冲区的数据拷贝到了 Socket 缓冲区。

之所以能够减少一次拷贝,就是因为 mmap 直接将磁盘文件数据映射到内核缓冲区,这个映射的过程是基于 DMA 拷贝的,同时用户缓冲区是跟内核缓冲区共享一块映射数据的,建立共享映射之后,就不需要从内核缓冲区拷贝到用户缓冲区了。

虽然减少了一次拷贝,但是上下文切换的次数还是没变。

RocketMQ 中就是使用的 mmap 来提升磁盘文件的读写性能。

sendFile

在 Linux 中,提供 sendFile 函数,实现了零拷贝,依旧是先上图。

可以看到在图中,已经没有了用户缓冲区,因为用户缓冲区是在用户空间的,所以没有了用户缓冲区也就意味着不需要上下文切换了,就省略了这一步的从内核态切换为用户态。

同时也不需要基于 CPU 将内核缓冲区的数据拷贝到 Socket 缓冲区了,只需要从内核缓冲区拷贝一些 offset 和 length 到 Socket 缓冲区。接着从内核态切换到用户态,从内核缓冲区直接把数据拷贝到网络协议引擎里去;同时从 Socket 缓冲区里拷贝一些 offset 和 length 到网络协议引擎里去,但是这个 offset 和 length 的量很少,几乎可以忽略。

sendFile 整个过程只有两次上下文切换和两次 DMA 拷贝,很重要的一点是这里完全不需要 CPU 来进行拷贝了,所以才叫做零拷贝,这里的拷贝指的就是操作系统的层面。

那你肯定会问,那 mmap 里面有一次 CPU 拷贝为啥也算零拷贝,只能说那不算是严格意义上的零拷贝,但是他确实是优化了普通 IO 的执行流程,就像老婆饼里也没有老婆嘛。Kafka 和 Tomcat 内部使用就是 sendFile 这种零拷贝。

总结一下

传统 IO 执行的话需要 4 次上下文切换(用户态 -> 内核态 -> 用户态 -> 内核态 -> 用户态)和 4 次拷贝(磁盘文件 DMA 拷贝到内核缓冲区,内核缓冲区 CPU 拷贝到用户缓冲区,用户缓冲区 CPU 拷贝到 Socket 缓冲区,Socket 缓冲区 DMA 拷贝到协议引擎)。

mmap 将磁盘文件映射到内存,支持读和写,对内存的操作会反映在磁盘文件上,适合小数据量读写,需要 4 次上下文切换(用户态 -> 内核态 -> 用户态 -> 内核态 -> 用户态)和 3 次拷贝(磁盘文件 DMA 拷贝到内核缓冲区,内核缓冲区 CPU 拷贝到 Socket 缓冲区,Socket 缓冲区 DMA 拷贝到协议引擎)。sendfile 是将读到内核空间的数据,转到 socket buffer,进行网络发送,适合大文件传输,只需要 2 次上下文切换(用户态 -> 内核态 -> 用户态)和 2 次拷贝(磁盘文件 DMA 拷贝到内核缓冲区,内核缓冲区 DMA 拷贝到协议引擎)。


文章福利 Linux 后端开发网络底层原理知识学习提升 点击学习资料获取,完善技术栈,内容知识点包括 Linux,Nginx,ZeroMQ,MySQL,Redis,线程池,MongoDB,ZK,Linux 内核,CDN,P2P,epoll,Docker,TCP/IP,协程,DPDK 等等。


用户头像

直奔腾讯去,一起学习:Q群654378476 2021.05.20 加入

我要学完第十代《Linux C/C++服务架构开发》知识体系里的内容,直奔腾讯去,一起学习:Q群654378476 系统学习免费课程:https://ke.qq.com/course/417774?flowToken=1033508

评论

发布
暂无评论
细细阅读,3张图带你理解,零拷贝,mmap和sendFile