sendfile相关「建议收藏」

sendfile相关「建议收藏」考虑将一个本地文件通过socket发送出去的问题。我们通常的做法是:打开文件fd和一个socket,然后循环地从文件fd中read数据,并将读取的数据send到socket中。这样,每次读写我们都需要两次系统调用,并且数据会被从内核拷贝到用户空间(read),再从用户空间拷贝到内核(send)。而sendfile就将整个发送过程封装在一个系统调用中,避免了多次系统调用,避免了数据在内核空间

大家好,又见面了,我是你们的朋友全栈君。考虑将一个本地文件通过socket发送出去的问题。我们通常的做法是:打开文件fd和一个socket,然后循环地从文件fd中read数据,并将读取的数据send到socket中。这样,每次读写我们都需要两次系统调用,并且数据会被从内核拷贝到用户空间(read),再从用户空间拷贝到内核(send)。

而sendfile就将整个发送过程封装在一个系统调用中,避免了多次系统调用,避免了数据在内核空间和用户空间之间的大量拷贝。

ssize_t sendfile(int out_fd, int in_fd, off_t *offset, size_t count);

虽然这个系统调用接收in和out两个fd,但是有所限制,in只能是普通文件,out只能是socket(这个限制不知道后来的内核版本有没有放宽)。

一、什么是“零拷贝”

零拷贝(zero-copy)基本思想是:数据报从网络设备到用户程序空间传递的过程中,减少数据拷贝次数,减少系统调用,实现CPU的零参与,彻底消除CPU在这方面的负载。实现零拷贝用到的最主要技术是DMA数据传输技术和内存区域映射技术。

先看普通网络服务守护进程的一般服务方法:

表面上看来,系统的负荷似乎只是两个系统调用,而没什么开销。如果这么认为,那么这就大错特错了。在这两个函数的背后,数据至少已经被复制了 4 次,以及几乎一样的用户空间到内核空间的切换次数(上下文切换)。下图可以很好的说明这一个过程:


sendfile相关「建议收藏」

分析上图中的步骤:

第一步,系统调用 read() 导致了用户空间到内核空间的切换。这时,DMA模块将磁盘上的文件内容拷贝(CMA COPY)到内核缓冲区,这就完成了第 1 次复制。

第二步,将内核缓冲区中的内容拷贝到用户缓冲区(kernel buffer –> user buffer),这样又导致了内核空间到用户空间的上下文切换。这时,我们需要的数据已经存放在 tmp_buf 中,这是第 2 次复制。

第三步,在调用 write() 时,又导致了用户空间到内核空间的上下文切换。数据从用户空间缓冲区再次被复制到内核空间缓冲区。这时完成了第 3 次的复制。这里需要注意的是,这次所用的内核缓冲区是与 socket 相关的缓冲区,而非“第一步”中的缓冲区。

第四步,write() 系统调用返回,这时导致了第 4 次的上下文切换。这一次是 DMA 模块将内核中的数据(socket buffer)拷贝到协议引擎中。这些动作是与代码的执行是独立并且是异步发生的。反过来说,假如是非独立且同步的,那么函数返回时那么数据也同时被发送。事实上并非如此,函数返回并不能说明数据被发送出去了,甚至是 write() 的返回都无法保证传输的开始!为什么这么说呢?这是因为,调用的返回,只是表明以太网驱网卡的动程序在其传输队列中有空位,并且已经接受我们的数据用于传输。这时候,有一种情况可能是,还有许多数据还排在我们这些数据的前头,除非我们的数据拥有特权或优先级很高(如果以太网驱动程序是采用队列优先级的方法来发送数据且我们的数据的优先级确实很高),否则数据按照 FIFO 这种先进先出的方法传送。所以在上图中,红色虚线箭头正表示了我们的传输可能发生延迟,若是实线,则刚好发送而没延迟。

正如上面所分析,整个过程中存在着数据冗余。某些冗余可以消除以减少开销并提升性能。有些硬件支持完全绕开内存,可以将数据直接传递给其它设备,这样的特性避免了系统内存中的数据副本,虽然这是一种很好的选择,但并非所有的硬件都能如此。此外,来自硬盘的数据必须重新打包(地址连续)才能用于网络传输,这让情况也会变得有些复杂。

为了减少开销,我们就从消除内核缓冲区和用户缓冲区之间的复制入手。

消除的一种方法就是使用 mmap() 系统调用来替代 read() 系统调用,例如:

tmp_buf = mmap(file, len);  write(socket, tmp_buf, len);

