sendfile实现零拷贝

sendfile函数在两个文件描述符之间直接传递数据,完全在内核操作,从而避免了内核缓冲区和用户缓冲区的数据拷贝,效率很高,被称为零拷贝。

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

out_fd 是待写入内容的文件描述符,它必须是一个socket

in_fd 是待读出内容的文件描述符,它必须是一个支持类似mmap函数的文件描述符,即它必须指向真实的文件,不能是socket和管道

可以看出这个函数几乎就是专门为在网络上传输文件设置的,有socket,有文件描述符

offset参数指定从读入文件流的哪个位置开始读,如果为空,则使用读入文件流默认的起始位置

count参数指定在out_fd 和in_fd之间传输的字节数

成功返回传输的字节数,失败返回-1并设置errno

与read和write系统调用函数的比较:

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

如果我们使用read和write函数,需要进行如下几个步骤:

步骤一:系统调用read导致了从用户空间到内核空间的上下文切换。DMA模块(直接内存访问,是一种不经过CPU而直接从内存存取数据的数据交换模式。在DMA模式下,CPU只须向DMA控制器下达指令,让DMA控制器来处理数据的传送,数据传送完毕再把信息反馈给CPU)从磁盘中读取文件内容,并将其存储在内核空间的缓冲区内,完成了第1次复制。

步骤二:数据从内核空间缓冲区复制到用户空间缓冲区,之后系统调用read返回,这导致了从内核空间向用户空间的上下文切换。此时,需要的数据已存放在指定的用户空间缓冲区内(参数tmp_buf),程序可以继续下面的操作。

步骤三:系统调用write导致从用户空间到内核空间的上下文切换。数据从用户空间缓冲区被再次复制到内核空间缓冲区,完成了第3次复制。不过,这次数据存放在内核空间中与使用的socket相关的特定缓冲区中,而不是步骤一中的缓冲区。

步骤四:系统调用返回,导致了第4次上下文切换。第4次复制在DMA模块将数据从内核空间缓冲区传递至协议引擎的时候发生,这与我们的代码的执行是独立且异步发生的。

而使用sendfile函数,需要进行如下几个步骤:

步骤一:sendfile系统调用导致文件内容通过DMA模块被复制到某个内核缓冲区,之后再被复制到与socket相关联的缓冲区内。

步骤二:当DMA模块将位于socket相关联缓冲区中的数据传递给协议引擎时,执行第3次复制。

对比我们可以得出,相比使用sendfile函数,Read & Write方式带来的性能损耗主要有两点:
1. 不必要的内存拷贝。即多了一次从内核缓冲区到用户缓冲区的数据拷贝。
2. 系统调用带来的额外的用户态/内核态上下文切换。我们知道,使用系统调用会发生从内核态到用户态之间的相互转换。使用read&write方式,多使用了一次系统调用,就多了一次上下文切换,降低了性能。

  sendfile函数可能不属于真正意义的零拷贝,但是它减少切换次数和拷贝次数,总体来说是一个非常高效的数据拷贝函数,尤其在传输一个非常大的文件时,sendfile函数的高性能体现的更为明显。所以,一般网络传输文件时,应该使用sendfile函数,提高效率。

 

参考:https://blog.youkuaiyun.com/eydwyz/article/details/76637947

 

 

 

### sendfile 实现零拷贝的机制 为了提高文件传输效率并减少CPU开销,Linux提供了一种称为“零拷贝”的技术。通过`sendfile`系统调用,在网络编程或文件传输过程中可以避免将数据从用户空间复制到内核空间。 当应用程序使用`sendfile`发送文件时,操作系统会直接把磁盘上的文件内容传递给网卡控制器而不需要经过额外的数据缓冲区[^1]。这种方式减少了内存带宽消耗以及上下文切换次数,从而提高了性能。 具体来说,传统的读取-写入操作涉及四次上下文切换和两次完整的内存副本;而在采用`sendfile`的情况下,则只需要一次DMA(Direct Memory Access)传输即可完成整个过程: 1. 文件描述符指向源文件; 2. 另一文件描述符指向目标套接字; 3. 调用`sendfile`函数指定上述两个参数及要传送的数量。 这种优化使得大量数据能够更高效地在网络间流动而不必担心过多占用主机资源。 #### 使用 `sendfile` 进行文件传输的例子 下面是一个简单的C语言程序片段展示如何利用`sendfile`来实现高效的文件传输功能: ```c #include <sys/sendfile.h> #include <fcntl.h> // 假设已经创建好了一个监听socket fd 'listenfd' int connfd; struct sockaddr_in cliaddr; socklen_t clilen; connfd = accept(listenfd, (struct sockaddr *)&cliaddr, &clilen); // 打开源文件准备传输 int infile_fd = open("example.txt", O_RDONLY); off_t offset = 0; /* 开始位置 */ size_t count = 8192; /* 每次最大传输量 */ while ((ret = sendfile(connfd, infile_fd, &offset, count)) > 0) { // 继续直到全部发送完毕... } close(infile_fd); close(connfd); ``` 此代码段展示了打开一个名为 "example.txt" 的本地文件,并将其内容逐块传送给已连接客户端的过程。这里的关键在于`sendfile()` 函数的应用——它接受目的端口、源文件描述符以及其他必要的偏移量信息作为输入参数来进行实际的数据搬运工作。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值