零拷贝总结(zero copy)

10次阅读

共计 2171 个字符,预计需要花费 6 分钟才能阅读完成。

零拷贝总结(zero copy)
前言
在学习 netty 的过程中,发现 Netty 对缓存做了很多优化,其中零拷贝一直令我迷惑,所以在网上找了一些博客进行学习,并作此总结
定义
零复制(英语:Zero-copy;也译零拷贝)技术是指计算机执行操作时,CPU 不需要先将数据从某处内存复制到另一个特定区域。这种技术通常用于通过网络传输文件时节省 CPU 周期和内存带宽。[1]

应用场景 [2]

在 web 应用中,经常需要传输一些静态文件 (css, js, 图片等),可讲这一过程抽象为下面两个系统调度:
read(file, tmp_buf, len); write(socket, tmp_buf, len);
系统调用会进行上下文切换,共两次上下文切换:
用户态 -> 内核态 -> 用户态
上面的传输过程中,共涉及 4 次上下文切换,4 次数据复制,其流程如下图上图中分为两部分,上半部分表示执行上下文 (PC, 寄存器等),下半部分表示数据区(堆栈等)上班部分描绘了这一过程的上下文切换,下半部分描述了数据移动。
在第一个区间里(处于用户态),执行 read 系统调度,读取文件数据到 tmp_buf 中,因此会进行上下文切换,切换到内核态(第二区间),然后内核从磁盘中读取文件数据到内核的缓冲区中(红线所示,这种复制是利用 DMA 直接从磁盘复制到内存,不经过 CPU 的寄存器,所以称为 DMA copy),接下来在将文件数据从内核缓冲区中复制到用户缓冲区 tmp_buf(蓝色虚线所示,这种复制是先从内存到寄存器,然后寄存器到内核缓冲区,所以称为 CPU copy),并切换回用户态(第三个区间),此时 read 调用就完成了,文件数据存在 tmp_buf 中。
接下来进行 write 系统调用(处于第三个区间,用户态),讲 tmp_buf 的数据写入 socket 中,首先进行上下文切换,进入内核态(第四个区间),然后讲 tmp_buf 的数据复制到 socket 缓冲区中(蓝色虚线所示),最后利用 DMA 从 socket 缓冲区将数据复制到网卡,然后切换回用户态(第五个区间),此时 wirte 执行完成,但是数据可能还没有发送完成。
当需要进行大量的上述操作时,上下文切换和内存复制就会严重的影响性能,为此 Linux 内核提供了 sendFile 来解决这个文件。
为什么 read 调用,需要先将数据从磁盘复制到内核缓冲区,然后再从 kernel buffer 复制到 user buffer 呢?
首先 kernel buffer 和 user buffer 大小不一定相等。kernel buffer 的作用是预读,每次调用 read 时不一定会触发磁盘读,只用当 kernel buffer 中的数据都被读取完后,才会触发,然后从磁盘中多读取一些数据到 kernel buffer(从磁盘的读取数据的大小,并不完全取决于用户执行的大小,还取决于 kernel buffer 的大小,以及文件的大小)。这样当用户多次调用 read 读取同一文件的数据时,就不需要每次都直接从硬盘中读取,因为每次会多读一些数据到 kernel buffer 中,这样 read 就可以直接从 kernel buffer 中读取数据,然后复制到 user buffer,这种做法在每次读取数据量小于 kernel buffer 的大小时,可以显著的提高效率,但是当每次读取数据量超过 kernel buffer 大小时,就会显得低效了。
为什么 write 调用,需要先将数据从 user buffer 复制到 kernel buffer,然后在从 kernel buffer 复制到网卡呢?
这样做的原因是为了快速返回,从 user buffer 复制到 kernel buffer 的速度应该是快于从 kernel buffer 到网卡的,如果直接冲 user buffer 复制到网卡,会影响程序的性能。在将数据从 user buffer 复制到 kernel buffer 后,write 就可以返回了,剩下的就是利用 DMA 从 kernel buffer 读取数据到网卡。我认为,这是阻塞 io 的做法,对于一些异步 io,是可以直接将 user buffer 的数据复制到网卡的(存疑,待以后解决)。
sendfile
Linux2.1
在 Linux2.1 中,sendfile 的执行过程如下图:这里只有一次系统调用,所以只有两次上下文切换:用户态 -> 内核态 -> 用户态数据复制有 3 次:磁盘 -> 内核缓冲区 -> socket 缓冲区 -> 网卡上述的复制种,从内核缓冲区到 socket 缓冲区显得比较多余,因此在 Linux2.4 对其进行了优化
Linux2.4
在 Linux2.4 种,sendfile 的执行过程如下图:关键的改动在于,将 kernel buffer 的信息添加到 socket buffer,然后协议引擎利用这个信息,直接从 kernel buffer 读取数据。但是这种操作,需要网卡支持 gather 操作。
问题
零拷贝,虽然对提升程序性能有很大的帮助,但是对传输的文件的安全性没有保证,相当于明文传输。由于文件直接从磁盘读取到网络,所以无法对文件进行加密。因此对于需要对传输的数据进行加密的程序,如 HTTPS,并不适用。
参考文献
[1] 零复制 [2] 什么是 Zero-Copy?[3] Zero-Copy&sendfile 浅析 [4] Efficient data transfer through zero copy[5] Zero Copy I: User-Mode Perspective

正文完
 0