找回密码
 用户注册

QQ登录

只需一步,快速开始

查看: 8557|回复: 11

Linux "零拷贝" sendfile函数中文说明及实际操作分析

[复制链接]
发表于 2009-6-18 15:20:45 | 显示全部楼层 |阅读模式
Sendfile函数说明
#include <sys/sendfile.h>
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时发生未知错误.
ENOMEM 读(read)in_fd时内存不足.

------------------------------------------------------------------------------

由于想再提升原有系统中文件传输模块的速度,并减少系统资源占用,进行了一次sendfile()的性能测试,但失败了.不过还是将它用在了模块中.记录一下这次失改的微调测试.

运行平台: 客户机与服务器均为P4计算机,IDE硬盘; Fedora5发行版; 百兆局域网;

接收端程序如下:
FILE *fp = fopen(FILENAME,"wb");
   while((len = recv(sockfd, buff, sizeof(buff), 0)) > 0)
   {
      fwrite(buffer, 1, len, fp);
   }
   fclose(fp);



A. 发送端传统方式代码段如下:
fd = open(FILENAME, O_RDONLY);
   while((len =read(fd, buff, sizeof(buff))) >0)
   {
       send(sockfd, buff, len ,0);
   }
   close(fd);  

由于我磁盘分区时指定的块大小为4096,为了最优读取磁盘数据,buff大小设为4096字节.但在测试中发现设为1024或8192不会对传输速度带来影响.
文件大小:9M; 耗时:0.71 - 0.76秒;
文件大小:32M; 耗时:2.64 - 2.68秒;
文件大小:64M; 耗时:5.36 - 5.43秒;
B. 使用sendfile()传输代码段.
off_t offset = 0;
   stat(FILENAME, &filestat);
   fd = open(FILENAME, O_RDONLY);
   sendfile(sockfd, fd, &offset, filestat.st_size) );
   close(fd);  

文件大小:9M; 耗时:0.71 - 1.08秒;
文件大小:32M; 耗时:2.66 - 2.74秒;
文件大小:64M; 耗时:5.43 - 6.64秒;
似乎还略有下降.根据sendfile的man手册,我在使用该函数前调用了
int no = 1;
printf("%d\n", setsockopt(sockfd, IPPROTO_TCP, TCP_CORK, (char*)&no, sizeof(int)) );

文件大小:9M; 耗时:0.72 - 0.75秒;
文件大小:32M; 耗时:2.66 - 2.68秒;
文件大小:64M; 耗时:5.38 - 5.60秒;
这样似乎达到了传统方式的速度?!不管哪种环境下,我用ethereal抓包显示每一个tcp包的playload部分最大也通常是1448字节.
看来我的测试没有体现出"应用层数据的两次拷贝带来很大的消耗"这一说法.如果按照存在就是有理的说法的话,那我想sendfile()在两种情况下才体现优势,但我却没有环境测试:
1. 大并发量的文件服务器或HTTP服务器;
2. 内存资源紧张的嵌入式系统;
另外,网络上大量的关于tcp选项中的TCP_CORK描述已经过时.在man手册中早已提到该参数可以与TCP_NODELAY结合使用了.只是,只要设置了TCP_NODELAY选项后,不管是否设置TCP_CORK,包都会立即发出.
----------------------------------------------------------------------
补充:
TCP_NODELAY和TCP_CORK基本上控制了包的“Nagle化”,Nagle化在这里的含义是采用Nagle算法把较小的包组装为更大的帧。 John Nagle是Nagle算法的发明人,后者就是用他的名字来命名的,他在1984年首次用这种方法来尝试解决福特汽车公司的网络拥塞问题(欲了解详情请参看IETF RFC 896)。他解决的问题就是所谓的silly window syndrome ,中文称“愚蠢窗口症候群”,具体含义是,因为普遍终端应用程序每产生一次击键操作就会发送一个包,而典型情况下一个包会拥有一个字节的数据载荷以及40个字节长的包头,于是产生4000%的过载,很轻易地就能令网络发生拥塞,。 Nagle化后来成了一种标准并且立即在因特网上得以实现。它现在已经成为缺省配置了,但在我们看来,有些场合下把这一选项关掉也是合乎需要的。
现在让我们假设某个应用程序发出了一个请求,希望发送小块数据。我们可以选择立即发送数据或者等待产生更多的数据然后再一次发送两种策略。如果我们马上发送数据,那么交互性的以及客户/服务器型的应用程序将极大地受益。例如,当我们正在发送一个较短的请求并且等候较大的响应时,相关过载与传输的数据总量相比就会比较低,而且,如果请求立即发出那么响应时间也会快一些。以上操作可以通过设置套接字的TCP_NODELAY选项来完成,这样就禁用了 Nagle算法。
另外一种情况则需要我们等到数据量达到最大时才通过网络一次发送全部数据,这种数据传输方式有益于大量数据的通信性能,典型的应用就是文件服务器。应用Nagle算法在这种情况下就会产生问题。但是,如果你正在发送大量数据,你可以设置TCP_CORK选项禁用Nagle化,其方式正好同 TCP_NODELAY相反(TCP_CORK 和 TCP_NODELAY 是互相排斥的)。下面就让我们仔细分析下其工作原理。
假设应用程序使用sendfile()函数来转移大量数据。应用协议通常要求发送某些信息来预先解释数据,这些信息其实就是报头内容。典型情况下报头很小,而且套接字上设置了TCP_NODELAY。有报头的包将被立即传输,在某些情况下(取决于内部的包计数器),因为这个包成功地被对方收到后需要请求对方确认。这样,大量数据的传输就会被推迟而且产生了不必要的网络流量交换。
但是,如果我们在套接字上设置了TCP_CORK(可以比喻为在管道上插入“塞子”)选项,具有报头的包就会填补大量的数据,所有的数据都根据大小自动地通过包传输出去。当数据传输完成时,最好取消TCP_CORK 选项设置给连接“拔去塞子”以便任一部分的帧都能发送出去。这同“塞住”网络连接同等重要。
总而言之,如果你肯定能一起发送多个数据集合(例如HTTP响应的头和正文),那么我们建议你设置TCP_CORK选项,这样在这些数据之间不存在延迟。能极大地有益于WWW、FTP以及文件服务器的性能,同时也简化了你的工作。
http://blog.chinaunix.net/u/1214/showart_432956.html
~·恩 以前从没有注意到这个函数~~
Google Architecture
http://highscalability.com/google-architecture
 楼主| 发表于 2009-6-18 15:21:20 | 显示全部楼层
