图解|零拷贝Zero-Copy技术大揭秘( 二 )
- 应用程序要向网卡写数据 , 调用write()函数实现用户态切换内核态 , 这是第1次切换;
- CPU将用户缓冲区数据拷贝到内核缓冲区 , 这是第1次CPU拷贝;
- DMA控制器将数据从内核缓冲区复制到socket缓冲区 , 这是第1次DMA拷贝;
- 完成拷贝之后 , write()函数返回实现内核态切换用户态 , 这是第2次切换;
- 读过程涉及2次空间切换、1次DMA拷贝、1次CPU拷贝;
- 写过程涉及2次空间切换、1次DMA拷贝、1次CPU拷贝;
4. 零拷贝技术4.1 出现原因我们可以看到 , 如果应用程序不对数据做修改 , 从内核缓冲区到用户缓冲区 , 再从用户缓冲区到内核缓冲区 。 两次数据拷贝都需要CPU的参与 , 并且涉及用户态与内核态的多次切换 , 加重了CPU负担 。
我们需要降低冗余数据拷贝、解放CPU , 这也就是零拷贝Zero-Copy技术 。
4.2 解决思路目前来看 , 零拷贝技术的几个实现手段包括:mmap+write、sendfile、sendfile+DMA收集、splice等 。
文章插图
4.2.1 mmap方式mmap是Linux提供的一种内存映射文件的机制 , 它实现了将内核中读缓冲区地址与用户空间缓冲区地址进行映射 , 从而实现内核缓冲区与用户缓冲区的共享 。
这样就减少了一次用户态和内核态的CPU拷贝 , 但是在内核空间内仍然有一次CPU拷贝 。
文章插图
mmap对大文件传输有一定优势 , 但是小文件可能出现碎片 , 并且在多个进程同时操作文件时可能产生引发coredump的signal 。
4.2.2 sendfile方式mmap+write方式有一定改进 , 但是由系统调用引起的状态切换并没有减少 。
sendfile系统调用是在 Linux 内核2.1版本中被引入 , 它建立了两个文件之间的传输通道 。
sendfile方式只使用一个函数就可以完成之前的read+write 和 mmap+write的功能 , 这样就少了2次状态切换 , 由于数据不经过用户缓冲区 , 因此该数据无法被修改 。
文章插图
文章插图
从图中可以看到 , 应用程序只需要调用sendfile函数即可完成 , 只有2次状态切换、1次CPU拷贝、2次DMA拷贝 。
但是sendfile在内核缓冲区和socket缓冲区仍然存在一次CPU拷贝 , 或许这个还可以优化 。
4.2.3 sendfile+DMA收集Linux 2.4 内核对 sendfile 系统调用进行优化 , 但是需要硬件DMA控制器的配合 。
升级后的sendfile将内核空间缓冲区中对应的数据描述信息(文件描述符、地址偏移量等信息)记录到socket缓冲区中 。
DMA控制器根据socket缓冲区中的地址和偏移量将数据从内核缓冲区拷贝到网卡中 , 从而省去了内核空间中仅剩1次CPU拷贝 。
文章插图
这种方式有2次状态切换、0次CPU拷贝、2次DMA拷贝 , 但是仍然无法对数据进行修改 , 并且需要硬件层面DMA的支持 , 并且sendfile只能将文件数据拷贝到socket描述符上 , 有一定的局限性 。
4.2.4 splice方式splice系统调用是Linux 在 2.6 版本引入的 , 其不需要硬件支持 , 并且不再限定于socket上 , 实现两个普通文件之间的数据零拷贝 。
推荐阅读
- 禁止拷贝构造,禁止bug
- 电脑重装系统教程来了,Win10 20H2正式版安装图解
- 图解|他们是山东科技最强“内核”,致敬齐鲁最美科技工作者
- GitHub上两本图解网络/算法书火了,完整版开放下载
- 关于Netty ByteBuf 的零拷贝
- 被大厂面试官问烂的算法图解:你还不明白如何找出栈中的最小值?
- Maven 编译拷贝资源的时候提示 UTF-8 编码信息
- 零基础小白Python入门必看:通俗易懂,搞定深浅拷贝
- 更新了!深入浅出图解Git,入门到精通(保姆级教程)第三篇
- 图解|什么是高并发利器NoSQL