工作原理如下图:

sendfile相关「建议收藏」

从上图可见:

第一步,调用 mmap() 后,文件的内容通过 DMA 模块复制到内核缓冲区中,该缓冲区之后与用户进程共享,这样一来,就无需再进行内核缓冲区和用户缓冲区的切换,也就是它们之间的复制就不会再发生。

第二步,在调用 write() 后,内核缓冲区中的数据被复制到与 socket 相关联的内核缓冲区中。

第三步,DMA 模块将数据由 socket 的缓冲区复制到协议引擎,这时第 3 次复制发生。

通过调用 mmap() 而不是 read(),我们已经将内核需要执行的复制操作减半。当有大量的数据传输时,有相当好的效果。但是性能改进的同时,也潜藏着一定的代价与陷阱。比如,在对文件进行内存映射后调用 write(),而这时有另外一个进程将映射的文件截断,此时 write() 系统调用会被进程接收到的 SIGBUS 信号而中断,SIGBUS 信号往往意味着尝试进行非法地址访问。对 SIGBUS 信号的默认处理方式是杀死当前进程并生成 core dump 文件 — 这对于网络服务器来说是极不期望的!

有两种方式可以解决该问题:

第一种是为 SIGBUS 信号设置处理程序,并在处理中简单的执行 return 语句。这样,write() 系统调用将返回被信号中断前已写的字节数,同时设置 errno 变量。但是这样的做法并不值得鼓励,因为收到 SIGBUS 信号意味着发生了严重错误!

第二种是采用文件租约的方式。文件租约是指,通过对文件描述符执行租借,你可以和内核对某个文件达成租约,从内核可以获得读/写租约。当另外的一个进程试图将你正在传输的文件截断时,内核会向你发出实时信号 RT_SIGNAL_LEASE 。该信号通知你的进程,内核即将终止你在该文件上曾经获得的租约。这样,当 write() 访问非法地址时,并即被随后到来的 SIGBUS 杀诉之前,write() 系统调用会被 RT_SIGNAL_LEASE 信号中断。write() 的返回值就是被中断之前已写的字节数,全局变量 errno 设置为成功。下面是一段示例租约的代码:

if(fcntl(fd, F_SETSIG, RT_SIGNAL_LEASE) == -1) {      perror(“kernel lease set signal”);      return -1;  }  /* l_type can be F_RDLCK F_WRLCK */  if(fcntl(fd, F_SETLEASE, l_type)){      perror(“kernel lease set type”);      return -1;  }

在文件进行映射之前 (通过 mmap() 系统调用),应该先获得租约,并在 write() 结束之后结束租约。这通过在 fcntl() 函数中指定租约类型为 F_UNLCK 来实现。

sendfile

sendfile() 的目的是简化通过网络在两个本地之间的数据传输过程。sendfile() 系统调用的引入,不仅减少了数据的复制,还减少了上下文切换的次数。 使用方法如下:

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

它的原理如下图所示:

sendfile相关「建议收藏」

在上图中,

第一步,sendfile() 导致文件内容通过 DMA 模块复制到某个内核缓冲区,之后被复制到与 soket 相关联的的缓冲区中。

第二步,DMA 模块将 socket 缓冲区中的数据复制到协议亲引擎,这时进行第 3 次复制。

在调用 sendfile() 期间,如果有另外一个进程将文件截断,且进程没有为 SIGBUS 注册任何的信号处理函数时,sendfile() 调用仍会返回进程被信号中断前已发送的字节数,并将全局变量 errno 设置为成功。然而,类似的,如果在调用 sendfile() 前,从内核里获得了文件租约,那么 sendfile() 在返回前也会收到 RT_SIGNAL_LEASE。

到此为止,我们已经能够避免内核的多次复制了,然而我们还存在一个多余的副本,这里就是 socket buffer。那么,这个副本是否可以消除? 确实可以,但这需要特定硬件的支持。为了消除内核产生的冗余数据,需要网络适配器支持聚合操作特性。该特性被支持意味着要发送的数据无须存放在地址连续的内存空间中,相反可以存放在各个内存位置。在 2.4 版本的内核中,socket 缓冲区描述符发生了变动,以适合聚合(将零散分布的数据聚合起来发送)操作的要求 –这就是 Linux 中所谓的“零拷贝”。这种方式不但减少了多个上下文的切换,而且消除了数据冗余。从用户层程序的角度来看,没有发生任何改动,所有的代码还是和以前一样。这里所描述原理如下图所示:

