写点什么

深入理解掌握零拷贝技术

发布于: 2 小时前

前言

零拷贝技术是指计算机执行操作时,CPU 不需要先将数据从某处内存复制到另一个特定区域。这种技术通常用于通过网络传输文件时节省 CPU 周期和内存带宽。

原始的网络请求,需要数次在用户态和内核态之间切换以及数据的拷贝,这无疑大大影响了处理的效率,零拷贝技术就是为解决这一问题而诞生的。

我们常见的高性能组件(Netty、Kafka 等),其内部基本都应用了零拷贝,在学习这些组件之前,有必要先了解什么是零拷贝。

推荐视频:

C++架构师学习地址:C/C++Linux服务器开发高级架构师/Linux后台架构师

零拷贝的实现,用户态协议栈ntytcp

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

传统文件传输 read + write

DMA 拷贝:指外部设备不通过 CPU 而直接与系统内存交换数据的接口技术

如上图所示,传统的网络传输,需要进行 4 次用户态和内核态切换,4 次数据拷贝(2 次 CPU 拷贝,2 次 DMA 拷贝)

上下文的切换涉及到操作系统,相对 CPU 速度是非常耗时的,而且仅仅一次文件传输,竟然需要 4 次数据拷贝,造成 CPU 资源极大的浪费

不难看出,传统网络传输涉及很多冗余且无意义的操作,导致应用在高并发情况下,性能指数级下降,表现异常糟糕

为了解决这一问题,零拷贝技术诞生了,他其实是一个抽象的概念,但其本质就是通过减少上下文切换和数据拷贝次数来实现的

mmap + write

如上图所示,mmap 技术传输文件,需要进行 4 次用户态和内核态切换,3 次数据拷贝(1 次 CPU 拷贝、两次 DMA 拷贝)

相对于传统数据传输,mmap 减少了一次 CPU 拷贝,其具体过程如下:

  1. 应用进程调用 mmap() ,DMA 会把磁盘的数据拷贝到内核的缓冲区里,应用进程跟操作系统内核「共享」这个缓冲区

  2. 应用进程再调用 write(),操作系统直接将内核缓冲区的数据拷贝到 socket 缓冲区中,这一切都发生在内核态,由 CPU 来搬运数据

  3. 最后,把内核的 socket 缓冲区里的数据,拷贝到网卡的缓冲区里,这个过程是由 DMA 搬运的

显然仅仅减少一次数据拷贝,依然难以满足要求

sendfile

如上图所属,sendfile 技术传输文件,需要进行 2 次用户态和内核态的切换,3 次数据拷贝(1 次 CPU 拷贝、两次 DMA 拷贝)

相对于 mmap,其又减少了两次上下文的切换,具体过程如下:

  1. 应用调用 sendfile 接口,传入文件描述符,应用程序切换至内核态,并通过 DMA 将磁盘上的数据拷贝到内核缓冲区中

  2. CPU 将缓冲区数据拷贝至 Socket 缓冲区

  3. DMA 将数据拷贝到网卡的缓冲区里,应用程序切换至用户态

sendfile 其实是将原来的两步读写操作进行了合并,从而减少了 2 次上下文的切换,但其仍然不是真正意义上的“零”拷贝

文章福利:现在 C++程序员面临的竞争压力越来越大。那么,作为一名 C++程序员,怎样努力才能快速成长为一名高级的程序员或者架构师,或者说一名优秀的高级工程师或架构师应该有怎样的技术知识体系,这不仅是一个刚刚踏入职场的初级程序员,也是工作三五年之后开始迷茫的老程序员,都必须要面对和想明白的问题。为了帮助大家少走弯路,技术要做到知其然还要知其所以然。以下视频获取点击:C++架构师学习资料

如果想学习 C++工程化、高性能及分布式、深入浅出。性能调优、TCP,协程,Nginx 源码分析 Nginx,ZeroMQ,MySQL,Redis,MongoDB,ZK,Linux 内核,P2P,K8S,Docker,TCP/IP,协程,DPDK 的朋友可以看一下这个学习地址C/C++Linux服务器开发高级架构师/Linux后台架构师

sendfile + SG-DMA