在Linux操作系统lighttp使用sendfile方式处理静态资源
2009-05-15 18:56
在Linux操作系统上面,只需要在lighttpd.conf配置文件如下配置,lighttpd就会使用sendfile方式处理静态资源的下载,效率非常高:

引用
server.network-backend = "linux-sendfile"

发表于 2009-6-20 13:14:48 | 显示全部楼层
不知道作者什么环境测的,我自己简单测了下,ubuntu8.10,发送者和接受者都在一台主机,用loopback接口。
文件大小650M,多个备份,每次测试都用不同的备份以免cache的影响。
缓冲区大小4k,跑了10多次,基本上是sendfile方式要快5-10%,从来没比普通方式慢过。

按原理0 copy不仅节省了copy时间,还减少了系统调用,减轻了cpu负担,和内存占用。在高并发(cpu负担重,内存紧张)的环境下效率应该更高些。
发表于 2009-6-20 13:16:13 | 显示全部楼层
ACE有ACE_Asynch_Transmit_File,文档中说推荐应用在Web Server, Image Server等。不知道底层是不是用到了os的零拷贝api
 楼主| 发表于 2009-6-20 14:05:25 | 显示全部楼层
我只是看到你转的文章,感兴趣,顺便搜索了一下,并未自己验证。
发表于 2009-6-20 14:46:02 | 显示全部楼层
我知道是你转别人的,就是看到文中说他实验失败了,觉得奇怪就自己试了下。
测试环境比较随便,但也能证明确实零拷贝是有效的。跟developerWorks的结果(提高65%)有差距,可能是有些优化选项没有设置。
发表于 2009-6-30 01:39:45 | 显示全部楼层

sendfile理论上要快一些。

一般而言,内核loopback 和p2p在实现上是不同的。
“playload部分最大也通常是1448字节.”这是MTU决定的。
因为各个平台的实现不同,个人觉得还是send可靠些。
发表于 2009-6-30 14:42:04 | 显示全部楼层
steven99ca  也是版主么?以前怎么很少见你啊,要常来哦。:lol
 楼主| 发表于 2009-6-30 14:47:57 | 显示全部楼层
steven99ca在论坛最早的时候就在,是我辈仰慕的高人也。。。
发表于 2009-6-30 15:01:18 | 显示全部楼层
啊。。。欢迎steven99ca前辈常来指教:handshake :handshake
您需要登录后才可以回帖 登录 | 用户注册

本版积分规则

Archiver|手机版|小黑屋|ACE Developer ( 京ICP备06055248号 )

GMT+8, 2024-12-22 16:34 , Processed in 0.077128 second(s), 6 queries , Redis On.

Powered by Discuz! X3.5

© 2001-2023 Discuz! Team.

快速回复 返回顶部 返回列表