sendfile相关「建议收藏」

在上图中,

第一步,sendfile() 系统调用导致 DMA 模块将文件内容复制到内核缓冲区中。

第二步,数据并未复制到 socket 缓冲区中,取而代之的是,只有记录数据位置和长度的描述符被加入到 socket 缓冲区中。DMA 模块将内核缓冲区中的数据传递给协议引擎,从而消除了遗留的最后一次复制。

由于数据实际上仍然要通过从硬盘拷贝到内存,再由内存再发送到设备,有人可能会觉得这不是真正的“零拷贝”。然而,从操作系统的角度来看,这就是“零拷贝”,因为内核空间不存在冗余数据(既不会存在将数据存放内核一块数据缓冲区中,又将数据存放在 socket 缓冲区中)。应用“零拷贝”特性,除了避免了重复复制外,还可以获得其他性能的优势,例如更少的上下文切换,更少的 CPU cache污染和没有必要的 CPU 必要校验。

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/140915.html原文链接:https://javaforall.net

(0)
全栈程序员-站长的头像全栈程序员-站长


相关推荐

  • 紫光同创国产FPGA学习之Power Calculator

    紫光同创国产FPGA学习之Power Calculator紫光同创里面的,芯片功耗计算。没看过,没用过。有兴趣瞄一下。我又不用再电池行业,对电源没啥要求。没用经验之谈,拷贝参考书。一、总体介绍  (一)PangoPowerCalculator总体介绍PangoPowerCalculator是用来计算用户设计产生功耗的软件,简称PPC,是集成在PDS中的一个组件。用户在完成design设计,经过综合、map、布局布线后,可以使用P…

    2022年8月30日
    0
  • 运维面试官喜欢问的问题是_运维是什么意思

    运维面试官喜欢问的问题是_运维是什么意思你最大的缺点是什么?行为问题(behaviouralquestion),这类问题目的是看性格是否合适公司文化。主要考察:①你是否真心想做这个工作;②你性格与企业文化是否相符。所有答案都应该围绕这两点组织,即每个经历都应回归到你通过这个经历学到什么该职位所需关键技巧,这些经历为何让你想做这个工作,和该经历体现出你什么样的个人风格。你最大的缺点是什么:①避免避重就轻,谈一个算不得缺点的缺点,这…

    2022年9月3日
    4
  • 422模块接线_422接口定义

    422模块接线_422接口定义#硬件芯片422引脚连接方式前言参考连接对应表举例-正好拿手头的一款芯片链接举例查手册前言最近调试串口转422程序,便买了一个“多功能5和1的转换器“,如下图所示,但是调试的时候,没怎么看线序,改线后忘记如何连接,只能重新查找资料,为例方便更多人也方便自己记录参考。参考连接个人觉的写言简意赅的,美中不足的地方就是看着费劲,所以优化了一下。参考如下连接:https://blog.csdn.net/fzktongyong/article/details/86163206对应表举例-正好拿手头的

    2022年8月30日
    0
  • Python详细知识体系总结(2021版)「建议收藏」

    Python知识体系总结(持续更新ing)本文专注整理一些有关Python学习的知识体系,不定期更新。整理的Python知识体系主要包括基础知识,Python热门的应用方向,推荐书籍,FAQ以及一些常见面试题目,包含了作为一个Python全栈工程师以及数据分析工程师在开发工作和学习中需要用到或者可能用到的绝大部分知识。希望大家可以根据自己感兴趣的方面多多学习。另:写的博客如有错误或者疏忽的地方,还望各位大佬指点,在此表示感激不尽。文章目录Python知识体系总结(持续更新ing)面试题汇总Pyth

    2022年4月7日
    85
  • VirtualBox安装Mac OS 10.11——虚拟机安装黑苹果

    VirtualBox安装MacOS10.11,安装日期:2016/5/14用虚拟机装黑苹果本人也装了不下3次了,这次为了做这个教程还特意把virtualbox和旧版的MacOS删了,重新再装一遍。所以保证能运行,不像网上其他教程都是导出复制,还不要脸的贴个原创。VirtualBox是官网下的最新版:5.0.20forWindowshostsx

    2022年4月4日
    762
  • 数据库连接池到底应该设多大?这下终于get到了!

    点击上方“全栈程序员社区”,星标公众号 重磅干货,第一时间送达 作者:kelgon www.jianshu.com/p/a8f653fc0c54 本文内容95%译自这篇文章: ht…

    2021年6月28日
    126

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号