从 Linux 内核 2.4 版本开始起,对于支持网卡支持 SG-DMA 技术的情况下, sendfile() 系统调用的过程发生了点变化,如上图所示,sendfile + SG-DMA 技术传输文件,需要进行 2 次用户态和内核态的切换,2 次数据拷贝(1 次 DMA 拷贝,1 次 SG-DMA 拷贝)

具体过程如下:

  1. 通过 DMA 将磁盘上的数据拷贝到内核缓冲区里;

  2. 缓冲区描述符和数据长度传到 socket 缓冲区,这样网卡的 SG-DMA 控制器就可以直接将内核缓存中的数据拷贝到网卡的缓冲区里,此过程不需要将数据从操作系统内核缓冲区拷贝到 socket 缓冲区中,这样就减少了一次数据拷贝;

此种方式对比之前的,真正意义上去除了 CPU 拷贝,CPU 的高速缓存再不会被污染了,CPU 可以去执行其他的业务计算任务,同时和 DMA 的 I/O 任务并行,极大地提升系统性能。

但他的劣势也很明显,强依赖于硬件的支持

splice

Linux 在 2.6.17 版本引入 splice 系统调用,不再需要硬件支持,同时还实现了两个文件描述符之间的数据零拷贝。

splice 系统调用可以在内核空间的读缓冲区(read buffer)和网络缓冲区(socket buffer)之间建立管道(pipeline),从而避免了用户缓冲区和 Socket 缓冲区的 CPU 拷贝操作。

基于 splice 系统调用的零拷贝方式,整个拷贝过程会发生 2 次用户态和内核态的切换,2 次数据拷贝(2 次 DMA 拷贝),具体过程如下:

  1. 用户进程通过 splice() 函数向内核(kernel)发起系统调用,上下文从用户态(user space)切换为内核态(kernel space)。

  2. CPU 利用 DMA 控制器将数据从主存或硬盘拷贝到内核空间(kernel space)的读缓冲区(read buffer)。

  3. CPU 在内核空间的读缓冲区(read buffer)和网络缓冲区(socket buffer)之间建立管道(pipeline)。

  4. CPU 利用 DMA 控制器将数据从网络缓冲区(socket buffer)拷贝到网卡进行数据传输。

  5. 上下文从内核态(kernel space)切换回用户态(user space),splice 系统调用执行返回。

splice 拷贝方式也同样存在用户程序不能对数据进行修改的问题。除此之外,它使用了 Linux 的管道缓冲机制,可以用于任意两个文件描述符中传输数据,但是它的两个文件描述符参数中有一个必须是管道设备

总结

本文简单介绍了 Linux 中的几种 Zero-copy 技术,随着技术的不断发展,又出现了诸如:写时复制、共享缓冲等技术,本文就不再赘述。

广义的来讲,Linux 的 Zero-copy 技术可以归纳成以下三大类:

  • 减少甚至避免用户空间和内核空间之间的数据拷贝:在一些场景下,用户进程在数据传输过程中并不需要对数据进行访问和处理,那么数据在 Linux 的 Page Cache 和用户进程的缓冲区之间的传输就完全可以避免,让数据拷贝完全在内核里进行,甚至可以通过更巧妙的方式避免在内核里的数据拷贝。这一类实现一般是是通过增加新的系统调用来完成的,比如 Linux 中的 mmap(),sendfile() 以及 splice() 等。

  • 绕过内核的直接 I/O:允许在用户态进程绕过内核直接和硬件进行数据传输,内核在传输过程中只负责一些管理和辅助的工作。这种方式其实和第一种有点类似,也是试图避免用户空间和内核空间之间的数据传输,只是第一种方式是把数据传输过程放在内核态完成,而这种方式则是直接绕过内核和硬件通信,效果类似但原理完全不同。

  • 内核缓冲区和用户缓冲区之间的传输优化:这种方式侧重于在用户进程的缓冲区和操作系统的页缓存之间的 CPU 拷贝的优化。这种方法延续了以往那种传统的通信方式,但更灵活。

用户头像

Linux服务器开发qun720209036,欢迎来交流 2020.11.26 加入

专注C/C++ Linux后台服务器开发。

评论

发布
暂无评论
深入理解掌握零拷贝技术