实例.docx
《实例.docx》由会员分享,可在线阅读,更多相关《实例.docx(24页珍藏版)》请在冰豆网上搜索。
实例
实例——fopen和getc函数的实现
下面以标准库函数fopen和getc的一种实现方法为例来说明如何将这些系统调用结合
起来使用。
我们回忆一下,标准库中的文件不是通过文件描述符描述的,而是使用文件指针描述的。
文件指针是一个指向包含文件各种信息的结构的指针,该结构包含下列内容:
一个指向缓冲
区的指针,通过它可以一次读入文件的一大块内容;一个记录缓冲区中剩余的字符数的计数
器;一个指向缓冲区中下一个字符的指针;文件描述符;描述读/写模式的标志;描述错误
状态的标志等。
描述文件的数据结构包含在头文件中,任何需要使用标准输入/输出库中函
数的程序都必须在源文件中包含这个头文件(通过#include指令包含头文件)。
此文件也被
库中的其它函数包含。
在下面这段典型的代码段中,只供标准库中其它函数所使
用的名字以下划线开始,因此一般不会与用户程序中的名字冲突。
所有的标准库函数都遵循
该约定。
#defineNULL0
#defineEOF(-1)
#defineBUFSIZ1024
#defineOPEN_MAX20/*max#filesopenatonce*/
typedefstruct_iobuf{
intcnt;/*charactersleft*/
char*ptr;/*nextcharacterposition*/
char*base;/*locationofbuffer*/
intflag;/*modeoffileaccess*/
intfd;/*filedescriptor*/
}FILE;
externFILE_iob[OPEN_MAX];
#definestdin(&_iob[0])
#definestdout(&_iob[1])
#definestderr(&_iob[2])
enum_flags{
_READ=01,/*fileopenforreading*/
_WRITE=02,/*fileopenforwriting*/
_UNBUF=04,/*fileisunbuffered*/
_EOF=010,/*EOFhasoccurredonthisfile*/
_ERR=020/*erroroccurredonthisfile*/
};
int_fillbuf(FILE*);
int_flushbuf(int,FILE*);
#definefeof(p)((p)->flag&_EOF)!
=0)
#defineferror(p)((p)->flag&_ERR)!
=0)
#definefileno(p)((p)->fd)
#definegetc(p)(--(p)->cnt>=0\
?
(unsignedchar)*(p)->ptr++:
_fillbuf(p))
#defineputc(x,p)(--(p)->cnt>=0\
?
*(p)->ptr++=(x):
_flushbuf((x),p))
#definegetchar()getc(stdin)
#defineputcher(x)putc((x),stdout)
宏getc一般先将计数器减1,将指针移到下一个位置,然后返回字符。
(前面讲过,一
个长的#define语句可用反斜杠分成几行。
)但是,如果计数值变为负值,getc就调用函数
_fillbuf填充缓冲区,重新初始化结构的内容,并返回一个字符。
返回的字符为unsigned
类型。
以确保所有的字符为正值。
尽管在这里我们并不想讨论一些细节,但程序中还是给出了putc函数的定义,以表明它
的操作与getc函数非常类似,当缓冲区满时,它将调用函数_flushbuf。
此外,我们还在
其中包含了访问错误输出、文件结束状态和文件描述符的宏。
下面我们来着手编写函数fopen。
fopen函数的主要功能是打开文件,定位到合适的位
置,设置标志位以指示相应的状态。
它不分配任何缓冲区空间,缓冲区的分配是在第一次读
文件时由函数_fillbuf完成的。
#include
#include"syscalls.h"
#definePERMS0666/*RWforowner,group,others*/
FILE*fopen(char*name,char*mode)
{
intfd;
FILE*fp;
if(*mode!
='r'&&*mode!
='w'&&*mode!
='a')
returnNULL;
for(fp=_iob;fp<_iob+OPEN_MAX;fp++)
if((fp->flag&(_READ|_WRITE))==0)
break;/*foundfreeslot*/
if(fp>=_iob+OPEN_MAX)/*nofreeslots*/
returnNULL;
if(*mode=='w')
fd=creat(name,PERMS);
elseif(*mode=='a'){
if((fd=open(name,O_WRONLY,0))==-1)
fd=creat(name,PERMS);
lseek(fd,0L,2);
}else
fd=open(name,O_RDONLY,0);
if(fd==-1)/*couldn'taccessname*/
returnNULL;
fp->fd=fd;
fp->cnt=0;
fp->base=NULL;
fp->flag=(*mode=='r')?
_READ:
_WRITE;
returnfp;
}
该版本的fopen函数没有涉及标准C的所有访问模式,但是,加入这些模式并不需要增加多
少代码。
特别是,该版本的fopen不能识别表示二进制访问方式的b标志,这是因为,在
UNIX系统中这种方式是没有意义的。
同时,它也不能识别允许同时进行读和写的+标志。
对于某一特定的文件,第一次调用getc函数时计数值为0,这样就必须调用一次函数
_fillbuf。
如果_fillbuf发现文件不是以读方式打开的,它将立即返回EOF;否则,它将
试图分配一个缓冲区(如果读操作是以缓冲方式进行的话)。
建立缓冲区后,_fillbuf调用read填充此缓冲区,设置计数值和指针,并返回缓冲区
中的第一个字符。
随后进行的_fillbuf调用会发现缓冲区已经分配。
#include"syscalls.h"
/*_fillbuf:
allocateandfillinputbuffer*/
int_fillbuf(FILE*fp)
{
intbufsize;
if((fp->flag&(_READ|_EOF_ERR))!
=_READ)
returnEOF;
bufsize=(fp->flag&_UNBUF)?
1:
BUFSIZ;
if(fp->base==NULL)/*nobufferyet*/
if((fp->base=(char*)malloc(bufsize))==NULL)
returnEOF;/*can'tgetbuffer*/
fp->ptr=fp->base;
fp->cnt=read(fp->fd,fp->ptr,bufsize);
if(--fp->cnt<0){
if(fp->cnt==-1)
fp->flag|=_EOF;
else
fp->flag|=_ERR;
fp->cnt=0;
returnEOF;
}
return(unsignedchar)*fp->ptr++;
}
最后一件事情便是如何执行这些函数。
我们必须定义和初始化数组_iob中的stdin、
stdout和stderr值:
FILE_iob[OPEN_MAX]={/*stdin,stdout,stderr*/
{0,(char*)0,(char*)0,_READ,0},
{0,(char*)0,(char*)0,_WRITE,1},
{0,(char*)0,(char*)0,_WRITE,|_UNBUF,2}
};
该结构中flag部分的初值表明,将对stdin执行读操作、对stdout执行写操作、对stderr
执行缓冲方式的写操作。
练习8-2用字段代替显式的按位操作,重写fopen和_fillbuf函数。
比较相应代
码的长度和执行速度。
练习8-3设计并编写函数_flushbuf、fflush和fclose。
练习8-4标准库函数
intfseek(FILE*fp,longoffset,intorigin)
类似于函数lseek,所不同的是,该函数中的fp是一个文件指针而不是文件描述符,且返回
值是一个int类型的状态而非位置值。
编写函数fseek,并确保该函数与库中其它函数使用
的缓冲能够协同工作。
8.6.实例——目录列表
我们常常还需要对文件系统执行另一种操作,以获得文件的有关信息,而不是读取文件
的具体内容。
目录列表程序便是其中的一个例子,比如__________UNIX命令ls,它打印一个目录中的文
件名以及其它一些可选信息,如文件长度、访问权限等等。
MS-DOS操作系统中的dir命令也
有类似的功能。
由于UNIX中的目录就是一种文件,因此,ls只需要读此文件就可获得所有的文件名。
但
是,如果需要获取文件的其它信息,比如长度等,就需要使用系统调用。
在其它一些系统中,
甚至获取文件名也需要使用系统调用,例如在MS-DOS系统中即如此。
无论实现方式是否同
具体的系统有关,我们需要提供一种与系统无关的访问文件信息的途径。
以下将通过程序fsize说明这一点。
fsize程序是ls命令的一个特殊形式,它打印命令
行参数表中指定的所有文件的长度。
如果其中一个文件是目录,则fsize程序将对此目录递
归调用自身。
如果命令行中没有任何参数,则fsize程序处理当前目录。
我们首先回顾UNIX文件系统的结构。
在UNIX系统中,目录就是文件,它包含了一个
文件名列表和一些指示文件位置的信息。
“位置”是一个指向其它表(即i结点表)的索引。
文件的i结点是存放除文件名以外的所有文件信息的地方。
目录项通常仅包含两个条目:
文件
名和i结点编号。
遗憾的是,在不同版本的系统中,目录的格式和确切的内容是不一样的。
因此,为了分
离出不可移植的部分,我们把任务分成两部分。
外层定义了一个称为Dirent的结构和3个
函数opendir、readdir和closedir,它们提供与系统无关的对目录项中的名字和i结点
编号的访问。
我们将利用此接口编写fsize程序,然后说明如何在与Version7和SystemV
UNIX系统的目录结构相同的系统上实现这些函数。
其它情况留作练习。
结构Dirent包含i结点编号和文件名。
文件名的最大长度由NAMZ_MAX设定,NAME_MAX
的值由系统决定。
opendir返回一个指向称为DIR的结构的指针,该结构与结构FILE类似,
它将被readdir和closedir使用。
所有这些信息存放在头文件dirent.h中。
#defineNAME_MAX14/*longestfilenamecomponent;*/
/*system-dependent*/
typedefstruct{/*portabledirectoryentry*/
longino;/*inodenumber*/
charname[NAME_MAX+1];/*name+'\0'terminator*/
}Dirent;
typedefstruct{/*minimalDIR:
nobuffering,etc.*/
intfd;/*filedescriptorforthedirectory*/
Direntd;/*thedirectoryentry*/
}DIR;
DIR*opendir(char*dirname);
Dirent*readdir(DIR*dfd);
voidclosedir(DIR*dfd);
系统调用stat以文件名作为参数,返回文件的i结点中的所有信息;若出错,则返回-1。
如下所示:
char*name;
structstatstbuf;
intstat(char*,structstat*);
stat(name,&stbuf);
它用文件name的i结点信息填充结构stbuf。
头文件中包含了描述stat
的返回值的结构。
该结构的一个典型形式如下所示:
structstat/*inodeinformationreturnedbystat*/
{
dev_tst_dev;/*deviceofinode*/
ino_tst_ino;/*inodenumber*/
shortst_mode;/*modebits*/
shortst_nlink;/*numberoflinkstofile*/
shortst_uid;/*ownersuserid*/
shortst_gid;/*ownersgroupid*/
dev_tst_rdev;/*forspecialfiles*/
off_tst_size;/*filesizeincharacters*/
time_tst_atime;/*timelastaccessed*/
time_tst_mtime;/*timelastmodified*/
time_tst_ctime;/*timeoriginallycreated*/
};
该结构中大部分的值已在注释中进行了解释。
dev_t和ino_t等类型在头文件
中定义,程序中必须包含此文件。
st_mode项包含了描述文件的一系列标志,这些标志在中定义。
我们只
需要处理文件类型的有关部分:
#defineS_IFMT0160000/*typeoffile:
*/
#defineS_IFDIR0040000/*directory*/
#defineS_IFCHR0020000/*characterspecial*/
#defineS_IFBLK0060000/*blockspecial*/
#defineS_IFREG0010000/*regular*/
/*...*/
下面我们来着手编写程序fsize。
如果由stat调用获得的模式说明某文件不是一个目
录,就很容易获得该文件的长度,并直接输出。
但是,如果文件是一个目录,则必须逐个处
理目录中的文件。
由于该目录可能包含子目录,因此该过程是递归的。
主程序main处理命令行参数,并将每个参数传递给函数fsize。
#include
#include
#include"syscalls.h"
#include/*flagsforreadandwrite*/
#include/*typedefs*/
#include/*structurereturnedbystat*/
#include"dirent.h"
voidfsize(char*)
/*printfilename*/
main(intargc,char**argv)
{
if(argc==1)/*default:
currentdirectory*/
fsize(".");
else
while(--argc>0)
fsize(*++argv);
return0;
}
函数fsize打印文件的长度。
但是,如果此文件是一个目录,则fsize首先调用dirwalk
函数处理它所包含的所有文件。
注意如何使用文件中的标志名S_IFMT和
S_IFDIR来判定文件是不是一个目录。
括号是必须的,因为&运算符的优先级低于==运算符
的优先级。
intstat(char*,structstat*);
voiddirwalk(char*,void(*fcn)(char*));
/*fsize:
printthenameoffile"name"*/
voidfsize(char*name)
{
structstatstbuf;
if(stat(name,&stbuf)==-1){
fprintf(stderr,"fsize:
can'taccess%s\n",name);
return;
}
if((stbuf.st_mode&S_IFMT)==S_IFDIR)
dirwalk(name,fsize);
printf("%8ld%s\n",stbuf.st_size,name);
}
函数dirwalk是一个通用的函数,它对目录中的每个文件都调用函数fcn一次。
它首
先打开目录,循环遍历其中的每个文件,并对每个文件调用该函数,然后关闭目录返回。
因
为fsize函数对每个目录都要调用dirwalk函数,所以这两个函数是相互递归调用的。
#defineMAX_PATH1024
/*dirwalk:
applyfcntoallfilesindir*/
voiddirwalk(char*dir,void(*fcn)(char*))
{
charname[MAX_PATH];
Dirent*dp;
DIR*dfd;
if((dfd=opendir(dir))==NULL){
fprintf(stderr,"dirwalk:
can'topen%s\n",dir);
return;
}
while((dp=readdir(dfd))!
=NULL){
if(strcmp(dp->name,".")==0
||strcmp(dp->name,".."))
continue;/*skipselfandparent*/
if(strlen(dir)+strlen(dp->name)+2>sizeof(name))
fprintf(stderr,"dirwalk:
name%s%stoolong\n",
dir,dp->name);
else{
sprintf(name,"%s/%s",dir,dp->name);
(*fcn)(name);
}
}
closedir(dfd);
}
每次调用readdir都将返回一个指针,它指向下一个文件的信息。
如果目录中已没有待处理
的文件,该函数将返回NULL。
每个目录都包含自身“.”和父目录“..”的项目,在处理时
必须跳过它们,否则将会导致无限循环。
到现在这一步为止,代码与目录的格式无关。
下一步要做的事情就是在某个具体的系统
上提供一个opendir、readdir和closedir的最简单版本。
以下的函数适用于Version7
和SystemVUNIX系统,它们使用了头文件(sys/dir.h>中的目录信息,如下所示:
#ifndefDIRSIZ
#defineDIRSIZ14
#endif
structdirect{/*directoryentry*/
ino_td_ino;/*inodenumber*/
chard_name[DIRSIZ];/*longnamedoesnothave'\0'*/
};
某些版本的系统支持更长的文件名和更复杂的目录结构。
类型ino_t是使用typedef定义的类型,它用于描述i结点表的索引。
在我们通常使用
的系统中,此类型为unsignedshort,但是这种信息不应在程序中使用。
因为不同的系统
中该类型可能不同,所以使用typedef定义要好一些。
所有的“系统”类型可以在文件
opendir函数首先打开目录,验证此文件是一个目录(调用系统调用fstat,它与stat
类似,但它以文件描述符作为参数),然后分配一个目录结构,并保存信息:
intfstat(intfd,structstat*);
/*opendir:
openadirectoryforreaddircalls*/
DIR*opendir(char*dirname)
{
intfd;
structstatstbuf;
DIR*dp;
if((fd=open(dirname,O_RDONLY,0))==-1
||fstat(fd,&stbuf)==-1
||(stbuf.st_mode&S_IFMT)!
=S_IFDIR
||(dp=(DIR*)malloc(sizeof(DIR)))==NULL)
returnNULL;
dp->fd=fd;
returndp;
}
closedir函数用于关闭目录文件并释放内存空间:
/*closedir:
closedirectoryopenedbyopendir*/
voidclosedir(DIR*dp)
{
if(dp){
close(dp->fd);
free(dp);
}
}
最后,函数readdir使用read系统调用读取每个目录项。
如果某个目录位置当前没有
使用(因为删除了一个文件),则它的i结点编号为0,并跳过该位置。
否则,将i结点编号
和目录名放在一个static类型的结构中,并给用户返回一个指向此结构的指针。
每次调用
readdir函数将覆盖前一次调用获得的信息。
#include/*localdirectorystructure*/
/*readdir:
readdirectoryentriesinsequence*/
Dirent*readdir(DIR*dp)
{
structdirectdirbu