在该章节中讨论的文件描述符的概念。其中包括:打开文件,关闭文件,从文件中读取数据和向文件中写数据。
实现一个简版的cp(1)命令:
#include
#include
#include "tlpi_hdr.h"
#include "error_functions.h"//用来输入错误信息
#ifndef BUF_SIZE
#define BUF_SIZE 1024
#endif
int main(int argc, char const *argv[])
{
int inputFd,outputFd,openFlags;//用来存储I/O调用的返回值
mode_t filePerms; //整型,用来表示文件权限及类型
ssize_t numRead; //有符号整型,当字节数(为负时)表示错误
char buf[BUF_SIZE];
//strcmp():比较两个字符串设这两个字符串为str1,str2,若str1==str2,则返回零
if(argc!=3||strcmp(argv[1],"--help")==0)
usageErr("%s old-file new-file\n",argv[0]);
inputFd=open(argv[1],O_RDONLY);
if(inputFd==-1)
errExit("opening file %s",argv[1]);
openFlags=O_CREAT | O_WRONLY | O_TRUNC; //open()函数的参数,后面会介绍
filePerms=S_IRUSR | S_IWUSR |S_IWGRP | S_IROTH |S_IWOTH;
//文件类型参数,后面介绍
outputFd=open(argv[2],openFlags,filePerms);
if(outputFd==-1)
errExit("opening file %s",argv[2]);
while((numRead=read(inputFd,buf,BUF_SIZE))>0)
if(write(outputFd,buf,numRead)!=numRead)
fatal("couldn't write whole buffer");
if(numRead==-1)
errExit("read");
if(close(inputFd)==-1)
errExit("close input");
if(close(outputFd)==-1)
errExit("close output");
exit(EXIT_SUCCESS);
return 0;
}
#include
#include
int open(const char* pathname,int flags,.../*mode_t mode*/);
//返回:打开成功的话返回文件描述符,打开失败的话返回-1
要打开的文件由参数pathname来标识,如果pathname是一个符号链接,那么该调用会对其进行解引用。如果调用成功,open()返回文件描述符,用于在后续函数调用中指代该文件,如果发生错误,则返回-1,并将errno置为相应的错误标志。参数flags为位掩码,用来指定文件的访问模式。 当调用open()创建新文件时,位掩码参数mode指定了文件的访问权限。
如果open()并未指定O_CREAT标志,则可以省略mode参数。 O_RDONLY —->以只读方式打开文件 O_WRONLY —->以只写方式打开文件 O_RDWR —->以读写方式打开文件。
open函数使用的例子:
#include
#include
#include "tlpi_hdr.h"
#include "error_functions.h"//用来输入错误信息
int main(int argc, char const *argv[])
{
int fd;
fd=open("startup",O_RDONLY);
if(fd==-1)
errExit("open");
fd=open("myfile",O_RDWR|O_CREAT|O_TRUNC,S_IRUSR|S_IWUSR);
if(fd==-1)
errExit("open");
fd=open("w.log",O_WRONLY|O_CREAT|O_TRUNC|O_APPEND,S_IRUSR|S_IWUSR);
if(fd==-1)
errExit("open");
return 0;
}
open()调用所返回的文件描述符数值:SUSv3规定,如果调用open()成功,必须保证其返回值为进程为用文件描述符中数值最小者。
所以可以利用这项特性来以特定的文件描述符打开某一个文件。
例如:下例代码会确保使用标准输入(文件描述符0)打开一个文件
#include
#include
#include "tlpi_hdr.h"
#include "error_functions.h"//用来输入错误信息
int main(int argc, char const *argv[])
{
int fd;
if(close(STDIN_FILENO)==-1)
//close()关闭STDIN_FILENO 使得系统内最小文件描述符为0
errExit("close");
fd=open("startup",O_RDONLY);
if(fd==-1)
errExit("open");
printf("%d\n",fd);
return 0;
}
由于文件描述符0未用,所以open()调用势必使用此描述符打开文件。
flags常量的详解:
O_APPEND : 标志如其名,总是在文件尾部追加数据。
O_ASYNC : 当对open()调用所返回的文件描述符可以实施I/O操作时,系统会产生一个信号通知进程。这一个特性,也被称之为信号驱动I/O,仅对特定类型的文件有效,诸如终端、FIFO及socket。在linux中,调用open()时指定O_ASYNC标志没有任何实质效果,要启用信号驱动I/O特性,必须调用fcntl()的F_SETFL操作来设置O_ASYNC标志。
O_CLOEXEC : 为新(创建)的文件描述符启用close-on-flag标志(FD_CLOEXEC)。使用O_CLOEXEC标志(打开文件),可以免去程序执行fcntl()的F_GETFD和F_SETFD操作来设置close-on-exec标志的额外工作。———————-看不懂
O_CREATE : 如果文件不存在,将创建一个新的空文件。即使文件以只读方式打开,此标志依然有效。如果在open()调用中指定O_CREATE标志,那么还要提供mode参数,否则,会将新文件的权限设置为栈中的某个随机值。
O_TRUNC : 如果文件已经存在且为普通文件,那么将清空文件内容,将其长度置为0。在linux下使用此标志,无论以读、写方式打开文件,都可清空文件内容(在这两种情况下,都必须拥有对文件的读写权限)。
O_DIRECTORY : 如果pathname参数并非目录,将返回错误(错误号errno为ENOTDIR)。这一标志是专为实现opendir()函数而设计的扩展标志。为使O_DIRECTORY标志的常量定义在
#include
int create(const char * pathname,mode_t mode)
create()系统调用根据pathname参数创建并打开一个文件,若文件已存在,则打开文件,并清空文件内容,将其长度清0。create()返回一文件描述符,供后续系统调用使用。create()系统调用等同于如下open()调用:
fd=open(pathname,O_WRONLY|O_CREATE|O_TRUNC,mode); 现在一般都使用open()来代替create()的操作。
读取文件内容:read()
read()系统调用从文件描述符fd所指代的打开文件中读取数据。
#include
ssize_t read(int fd,void *buffer,size_t count);
return number of bytes read,0 on EOF ,or -1 on error
count参数指定最多能读取的字节数,(size_t数据类型属于无符号整数类型)。buffer参数提供用来存放输入数据的内存缓冲区地址。缓冲区至少应有count个字节。linux的系统调用不会分配内存缓冲区用以返回信息给调用者。所以,必须预先分配大小合适的缓冲区并将缓冲区指针传递给系统调用。但有些库函数却会分配内存缓冲区用以返回信息给调用者。
如果read()调用成功,将返回实际读取的字节数。如果遇到文件结束(EOF)则返回0,如果出现错误则返回-1。ssize_t数据类型属于有符号的整数类型,用来存放(读取的)字节数或-1(表示错误)。如果读普通文件时,在读到要求字节数之前已经达到文件结尾,解决方法如:若达到文件尾端之前有30个字节,而要求读50个字节,则第一次read返回数值为30,在下次调用read时返回0(表示文件读取完毕)。
一次read()调用所读取的字节数可以小于请求的字节数。对于普通文件而言,这可能时因为当前的读取位置靠近文件尾部。
当read()应用于其他文件类型时,比如管道、FIFO、socket或者终端,在不同环境下也会出现read()调用读取的字节小于请求字节数的情况。
#include
#include "tlpi_hdr.h"
#include "error_functions.h"
#define MAX_READ 20
int main(int argc, char const *argv[])
{
/* code */
char buffer[MAX_READ+1];
ssize_t numRead=read(STDIN_FILENO,buffer,MAX_READ);
if(numRead==-1)
errExit("read");
buffer[numRead]='\0'; //必须加上这一行,才能在终端内读取字符。
printf("The input data was :%s\n",buffer);
return 0;
}
read()可以从文件中读取任意序列的字节,有时读到的信息可能是文本数据,但有时可能是二进制整数或二进制形式的C语言数据结构。read()不能区分这些数据,所以不能遵从c语言对字符串处理的约定——在字符串尾部追加标识字符串结束的空字符。
所以如果要使用c语言和read()系统调用,必须在输入缓冲区结尾处显示追加一个表示终止的空字符。
数据写入文件:write()
write()系统调用将数据写入一个已经打开的文件中
#include
ssize_t write(int fd,void *buffer,size_t count
write()调用的参数含义与read()调用类似。buffer参数为要写入文件中数据的内存地址,count参数为欲从buffer写入文件的数据字节数,fd参数为一文件描述符,指代数据要写入的文件。如果write()调用成功,将返回实际写入文件的字节数,该返回值可能小于count参数值。这被称为“部分写”。对磁盘文件来说,造成”部分写”的原因可能时因为磁盘已满,或是因为进程资源对文件大小的限制。
对磁盘文件执行I/O操作时,wirte()调用成功并不能保存数据已经写入磁盘。因为为了减少磁盘活动量和加快write()系统调用,内核会缓存磁盘的I/O操作。
关闭文件:close()
close()系统调用关闭一个打开的文件描述符,并将其释放返回调用进程,供进程继续使用。当一进程终止时,将其自动关闭其已打开的所有文件描述符。
#include
int close(int fd);
显式关闭不再需要的文件描述符是个良好的编程习惯,可以使代码在后续修改时更具有可读性,也更可靠。文件描述符属于有限资源,因此文件描述符关闭失败可能会导致一个进程将文件描述符资源消耗殆尽。要像其他所有系统调用一样,应对close()的调用进行错误检查。
if(close(fd)==-1) errExit("close");
上面这个错误提示可以捕获到的错误有:尝试关闭一个未打开的文件描述符、两次关闭同一文件描述符等。
#include
off_t lseek(int fd,off_t offset,int whence) return new file offset if successful ,or -1 on error
offset参数指定了一个以字节为单位的数值,whence参数则表明应参照哪个基点来解释offset参数。
whence参数应为下:
SEEK_SET : 将文件偏移量设置为从文件头部起始点开始的offset个字节。———-头部
SEEK_CUR: 相对于当前文件偏移量,将文件偏移量调整offset个字节。 ——–当前位置
SEEK_END:将文件偏移量设置为起始于文件尾部的offset个字节。也就是说,offset参数应该从文件最后一个字节之后的下一个字节算起。
如果whence参数值为SEEK_CUR或SEEK_END,offset参数可以为正数也可以为负数;如果whence参数值为SEEK_SET,offset参数值必须为非负数。
lseek()调用成功会返回新的文件偏移量。下面的调用只是获取文件偏移量的当前位置,并没有修改它。
curr=lseek(fd,0,SEEK_CUR)