sendfile函数精讲

时间 : 15-08-02 栏目 : linux编程 作者 : 老薛 评论 : 2 点击 : 3,527 次

一、Sendfile函数说明
#include
ssize_t sendfile(int out_fd, int in_fd, off_t *offset, size_t count);

sendfile()是作用于数据拷贝在两个文件描述符之间的操作函数.这个拷贝操作是内核中操作的,所以称为"零拷贝".sendfile函数比起read和write函数高效得多,因为read和write是要把数据拷贝到用户应用层操作.

参数说明:
out_fd 是已经打开了,用于写操作(write)的文件描述符;
in_fd 是已经打开了,用于读操作(read)的文件描述符;
offset 偏移量;表示sendfile函数从in_fd中的哪一偏移量开始读取数据.如果是零表示从文件的开始读,否则从相应的便宜量读取.如果是循环读取的时候,下一次offset值应为sendfile函数返回值加上本次的offset的值.
count是在两个描述符之间拷贝的字节数(bytes)

返回值:
如果成功的拷贝,返回写操作到out_fd的字节数,错误返回-1,并相应的设置error信息.

EAGAIN 无阻塞I/O设置O_NONBLOCK时,写操作(write)阻塞了.
EBADF 输出或者输入的文件描述符没有打开.
EFAULT 错误的地址.
EINVAL 描述符不可用或者锁定了,或者用mmap()函数操作的in_fd不可用.

EIO 当读取(read)in_fd时发生未知错误.

二、原理分析

在apache,nginx,lighttpd等web服务器当中,都有一项sendfile相关的配置,在一些网上的资料都有谈到sendfile会提升文件传输性能,那sendfile到底是什么呢?它的原理又是如何呢? 

在传统的文件传输里面(read/write方式),在实现上其实是比较复杂的,需要经过多次上下文的切换,我们看一下如下两行代码: 

read(file,tmp_buf,len)
write(socket,tmp_buf,len)
以上两行代码是传统的read/write方式进行文件到socket的传输。 

当需要对一个文件进行传输的时候,其具体流程细节如下: 
1、调用read函数,文件数据被copy到内核缓冲区 
2、read函数返回,文件数据从内核缓冲区copy到用户缓冲区 
3、write函数调用,将文件数据从用户缓冲区copy到内核与socket相关的缓冲区。 
4、数据从socket缓冲区copy到相关协议引擎。 

以上细节是传统read/write方式进行网络文件传输的方式,我们可以看到,在这个过程当中,文件数据实际上是经过了四次copy操作: 

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

而sendfile系统调用则提供了一种减少以上多次copy,提升文件传输性能的方法。Sendfile系统调用是在2.1版本内核时引进的: 

sendfile(socket,file,len)
运行流程如下: 
1、sendfile系统调用,文件数据被copy至内核缓冲区 
2、再从内核缓冲区copy至内核中socket相关的缓冲区 
3、最后再socket相关的缓冲区copy到协议引擎 


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

sendfile函数在两个文件描述符之间直接传递数据(完全在内核中操作),从而避免了内核缓冲区和用户缓冲区之间的数据拷贝,这被称为零拷贝。sendfile几乎是专门为在网络上传输文件而设计的。

本文标签 ,

除非注明,文章均为( 老薛 )原创,转载请保留链接: http://www.bdkyr.com/xtyw003/877.html

sendfile函数精讲:目前有2 条留言

  1. 沙发
    清风
    Post: 2015-08-05 下午12:37

    sendfile很流行?

    • admin
      admin
      Post: 2015-08-05 下午12:41

      用的较多,性能有很大提升,具体解释如下:

      现在流行的 web 服务器里面都提供 sendfile 选项用来提高服务器性能,那到底 sendfile 是什么,怎么影响性能的呢?sendfile 实际上是 Linux 2.0+ 以后的推出的一个系统调用,web 服务器可以通过调整自身的配置来决定是否利用 sendfile 这个系统调用。先来看一下不用 sendfile 的传统网络传输过程:

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

      硬盘 >> kernel buffer >> user buffer >> kernel socket buffer >> 协议栈

      一般来说一个网络应用是通过读硬盘数据,然后写数据到 socket 来完成网络传输的。上面2行用代码解释了这一点,不过上面2行简单的代码掩盖了底层的很多操作。来看看底层是怎么执行上面2行代码的:

      1、系统调用 read() 产生一个上下文切换:从 user mode 切换到 kernel mode,然后 DMA 执行拷贝,把文件数据从硬盘读到一个 kernel buffer 里。
      2、数据从 kernel buffer 拷贝到 user buffer,然后系统调用 read() 返回,这时又产生一个上下文切换:从kernel mode 切换到 user mode。
      3、系统调用 write() 产生一个上下文切换:从 user mode 切换到 kernel mode,然后把步骤2读到 user buffer 的数据拷贝到 kernel buffer(数据第2次拷贝到 kernel buffer),不过这次是个不同的 kernel buffer,这个 buffer 和 socket 相关联。
      4、系统调用 write() 返回,产生一个上下文切换:从 kernel mode 切换到 user mode(第4次切换了),然后 DMA 从 kernel buffer 拷贝数据到协议栈(第4次拷贝了)。

      上面4个步骤有4次上下文切换,有4次拷贝,我们发现如果能减少切换次数和拷贝次数将会有效提升性能。在kernel 2.0+ 版本中,系统调用 sendfile() 就是用来简化上面步骤提升性能的。sendfile() 不但能减少切换次数而且还能减少拷贝次数。

      再来看一下用 sendfile() 来进行网络传输的过程:

      sendfile(socket, file, len);

      硬盘 >> kernel buffer (快速拷贝到kernel socket buffer) >> 协议栈

      1、系统调用 sendfile() 通过 DMA 把硬盘数据拷贝到 kernel buffer,然后数据被 kernel 直接拷贝到另外一个与 socket 相关的 kernel buffer。这里没有 user mode 和 kernel mode 之间的切换,在 kernel 中直接完成了从一个 buffer 到另一个 buffer 的拷贝。
      2、DMA 把数据从 kernel buffer 直接拷贝给协议栈,没有切换,也不需要数据从 user mode 拷贝到 kernel mode,因为数据就在 kernel 里。

      步骤减少了,切换减少了,拷贝减少了,自然性能就提升了。这就是为什么说在 Nginx 配置文件里打开 sendfile on 选项能提高 web serve r性能的原因。

随便看看

2