Nginx之sendfile

1、认识sendfile

nginx大家都再熟悉不过了,这里就不详细介绍了。再nginx众多参数中有个sendfile,想必大家也都用过。但是其中的作用并不是那么简单,下面就开始深入学习下sendfile

传统的文件传输方式

图1:传统的数据拷贝方式
Nginx之sendfile

图2:上下文切换
Nginx之sendfile

  • read() 调用(参见 图 2)引发了一次从用户模式到内核模式的上下文切换。在内部,发出 sys_read()(或等效内容)以从文件中读取数据。直接内存存取(direct memory access,DMA)引擎执行了第一次拷贝(参见 图 1),它从磁盘中读取文件内容,然后将它们存储到一个内核地址空间缓存区中。
  • 所需的数据被从读取缓冲区拷贝到用户缓冲区,read() 调用返回。该调用的返回引发了内核模式到用户模式的上下文切换(又一次上下文切换)。现在数据被储存在用户地址空间缓冲区。
  • send() 套接字调用引发了从用户模式到内核模式的上下文切换。数据被第三次拷贝,并被再次放置在内核地址空间缓冲区。但是这一次放置的缓冲区不同,该缓冲区与目标套接字相关联。。
  • send() 系统调用返回,结果导致了第四次的上下文切换。DMA 引擎将数据从内核缓冲区传到协议引擎,第四次拷贝独立地、异步地发生 。

引入零拷贝方法

  使用中间内核缓冲区(而不是直接将数据传输到用户缓冲区)看起来可能有点效率低下。但是之所以引入中间内核缓冲区的目的是想提高性能。在读取方面使用中间内核缓冲区,可以允许内核缓冲区在应用程序不需要内核缓冲区内的全部数据时,充当 “预读高速缓存(readahead cache)” 的角色。这在所需数据量小于内核缓冲区大小时极大地提高了性能。在写入方面的中间缓冲区则可以让写入过程异步完成。
  不幸的是,如果所需数据量远大于内核缓冲区大小的话,这个方法本身可能成为一个性能瓶颈。数据在被最终传入到应用程序前,在磁盘、内核缓冲区和用户缓冲区中被拷贝了多次。
  零拷贝通过消除这些冗余的数据拷贝而提高了性能。(零拷贝的方法有很多,这里只针对sendfile进行详细介绍了)
###深入学习sendfile
Linux系统使用man sendfile,查看sendfile原型如下:

  1. #include <sys/sendfile.h>
  2. ssize_t sendfile(int out_fd, int in_fd, off_t *offset, size_t count);

参数特别注意的是:in_fd必须是一个支持mmap函数的文件描述符(The in_fd argument must correspond to a file which supports mmap(2)-like operations (i.e., it cannot be a socket)),也就是说必须指向真实文件,不能使socket描述符和管道。out_fd必须是一个socket描述符。由此可见sendfile几乎是专门为在网络上传输文件而设计的。
Sendfile 函数在两个文件描述符之间直接传递数据(完全在内核中操作,传送),从而避免了内核缓冲区数据和用户缓冲区数据之间的拷贝,操作效率很高,被称之为零拷贝。
sendfile系统调用则提供了一种减少以上多次copy,提升文件传输性能的方法。
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 里。
参考链接:https://www.ibm.com/developerworks/cn/java/j-zerocopy/

1
未经许可,不得转载,否则将受到作者追究,博主联系方式见首页右上角
  • 转载请注明来源:Nginx之sendfile
  • 本文永久链接地址:http://www.52devops.com/chuck/1354.html

该文章由 发布

这货来去如风,什么鬼都没留下!!!
发表我的评论
取消评论
代码 贴图 加粗 链接 删除线 签到