进程每打开一个文件的时候,会得到该文件的文件描述符,然后续的读写操做都把文件描述符做为参数。在用户空间或者内核空间,都是经过文件描述符来惟一地索引一个打开的文件。文件描述符使用int类型表示,文件描述符的范围从0开始,到上限值-1,默认状况下,上限值为1024,也就是说,进程默认状况下最多能够打开1024个文件。负数是不合法的文件描述符,当函数调用出错时,返回的文件描述符为-1。html
每一个进程都至少包含三个文件描述符:socket
文件描述符 | 表示 | 宏 |
---|---|---|
0 | 标准输入(stdin) | STDIN_FILENO |
1 | 标准输出(stdout) | STDOUT_FINENO |
2 | 标准错误(stderr) | STDERR_FILENO |
遵循Linux一切皆文件的概念,文件描述符除了访问普通文件外,几乎可以访问任何可以读写的东西。包括设备文件、管道、先进先出缓冲区、套接字等。函数
对文件进行读写以前,必须先打开文件。Linux提供了系统调用open()。open()有两个函数原型:指针
#include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> int open(const char *pathname, int flags); int open(const char *pathname, int flags, mode_t mode);
两个函数都可用来打开文件,第二个函数比第一个多了参数mode,mode指定文件的权限---当建立新文件的时候才须要。若是文件打开成功,则返回文件描述符,指向pathname所指定的文件。flags参数用于指定打开的方式,它支持三种访问模式:code
访问模式 | 描述 |
---|---|
O_RDONLY | 打开一个供读取的文件 |
O_WRONLY | 打开一个供写入的文件 |
O_RDWR | 打开一个可供读写的文件 |
flags参数还能够与下面的值进行按位或运算,修改打开文件的行为:htm
打开方式 | 描述 |
---|---|
O_APPEND | 写入的全部数据将被追加到文件的末尾 |
O_CREAT | 打开文件,若是文件不存在则创建文件 |
O_EXCL | 若是已经设置了O_CREAT且文件存在,则强制open()失败,只能与O_CREAT搭配使用 |
O_TRUNC | 若是文件存在,且是普通文件,而且有写权限,将文件内容清空 |
O_NONBLOCK | 文件以非阻塞模式打开,请见read系统调用 |
举个例子,下面的句子表示:以写的方式打开文件,若是文件不存在,则建立新的文件,而且文件的内容为空:blog
int fd ; fd = open("file.txt",O_WRONLY|O_CREAT|O_TRUNC,0644);
这里的0644指定了新建立的文件访问权限,参数mode的取值以下:索引
打开方式 | 描述 |
---|---|
S_IRUSR | 文件全部者有读权限 |
S_IWUSR | 文件全部者有写权限 |
S_IXUSR | 文件全部者有执行权限 |
S_IRWXU | 文件全部者有读、写、执行权限 |
S_IRGRP | 组用户有读权限 |
S_IWGRP | 组用户有写权限 |
S_IXGRP | 组用户有执行权限 |
S_IRWXG | 组用户有读、写、执行权限 |
S_IROTH | 全部人有读权限 |
S_IWOTH | 全部人有写权限 |
S_IXOTH | 全部人有执行权限 |
S_IRWXO | 全部人有读、写、执行权限 |
实际上最终写入磁盘的文件访问权限是由mode参数和用户的文件建立掩码(umask)执行按位与操做获得的。举个例子:进程
int main() { int fd; fd = open("TEST.txt",O_WRONLY|O_CREAT|O_TRUNC,S_IRWXU|S_IRWXG|S_IRWXO );//以只读方式打开文件 //等价于 fd = open("TEST.txt",O_WRONLY|O_CREAT|O_TRUNC,0777 );//以只读方式打开文件 if(fd == -1) perror("open file error!"); return 0; }
按理来讲,建立出来的文件的访问权限应该是-rwxrwxrwx,而查看后发现其实不是:内存
ls -l TEST.txt -rwxrwxr-x 1 huanzhewu huanzhewu 0 5月 7 21:29 TEST.txt 【权限为0775】
查看当前的掩码:
$ umask 0002
能够发现 0775 = 0777 ^ (~0002) ,因此0775才是最后的文件访问权限。umask是进程级属性,经过调用umask()函数来修改,支持用户修改新建立的文件和目录的权限。
总结起来能够获得这样一条公式:
newmode = mode ^ (~ umask)
总结一下:至此,咱们了解了文件打开所提供的两个系统调用函数open(),了解了打开文件的方式、新建文件的访问权限设置。若是文件打开成功,那么将返回一个文件描述符,这是一个非零整数(由于0,1,2是进行已经拥有的文件描述符),不然函数将返回-1
顾名思义,creat函数用来建立一个文件,不过咱们可能产生疑问:前面的open函数使用一些选项后,不是也能够建立新文件吗?没错,creat函数彻底等价与下面的open语句:
int fd ; fd = open("file.txt",O_WRONLY|O_CREAT|O_TRUNC,0644); fd = creat("file.txt,0644"); /*两个语句的做用彻底等价*/
因为选项O_WRONLY|O_CREAT|O_TRUNC组合常用,于是系统调用专门使用creat函数来提供这个功能。creat函数的原型以下:
#include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> int creat(const char *pathname, mode_t mode);
其中参数的描述与open的参数一致,这里再也不赘述。
文件打开后,就可以读文件了。read()是最基础、最多见的读取文件的机制。read的函数原型为:
#include <unistd.h> ssize_t read(int fd, void *buf, size_t count);
fd 为文件描述符。每次调用read函数时,会从fd指向的文件的当前偏移(或称文件位置)开始读取count字节到buf所指的的内存中。随着文件的读取,fd的文件位置指针会向前移动。关于read的读取,会出现不少须要思考的问题:
咱们一一来看:
因为read有这么多须要考虑的问题,若是但愿每次都能读入count个字节,下面是一段示例代码:
//保证读取200个字节到ptr中 ssize_t ret ; int len = 200; while(len!= 0 && ( ret = read(fd, ptr, len )) != 0) { if(ret == -1) { if( errno == EINTR) continue; perror("read"); break; } len -= ret ; ptr += ret ; }
再来看看问题1,当文件没有数据能够读时(一开始就没有),read调用会被阻塞,直到文件有数据能够读,这是一种阻塞I/O。若是文件以O_NONBLOCK模式打开,则文件为非阻塞模式,当文件没有数据能够读时,read系统调用返回-1,并把errno设置为EAGAIN。
ssize_t ret ; int len = 200; while(len!= 0 && ( ret = read(fd, ptr, len )) != 0) { if(ret == -1) { if( errno == EINTR) { printf("读取被中断\n"); continue; } if(errno== EAGAIN) { printf("文件没有可读\n"); //从新提交读取 continue; } break; } len -= ret ; ptr += ret ; }
除了errno被设置为EINTR与EAGAIN,其余状况下都是出现严重的文件读取错误,从新执行读操做不会成功。
write的函数原型为:
#include <unistd.h> ssize_t write(int fd, const void *buf, size_t count); 【将buf中count个字节的内容写入fd指定的文件中】
write的返回值比较简单:
对于普通文件,write基本能保证每次执行调用可以写入所有的内容。对于其余文件如socket,须要进行循环写,保证全部的字节都写入了文件中:
ssize_t ret ; int len = 200; while(len!= 0 && ( ret = write(fd, ptr, len )) != 0) { if(ret == -1) { if( errno == EINTR) continue; perror("write"); break; } len -= ret ; ptr += ret ; }
一样的,当以非阻塞的模式打开文件时(O_NONBLOCK),系统调用write()会返回-1,并把errno设置为EAGAIN。
系统调用write()时,数据从用户空间的缓冲区中拷贝到了内核空间的缓冲区,但并无当即把数据写入磁盘中,这称为延迟写。延迟写的问题在于,若是在数据真正写入磁盘以前系统崩溃了,则数据可能丢失。内核设置了一个时间,在该时间内将内核空间缓冲区上的数据写入磁盘,该时间称为"最大存放时效"。Linux系统也支持强制文件当即写入磁盘上,这在后面介绍。
程序完成文件的读写后,调用close函数关闭文件描述符与文件之间的链接,使得文件描述符能够被重用。close的函数原型为:
#incldue<unistd.h> int close (int fd);
文件关闭成功返回0,出错返回-1,并设置相应的errno。文件成功关闭并不觉得着该文件的数据已经被写入磁盘,同步选项在后续介绍。
总结:本文简单介绍了文件的打开、建立、读写、关闭操做,介绍了一些经常使用的open参数选项,creat与open的等价性,循环读、循环写的必要性,也关注了各个系统调用的返回值含义,了解如何设置非阻塞读写,并简单提到了延迟写的问题,在后续的文件中将介绍同步I/O的内容。