Java 面试系列第一篇 零拷贝(上篇)

🍁基于面试的常见问题,学习总结输出的系列文章。可供面试参考。

注: 本文是对零拷贝进行学习整理的文章。

Java 面试系列第一篇 零拷贝

"零拷贝" 演进过程解决了两个问题:

  • 消除数据不必要的拷贝
  • 消除用户态和内核态之间切换带来的开销

零拷贝并非一开始就存在的技术,而是逐步演进的过程。

一、最原始的 IO 过程

下面以用户进程读取磁盘数据为例展开讲解

图片来源

1.1 过程理解

关于 IO 处理的过程结合图片理解如下:

  1. 应用程序读取磁盘数据,调用 read(),从用户态切换为内核态
  2. read() 系统调用由 CPU 完成
  3. CPU 向磁盘发起 IO 请求
  4. 磁盘控制器收到指令,将数据放入到磁盘控制器缓冲区,完成向 CPU 发起一个 IO 中断;
  5. CPU 收到磁盘控制器的 IO 中断后,开始CPU拷贝数据,从磁盘缓冲区到内核缓冲区
  6. 完成后 read()调用返回,从内核态切换成用户态

1.2 不足之处

CPU 资源宝贵,而整个拷贝的过程,独占 CPU 资源。

系统调用是应用程序和内核交互的桥梁,每次进行调用/返回会产生内核态与用户态的切换。

注:本文的上下文切换,特指用户态与内核态上下文切换。

二、DMA 技术演进

DMA: 直接内存访问 Direct Memory Access,可以理解为不需要 CPU 参与的数据访问行为。

还是用户进程读取磁盘数据为例展开讲解

2.1 过程理解

结合图片进行理解:

  1. 用户进程调用 read 方法,发起 IO 操作,用户态切换为内核态,自身进程进入阻塞。
  2. 操作系统将 IO 请求发送给 DMA,此时 CPU 释放
  3. DMA 将 IO 请求发送给磁盘
  4. 磁盘将数据放入磁盘控制器缓冲区,然后通知 DMA 控制器
  5. DMA 控制器将磁盘控制器缓冲数据拷贝到内核缓冲区
  6. 完成拷贝后 DMA 给 CPU 发送中断信号
  7. CPU 收到 DMA 信号,将数据从内核缓冲区拷贝到用户缓冲区
  8. 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 拷贝

拷贝顺序可以理解如下:

  1. DMA 把磁盘数据拷贝到系统内核缓冲区
  2. CPU 把系统内核缓冲区数据拷贝到用户缓冲区
  3. CPU 把用户缓冲区数据再拷贝到 socket 缓冲区
  4. 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 零拷技术

  1. mmap(代替read) + write
  2. sendfile (文件-socket)
  3. sendfile + DMA
  4. splice (文件-文件)

针对过程中提到的 mmap、 pageCache 扩展阅读,请参考下篇。

最后说明

本文是基于 原来 8 张图,就可以搞懂「零拷贝」 进行阅读整理,作者出品不错,感兴趣可以关注。

相关推荐
一只叫煤球的猫6 小时前
写代码很6,面试秒变菜鸟?不卖课,面试官视角走心探讨
前端·后端·面试
bobz9656 小时前
tcp/ip 中的多路复用
后端
bobz9656 小时前
tls ingress 简单记录
后端
皮皮林5517 小时前
IDEA 源码阅读利器,你居然还不会?
java·intellij idea
你的人类朋友7 小时前
什么是OpenSSL
后端·安全·程序员
bobz9658 小时前
mcp 直接操作浏览器
后端
前端小张同学10 小时前
服务器部署 gitlab 占用空间太大怎么办,优化思路。
后端
databook10 小时前
Manim实现闪光轨迹特效
后端·python·动效
武子康11 小时前
大数据-98 Spark 从 DStream 到 Structured Streaming:Spark 实时计算的演进
大数据·后端·spark
该用户已不存在11 小时前
6个值得收藏的.NET ORM 框架
前端·后端·.net