文件描述符
对于内核而言,所有打开的文件都通过文件描述符引用。文件描述符是一个非负整数,每一个进程默认有三个已经打开的文件描述符与之关联,分别是标准输入0,标准输出1和标准错误2。POSIX规范在头文件unistd.h中对此定义了三个符号常量STDIN_FILENO,STDOUT_FILENO和STDERR_FILENO。
任何一个进程可以同时打开的文件数是有限制的,这个限制通常是由limits.h头文件中的常量OPEN_MAX定义的,它的值随系统的不同而不同,但POSIX规范要求它至少为16。
open函数
#include <fcntl.h> int open(const char *pathname, int oflag, ... /* mode_t mode */);
open函数用来打开或创建一个文件,若成功返回文件描述符,否则返回-1。
pathname是要打开或创建文件的名字。
oflag参数是下列一个或多个常量执行按位或运算的结果杀
- O_RDONLY 只读打开
- O_WRONLY 只写打开
- O_RDWR 读写打开
上面三个常量必须指定一个并且只能指定一个,下面一些常量则是可选的:
- O_APPEND 将写入追加到文件的尾端
- O_CREAT 若文件不存在,则创建它。使用该选项时,需要第三个参数mode,用来指定新文件的访问权限位
- O_EXCL 如果同时指定了O_CREAT,而文件已经存在,则会出错
- O_TRUNC 如果此文件存在,而且为只写或读写模式成功打开,则将其长度截短为0
- O_NOCTTY 如果pathname指的是终端设备,则不将该设备分配作为此进程的控制终端
- O_NONBLOCK 如果pathname指的是一个FIFO文件、块设备文件或字符设备文件,则此选项将文件的本次打开操作和后续的I/O操作设置为非阻塞模式
下面三个标志也是可选的。它们是Single UNIX Specification(以及POSIX.1)中同步输入和输出选项的一部分:
- O_DSYNC 使每次write等待物理I/O操作完成,但是如果写操作并不影响读取刚写入的数据,则不等待文件属性被更新
- O_RSYNC 使每一个以文件描述符作为参数的read操作等待,直到任何对文件同一部分进行的未完成写操作都完成
- O_SYNC 使每次write都等待物理I/O操作完成,包括由write操作引起的文件属性更新所需的I/O
mode参数仅在oflag参数指定了O_CREAT选项时才被使用,用来指定新文件的访问权限位,这些标志在头文件sys/stat.h中定义:
- S_IRUSR 读权限,文件属主
- S_IWUSR 写权限,文件属主
- S_IXUSR 执行权限,文件属主
- S_IRGRP 读权限,文件所属组
- S_IWGRP 写权限,文件所属组
- S_IXGRP 执行权限,文件所属组
- S_IROTH 读权限,其他用户
- S_IWOTH 写权限,其他用户
- S_IROTH 执行权限,其他用户
有几个因素会对文件的访问权限产生影响。首先,指定的访问权限只有在创建文件时才会使用。其次,用户掩码会影响到被创建的文件的访问权限,也就是说,open给出的mode值与用户掩码的反值做AND运算后的结果,才是文件的真实访问权限。由open返回的文件描述符一定是最小的未被使用的描述符数值。
creat函数
#include <fcntl.h> int creat(const char *pathname, mode_t mode);
也可以调用creat函数创建一个新文件,该函数等效于: open(pathname, O_WRONLY | O_CREAT | O_TRUNC, mode); 函数执行成功返回打开的文件描述符,否则返回-1。
creat的一个不足之处是它以只写方式打开所创建的文件。
close函数
#include <unistd.h> int close(int filedes);
close函数关闭一个打开的文件,执行成功返回0,否则返回-1。
关闭一个文件时还会释放该进程加在该文件上的所有锁。当一个进程终止时,内核会自动关闭它所有打开的文件。
lseek函数
#include <unistd.h> off_t lseek(int filedes, off_t offset, int whence);
每个打开的文件都有一个与其相关联的当前文件偏移量。它通常是一个非负整数,用以度量从文劲啊开始处计算的字节数。通常,读、写操作都从当前文件偏移量处开始,并使偏移量增加所读写的字节数。按系统的默认情况,当打开一个文件时,除非指定O_APPEND选项,否则该偏移量被设置为0。
可以调用lseek显示地为一个打开的文件设置其偏移量。对参数offset的解释与参数whence的值有关:
- 若whence是SEEK_SET,则该文件的偏移量设置为距文件开始处offset个字节。
- 若whence是SEEK_CUR,则该文件的偏移量设置为当前值加offset,offset可为正或负。
- 若whence是SEEK_END,则该文件的偏移量设置为文件长度加offset,offset可为正或负。
若lseek执行成功,则返回新的文件偏移量,否则返回-1。
通常,文件的当前偏移量应当是一个非负整数,但是,某些设备也可能允许负的偏移量。但对于普通文件,则其偏移量必须是非负值。因为偏移量可能是负值,所以在比较lseek的返回值时应当谨慎,不要测试它是否小于0,而要测试它是否等于-1。
lseek仅将当前的文件偏移量记录在内核中,它并不引起任何I/O操作。然后,该偏移量用于下一个读或写操作。
文件偏移量可以大于文件的当前长度,在这种情况下,对该文件的下一次写将加长该文件,并在文件中构成一个空洞,所有位于文件中但并没有写过的字节都被读为0。
read函数
#include <unistd.h> ssize_t read(int filedes, void *buf, size_t nbytes);
调用read函数从打开的文件中读取数据,如果执行成功,则返回读到的字节数,若已到文件末尾则返回0,出错则返回-1。
write函数
#include <unistd.h> ssize_t write(int filedes, const void *buf, size_t nbytes);
调用write函数向打开的文件写入数据。执行成功返回写入的字节数,出错则返回-1。
其返回值通常与参数nbytes的值相同,否则表示出错。
下面是一个示例程序,用来演示底层I/O函数的用法。
1 #include <unistd.h> 2 #include <sys/stat.h> 3 #include <fcntl.h> 4 #include <stdlib.h> 5 6 int main(void) 7 { 8 char block[4096]; 9 int in, out; 10 int nread; 11 12 in = open("file.in", O_RDONLY); 13 out = open("file.out", O_WRONLY|O_CREAT, S_IRUSR|S_IWUSR|S_IRGRP); 14 while ((nread = read(in, block, 4096)) > 0) { 15 write(out, block, nread); 16 } 17 18 exit(0); 19 }