基础知识思考整理
http://blog.csdn.net/aganlengzi/article/details/53332877
关于Zero-Copy的原理。主要参照的是一篇03年的文章[1](Linux Journal),原理讲得很明白。
首先需要知道应用场景:
适用于静态资源从磁盘到网络的发送(中间不对资源进行改变),这在web server提供的功能中很常见,一个例子是:保存在磁盘上的一张图片应某个网络请求被从磁盘中取出并通过socket发送至请求方。
Linux系统分为user space和kernel space,user space中的很多操作需要通过系统调用陷入到内核中操作,比如文件读取操作。程序员通过某种编程语言提供的编程接口进行文件读取操作,比如C中的read,这个C语言的实现是通过调用kernel space提供的系统调用read实现的。在从user space陷入到kernel space的过程中,需要进行contex switch。同理,在从kernel space到user space的过程中也需要进行contex switch。
网络端口发送数据的时候,有一块socket buffer作为要发送数据的缓存,然后驱动从其中读取数据并组包发送。
对于上述的一张图片的请求和发送,从操作结果或者效果的角度看:我需要做的是将磁盘中的一部分数据直接(不加改变地)搬运到socket的buffer中进行组包再送到发送队列中等待发送的。可以想到:我们从user space发起任务到回到user space,至少要经历两次contex switch,数据从磁盘到发送队列,至少也要经过两次copy(磁盘到socket buffer和socket buffer到发送队列)。
Zero在我的理解中是指在上述过程中不存在多余copy的技术。
怎么会存在多余的copy呢?多余的copy有什么坏处吗?直接按照上述的过程进行不是就能够做到没有多余copy了吗?
多余的copy应该是软硬件历史的原因,这在后面的zero-copy的发展过程中可以看到。多余的copy会占用CPU时间片和内存带宽,造成系统性能的降低。Zero-copy就是想做到上述的过程,即最直观的过程。需要说明的是,并不是所有的数据的传输都适用zero-copy的,而且很大程度上不适用zero-copy的计算要比这种数据传输的应用要多得多,我们必须意识到这中占大多数应用的存在,毕竟使用计算机做的事情并不仅限于存储和传输。个人感觉zero-copy是一种具体问题具体分析和精细化功能实现以换取性能的过程。
我们来看看历史上对上述一张图片的响应的实现方式的变化:
实现1:contex switch 4次,copy 4次
read(file, tmp_buf, len);
write(socket, tmp_buf, len);
上面是系统调用,下面是内存copy。这种方式没有什么可说的,是没有技巧性的中规中矩做法。
实现2:contex switch 4次,copy 3次
tmp_buf = mmap(file, len);
write(socket, tmp_buf, len);
使用mmap在kernel space和user space之间构造了一块共享内存空间,相当于user space和kernel space都是访问统一块物理内存(但是在user space和kernel space中分别被映射到各自的虚拟地址空间中)。这种方式因为这块共享内存会产生问题:当另一个进程操作同一个文件的时候,文件更改,kernel buffer中的内容相当于失效,此时,SIGBUS总线错误会终止之前的进程并产生core错误。解决的方法可以是设置信号的处理函数,但是这种方式可能会屏蔽掉真正发生错误的情况;或者是对操作的文件进行类似于加锁的操作(读写锁等)。或者如果真的想减少copy次数,只能让操作系统内核和硬件进行支持了,这就是后面的实现。
实现3:contex switch 2次,copy 3次
sendfile(socket, file, len); //since kernel 2.1
新的系统调用被添加到linux kernel中以支持文件间的copy操作,主要是不用在不必要的时候bother user space。这种方式的好处是减少了contex switch的次数,但是实际上对于实现2中存在的另一个进程修改使用的同一个文件的情况并不能很好地解决。
实现4:contex switch 2次,copy 2次
sendfile(socket, file, len); //since kernel 2.4
在这种方式中,socket适应实现3的方式,并且获得了硬件的相关的支持,在实现3中的第2步的copy在这种方式中只是copy了必要的描述信息:文件描述符和长度。相比于要copy的数据来说,这大大减少了copy需要的时间。
上述基本是对Zero-copy的理解,相关的内容可能比较老旧,在新版本的kernel中可能已经改变,但是不影响对原理的理解。如果后面涉及到相关的知识,比如说在不同编程语言中的使用等,再进行相应补充。
#include <sys/socket.h>
ssize_t sendfile(int out_fd, int in_fd, off_t *offset, size_t count);
参考
[0] https://en.wikipedia.org/wiki/Zero-copy
[1] http://www.linuxjournal.com/article/6345?page=0,2
[2] https://segmentfault.com/a/1190000006114818
[3] http://www.ibm.com/developerworks/cn/linux/l-cn-zerocopy1/
[4] https://www.ibm.com/developerworks/cn/linux/l-cn-zerocopy2/