cat命令简单实现
cat工具实现起来比较简单,下面代码采用基本的open、read、printf、close函数,基本可以实现cat命令的功能:
1 #include <stdio.h>
2 #include <unistd.h>
3 #include <fcntl.h>
4
5 #define READSIZE 4096
6
7 int main(int ac, char* av[]){
8 int rfd=-1,rlen=-1,ret=-1; //被文件描述符、读取内容长度、程序返回值
9 char rbuf[READSIZE]; //读取内容缓冲
10 memset(rbuf,0,READSIZE);
11 if(ac == 2){
12 if((rfd= open(av[1],O_RDONLY))== -1)
13 {
14 perror("ccat:");
15 return -1;
16 }
17 while((rlen = read(rfd,rbuf,READSIZE)) > 0){
18 printf("%s",rbuf);
19 memset(rbuf,0,READSIZE);
20 }
21 ret = close(rfd);
22 if(ret == -1)
23 perror("ccat:");
24 }
25 return ret;
26 }
值得注意的是第5行的定义READSIZE定义为4096,这里是参考《Unix\Linux编程实践教程》一书中2.7节对于‘提高文件I/O效率的方法:使用缓冲’的讨论。
文件操作两大效率开销
传输数据量和模式切换。而每次传输数据量的缓冲区大小,直接影响到模式切换的次数。
什么是模式切换?上图解释,再叨叨。
图中最上方的read、write所在白色空间为“用户空间”,大框底部的灰色空间为“内核空间”,访问任何硬件设备包括磁盘都要经过“内核空间”这一层,但是在“用户模式”下只能访问“用户空间”,要访问“内核空间”需要从“用户模式”切换到“管理员模式”,书中还对这个过程做了个形象的比喻:
肯特要到电话亭从“用户模式”切换到“管理员模式”才能变成超人,完成任务在切回记者身份,赚钱糊口(毕竟拯救地球也是义务的,填不了肚子),如果任务多了,就算是超人,找电话亭切来切去,也是很低效的。
缓冲区的设置原理,像是cpu与硬盘之间的内存的作用,也是这么个原理,都知道内存过大是浪费,太小则低效,那么是否存在一个刚刚好的量?我们姑且把这个量称为临界点,Linux内核对于文件I/O交互缓冲区是否做了控制?
本书对于缓冲区大小测试的量是4096,测试的方法是读一个5M大小的文件将内容写到另一个文件里,测试结果见下表,似乎缓冲区大小设到4096以上测试结果都没有变化,难道这个临界值真的存在?
缓冲大小 执行时间/s
1 50.29
4 12.81
16 3.28
32 0.96
... ...
4096 0.18
8192 0.18
16384 0.18
改造cat
书中例子是复制操作,整个过程是内核从磁盘提取数据,传给用户空间,用户将数据写入内核空间,再写入磁盘。数据的来处和去处都是外设磁盘,一次读写经过两次“用户模式”和“管理员模式”的切换。
对于权威,我们不迷信,改造上面的ccat.c代码,测试缓冲区大小设定对于模式切换时效的影响,不同的是数据的来处是磁盘,但去处是显示设备,也经过了两次模式切换。
改造思路是——我们只需要在open和close的地方添加两个时间记录点,最后作差就是整个过程(open、read、print、close)的时间,不过,就书中的结论列表来看,取时间起码要取到毫秒的精度,我们可以使用gettimeofday(...)函数,它可以取到时间的微秒(10^6)级别,代码如下:
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/time.h>
#define READSIZE 16384 //一次性读取文件的大小
void showtime(char*,struct timeval);//打印时间
long filesize(char*); //取文件大小(bytes)
int main(int ac, char* av[]){
int rfd=-1,rlen=-1,ret=-1,i=1; //文件描述符、读取内容长度、程序返回值
int nfblocks;//文件分割块数
char rbuf[READSIZE]; //读取内容缓冲
struct timeval times[2]; //open和close时间
long int fsize; //文件大小
float sub; //时间差
memset(rbuf,0,READSIZE);
if(ac == 2){
if((fsize=filesize(av[1]))==0)
{
printf("file size unknown\n");
return ret;
}
//计算文件分割数(读取次数)
nfblocks = fsize/READSIZE;
if(fsize%READSIZE > 0) nfblocks++;
if((rfd= open(av[1],O_RDONLY))== -1)
{
perror("ccat:");
return -1;
}
gettimeofday(×[0],NULL); //open时间
while((rlen = read(rfd,rbuf,READSIZE)) > 0){
printf("%s",rbuf);
memset(rbuf,0,READSIZE);
i++;
}
ret = close(rfd); //close时间
gettimeofday(×[1],NULL);
if(ret == -1)
perror("ccat:");
//计算时间差
sub = (times[1].tv_sec-times[0].tv_sec)
+(times[1].tv_usec-times[0].tv_usec)/1000000.0;
printf(">>>file size:%ld,read size:%d,blocks num:%d\n",fsize,READSIZE,nfblocks);
printf(">>>open at:%ld(s):%ld(us)\n",(long)times[0].tv_sec,(long)times[0].tv_usec);
printf(">>>close at:%ld(s):%ld(us)\n",(long)times[1].tv_sec,(long)times[1].tv_usec);
printf(">>>sub=%f(s)\n",sub);
}
return ret;
}
long filesize(char* filename)
{
struct stat pstat;
if(stat(filename,&pstat) < 0)
return 0;
return (long)pstat.st_size;
}
文件大小是5292841bytes,下面是运行结果(read size为读取大小,blocks num为数据块数量,sub是整个过程所用的时间,是我们关注的结果):
当READSIZE=256时:
>>>file size:5292841,read size:256,blocks num:20676
>>>open at:1320222334(s):183692(us)
>>>close at:1320222347(s):258245(us)
>>>sub=13.074553(s)
当READSIZE=512时:
>>>file size:5292841,read size:512,blocks num:10338
>>>open at:1320222371(s):143244(us)
>>>close at:1320222383(s):451112(us)
>>>sub=12.307868(s)
当READSIZE=1024时:
>>>file size:5292841,read size:1024,blocks num:5169
>>>open at:1320222411(s):239208(us)
>>>close at:1320222422(s):288572(us)
>>>sub=11.049364(s)
当READSIZE=2048时:
>>>file size:5292841,read size:2048,blocks num:2585
>>>open at:1320222448(s):259733(us)
>>>close at:1320222459(s):147523(us)
>>>sub=10.887790(s)
当READSIZE=4096时:
>>>file size:5292841,read size:4096,blocks num:1293
>>>open at:1320222490(s):639213(us)
>>>close at:1320222501(s):419869(us)
>>>sub=10.780656(s)
当READSIZE=8192时:
>>>file size:5292841,read size:8192,blocks num:647
>>>open at:1320222535(s):491225(us)
>>>close at:1320222545(s):707764(us)
>>>sub=10.216539(s)
当READSIZE=16384时:
>>>file size:5292841,read size:16384,blocks num:324
>>>open at:1320222638(s):119740(us)
>>>close at:1320222648(s):66890(us)
>>>sub=9.947150(s)
结果分析
运行结果计算效率呈递增趋势,4096似乎也不是不可再优化的瓶颈,看这个趋势如果继续不吝惜内存,继续增大缓冲区大小,仍可提高存取的时间效率。这里要说明的是这个运行结果是一开机时的运行结果,于是我继续做实验,打开一些软件,cpu被占用到40%左右,再运行,结果值sub几乎是上面的两倍,如果CPU占用率有波动,那么测试结果也直接受其影响,很难得到一个稳定的结果,很明显的是Linux内核未对缓冲区大小做什么控制。
那么,书中的测试结果就比较奇怪了,缓冲区大小4096后存取效率不变化(保持在0.18s),如此稳定的测试结果,CPU使用率应该比较平稳,如果这样,也应该与我的测试结果一样呈递增趋势才对。
综上,临界量是不存在的,也就是说在CPU和内存足够富裕的情况下,每次内容交换量越大,那么文件I/O时效越高,而瓶颈值是存在的,缓冲区越大,文件读写时间效率越高,但也意味着对内存的需求也就越大。