likes
comments
collection
share

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

作者站长头像
站长
· 阅读数 5

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

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

Java 面试系列第一篇 零拷贝

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

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

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

一、最原始的 IO 过程

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

Java 面试系列第一篇 零拷贝(上篇) 图片来源

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 参与的数据访问行为。

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

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

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 ,数据读取和写入,需要用户态空间和内核空间来回拷贝。

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

传统过程,如图:

  • 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() 系统调用函数会直接把内核缓冲区里的数据映射到用户空间。如下图所示:用户态和内核态不再进行数据拷贝。

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

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 次上下文切换。

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

注: 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)技术发生了一些变化。

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

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局限:它要求两个文件描述符参数中有一个必须是管道设备)

Java 面试系列第一篇 零拷贝(上篇) 图片来源

6.4 零拷技术

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

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

最后说明

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