🍁基于面试的常见问题,学习总结输出的系列文章。可供面试参考。
注: 本文是对零拷贝进行学习整理的文章。
Java 面试系列第一篇 零拷贝
"零拷贝" 演进过程解决了两个问题:
- 消除数据不必要的拷贝
- 消除用户态和内核态之间切换带来的开销
零拷贝并非一开始就存在的技术,而是逐步演进的过程。
一、最原始的 IO 过程
下面以用户进程读取磁盘数据为例展开讲解
1.1 过程理解
关于 IO 处理的过程结合图片理解如下:
- 应用程序读取磁盘数据,调用 read(),从用户态切换为内核态
- read() 系统调用由 CPU 完成
- CPU 向磁盘发起 IO 请求
- 磁盘控制器收到指令,将数据放入到磁盘控制器缓冲区,完成向 CPU 发起一个 IO 中断;
- CPU 收到磁盘控制器的 IO 中断后,开始CPU拷贝数据,从磁盘缓冲区到内核缓冲区
- 完成后 read()调用返回,从内核态切换成用户态
1.2 不足之处
CPU 资源宝贵,而整个拷贝的过程,独占 CPU 资源。
系统调用是应用程序和内核交互的桥梁,每次进行调用/返回会产生内核态与用户态的切换。
注:本文的上下文切换,特指用户态与内核态上下文切换。
二、DMA 技术演进
DMA: 直接内存访问 Direct Memory Access,可以理解为不需要 CPU 参与的数据访问行为。
还是用户进程读取磁盘数据为例展开讲解
2.1 过程理解
结合图片进行理解:
- 用户进程调用 read 方法,发起 IO 操作,用户态切换为内核态,自身进程进入阻塞。
- 操作系统将 IO 请求发送给 DMA,此时 CPU 释放
- DMA 将 IO 请求发送给磁盘
- 磁盘将数据放入磁盘控制器缓冲区,然后通知 DMA 控制器
- DMA 控制器将磁盘控制器缓冲数据拷贝到内核缓冲区
- 完成拷贝后 DMA 给 CPU 发送中断信号
- CPU 收到 DMA 信号,将数据从内核缓冲区拷贝到用户缓冲区
- read()调用返回,内核态切换为用户态
可以看到 IO 设备与系统的数据传输都交给了 DMA控制器完成;而 CPU 不再参与该过程。
2.2 不足支持
- CPU 还是会参与数据从内核缓冲区拷贝到用户缓冲区。
- 还是有用户态与内核态之间的切换
2.3 扩展
支持 DMA的硬件有: 网卡、声卡、显卡、磁盘控制器等。 现在的 IO 设备都有自己的 DMA 控制器了。
有了 DMA 技术以后,再进一步看一个传统服务端文件传输场景。
三、传统文件传输过程
案例:从磁盘读取文件,然后通过网络发送出去,可以理解从服务端下载文件。
3.1 过程理解
传统 IO ,数据读取和写入,需要用户态空间和内核空间来回拷贝。
传统过程,如图:
- 2 次系统调用: read 和 write
- 4 次用户态与内核态的上下文切换,系统调用需要从用户态切换为内核态,完成后,再从内核态切回用户态。(图中1-4)
- 2 次 DMA 拷贝
- 2 次 CPU 拷贝
拷贝顺序可以理解如下:
- DMA 把磁盘数据拷贝到系统内核缓冲区
- CPU 把系统内核缓冲区数据拷贝到用户缓冲区
- CPU 把用户缓冲区数据再拷贝到 socket 缓冲区
- DMA 把 socket 缓冲区最后拷贝到网卡进行发送
3.2 不足支持
过多的数据拷贝过程以及用户态和内核态的切换影响了性能。
3.3 根因分析
用户态权限没有内核态那样对硬件操作的权限,而通过系统函数调用需要进行上下文切换,随之也伴随着数据的多次拷贝。
要想实现性能优化: 减少上下文的切换以及数据拷贝次数。
因此,便发展了一些新的技术,来解决这样的场景。
四、mmap 技术演进
mmap()
系统调用函数会直接把内核缓冲区里的数据映射到用户空间。如下图所示:用户态和内核态不再进行数据拷贝。
mmap + write 组合
4.1 过程理解
- 应用进程调用了 mmap,DMA 会把磁盘的数据拷贝到内核的缓冲区里。应用进程跟操作系统内核「共享」这个缓冲区 (①)
- 应用进程再调用 write(系统调用,CPU执行),操作系统直接将内核缓冲区的数据拷贝到 socket 缓冲区中 (②)
- 最后 DMA 再把内核的 socket 缓冲区里的数据,拷贝到网卡的缓冲区里(③)
通过使用 mmap()
来代替 read()
, 减少一次数据拷贝。
4.2 不足之处
CPU 把内核缓冲区的数据拷贝到 socket 缓冲区里,经历 mmap、write两次系统调用,依然有 4 次上下文切换。
因此需要再进一步演进。
五、sendfile 技术演进
sendfile的出现替换 read 和 write。 这样就只进行 1 次系统调用,即 2 次上下文切换。
注: linux 2.1 内核版本支持
5.1 过程理解
结合上图进行分析。
- sendfile 直接把内核缓冲区里的数据拷贝到 socket 缓冲区里,不再拷贝到用户态,这样就只有 2 次上下文切换,和 3 次数据拷贝。
5.2 不足之处
这个过程还算不上零拷贝。
六、linux 2.4+ sendfile && SG-DMA
从 linux 2.4 起,sendfile()
系统调用对于网卡支持 SG-DMA (The Scatter-Gather Direct Memory Access)技术发生了一些变化。
6.1 过程理解
- DMA 将磁盘上的数据拷贝到内核缓冲区里
- 缓冲区将描述符和数据长度传到 socket 缓冲区; 网卡的 SG-DMA 控制器就可以直接将内核缓存中的数据拷贝到网卡的缓冲区里。
不再需要将数据从操作系统内核缓冲区拷贝到 socket 缓冲区中。从而减少了一次数据拷贝。
经历了 sendfile 两次上下文切换,两次数据拷贝
6.2 零拷贝
这就是所谓的零拷贝(Zero-copy)技术。
- 用户态没有数据拷贝过程
- CPU 没有参考拷贝过程,由 DMA 来进行传输
- 经历 1 次 sendfile 系统调用,2 次上下文切换,2 次数据拷贝过程。
6.3 小结
经过逐步演进,终于理解了零拷贝是什么了。都是逐步演进的过程。
从最开始的 2 次系统调用,4 次上下文切换,4 次数据拷贝过程。减少 1 次 sendfile 系统调用,2 次上下文切换,2 次数据拷贝过程;性能至少提高 1 倍。
6.4 splice
splice 在 Linux 在 2.6 版本引入的,其不需要硬件支持,并且不再限定于socket上,实现两个普通文件之间的数据零拷贝。splice 系统调用可以在内核缓冲区和 socket 缓冲区之间建立管道来传输数据,避免了两者之间的 CPU 拷贝操作。(splice局限:它要求两个文件描述符参数中有一个必须是管道设备)
6.4 零拷技术
- mmap(代替read) + write
- sendfile (文件-socket)
- sendfile + DMA
- splice (文件-文件)
针对过程中提到的 mmap、 pageCache 扩展阅读,请参考下篇。
最后说明
本文是基于 原来 8 张图,就可以搞懂「零拷贝」 进行阅读整理,作者出品不错,感兴趣可以关注。