前言

在学习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


aozeliu
3 声望0 粉丝