零拷贝sendfile解析

传统方式read/write send/recv

在传统的文件传输里面(read/write方式),在实现上事实上是比較复杂的,需要通过屡次上下文的切换。咱们看一下例如如下两行代码:   html

1. read(file, tmp_buf, len);       linux

2. write(socket, tmp_buf, len);  nginx

 以上两行代码是传统的read/write方式进行文件到socket的传输。web

当需要对一个文件进行传输的时候,其详细流程细节例如如下:缓存

一、调用read函数,文件数据被copy到内核缓冲区服务器

二、read函数返回。文件数据从内核缓冲区copy到用户缓冲区网络

三、write函数调用。将文件数据从用户缓冲区copy到内核与socket相关的缓冲区。socket

四、数据从socket缓冲区copy到相关协议引擎。函数

以上细节是传统read/write方式进行网络文件传输的方式,咱们可以看到,在这个过程其中。文件数据其实是通过了四次copy操做:性能

硬盘—>内核buf—>用户buf—>socket相关缓冲区(内核)—>协议引擎

新方式sendfile

而sendfile系统调用则提供了一种下降以上屡次copy。提高文件传输性能的方法。

Sendfile系统调用是在2.1版本号内核时引进的:

1. sendfile(socket, file, len);   

执行流程例如如下:

一、sendfile系统调用,文件数据被copy至内核缓冲区

二、再从内核缓冲区copy至内核中socket相关的缓冲区

三、最后再socket相关的缓冲区copy到协议引擎

相较传统read/write方式,2.1版本号内核引进的sendfile已经下降了内核缓冲区到user缓冲区。再由user缓冲区到socket相关 缓冲区的文件copy,而在内核版本号2.4以后,文件描写叙述符结果被改变,sendfile实现了更简单的方式,系统调用方式仍然同样,细节与2.1版本号的 不一样之处在于,当文件数据被拷贝到内核缓冲区时,再也不将所有数据copy到socket相关的缓冲区,而是只将记录数据位置和长度相关的数据保存到 socket相关的缓存,而实际数据将由DMA模块直接发送到协议引擎,再次下降了一次copy操做。

 

1、典型IO调用的问题
一个典型的web服务器传送静态文件(如CSS,JS,图片等)的过程以下:

read(file, tmp_buf, len);
write(socket, tmp_buf, len);


首先调用read将文件从磁盘读取到tmp_buf,而后调用write将tmp_buf写入到socket,在这过程当中会出现四次数据copy,过程如图1所示

                图1

 

1。当调用read系统调用时,经过DMA(Direct Memory Access)将数据copy到内核模式
2。而后由CPU控制将内核模式数据copy到用户模式下的 buffer中
3。read调用完成后,write调用首先将用户模式下 buffer中的数据copy到内核模式下的socket buffer中
4。最后经过DMA copy将内核模式下的socket buffer中的数据copy到网卡设备中传送。

从上面的过程能够看出,数据白白从内核模式到用户模式走了一 圈,浪费了两次copy,而这两次copy都是CPU copy,即占用CPU资源。

 

2、Zero-Copy&Sendfile()
Linux 2.1版本内核引入了sendfile函数,用于将文件经过socket传送。
sendfile(socket, file, len);
该函数经过一次系统调用完成了文件的传送,减小了原来 read/write方式的模式切换。此外更是减小了数据的copy,sendfile的详细过程图2所示:

                图2

经过sendfile传送文件只须要一次系统调用,当调用 sendfile时:
1。首先经过DMA copy将数据从磁盘读取到kernel buffer中
2。而后经过CPU copy将数据从kernel buffer copy到sokcet buffer中
3。最终经过DMA copy将socket buffer中数据copy到网卡buffer中发送
sendfile与read/write方式相比,少了 一次模式切换一次CPU copy。可是从上述过程当中也能够发现从kernel buffer中将数据copy到socket buffer是不必的。

为此,Linux2.4内核对sendfile作了改进,如图3所示

                图3

改进后的处理过程以下:
1。DMA copy将磁盘数据copy到kernel buffer中
2。向socket buffer中追加当前要发送的数据在kernel buffer中的位置和偏移量
3。DMA gather copy根据socket buffer中的位置和偏移量直接将kernel buffer中的数据copy到网卡上。
通过上述过程,数据只通过了2次copy就从磁盘传送出去了。
(可能有人要纠结“不是说Zero-Copy么?怎么还有两次copy啊”,事实上这个Zero copy是针对内核来说的,数据在内核模式下是Zero-copy的。话说回来,文件自己在瓷盘上要真是彻底Zero-copy就能传送,那才见鬼了 呢)。
当前许多高性能http server都引入了sendfile机制,如nginx,lighttpd等。

3、Java NIO中的transferTo()
Java NIO中
FileChannel.transferTo(long position, long count, WriteableByteChannel target)
方法将当前通道中的数据传送到目标通道target中,在支持Zero-Copy的linux系统中,transferTo()的实现依赖于sendfile()调用。

 

4、参考文档
Zero Copy I: User-Mode Perspective》http://www.linuxjournal.com/article/6345?page=0,0
Efficient data transfer through zero copy》http://www.ibm.com/developerworks/linux/library/j-zerocopy
The C10K problem》http://www.kegel.com/c10k.html

相关文章
相关标签/搜索