本章开始讨论UNIX系统,先说明可用的文件I/O函数---打开文件、读写文件等
UNIX系统中的大多数文件I/O只需用到5个函数:open、read、write、lseek以及close
open函数 返回一个最小的未用描述符
#include <fcntl.h> int open(const char *pathname, int flags); int open(const char *pathname, int flags, mode_t mode);
其中path参数是打开或创建文件的名字,flags参数由下列一个或多个常量进行“或”运算得到:
O_RDONLY 只读打开
O_WRONLY 只写打开
O_RDWR 读、写打开
O_EXEC 只执行打开
O_SEARCH 只搜索打开(应用于目录)
这5个常量中必须指定一个,下列常量则是可选的:
O_APPEND 每次写时都追加到文件的尾端
O_CLOEXEC 把FD_CLOEXEC常量设置为文件描述符标志
O_CREAT 若此文件不存在则创建它。使用此选项时,open函数必须同时说明第三个参数mode
O_DIRECTORY 如果path引用的不是目录,则出错
O_EXEL 如果同时指定了O_CREAT,而文件已经存在,则出错
O_NOCTTY 如果path引用的是终端设备,则不讲该设备分配作为此进程的控制终端
O_NOFOLLOW 如果path应用的是一个符号链接,则出错
O_NONBLOCK 如果path引用的是一个FIFO、一个块特殊文件或一个字符特殊文件,则设置此文件本次打开操作跟后续的I/O操作为非阻塞方式
O_SYNC 使每次write等待物理I/O操作完成
O_TRUNC 如果此文件存在,而且为只写或读写成功打开,则将其长度截断为0
O_TTY_INIT 如果打开一个还未打开的终端设备,设置非标准termios参数值
creat函数
#include <fcntl.h> int creat(const char *path,mode_t mode);
该函数相当于open(path,O_WRONLY|O_CREATE|O_TRUNC,mode);
在第四章我们会详细说明文件访问权限,并说明如何指定mode
close函数
#include <unistd.h> int close(int fd);
close函数用于关闭一个打开文件
lseek函数
#include <unistd.h> off_t lseek(int fd,off_t offset,int whence);
对参数offset的解释与参数whence的值有关。
若whence是SEEK_SET,则将该文件的偏移量设置为距距文件开始处offset个字节
若whence是SEEK_CUR,则将该文件的偏移量设置为其当前值加offset,offset可为正或负
若whence是SEEK_END,则将该文件的偏移量设置为文件长度加offset,offset可正可负
若lseek成功执行,则返回新的文件偏移量,为此可以用下列方式确定打开文件的当前偏移量
off_t currpos; currpos=lseek(fd,0,SEEK_CUR);
如果文件描述符指向的是一个管道、FIFO、或者网络套接字,则lseek返回-1,并将errno设置为ESPIPE。
read函数
#include <unistd.h> ssize_t read(int fd,void *buf,size_t nbytes);
如read成功,则返回读到的字节数。如已到达文件的尾端,则返回0。
读操作从文件的当前偏移量处开始,在成功返回之前,该偏移量将增加实际读到的字节数。
write函数
#include <unistd.h> ssize_t write(int fd,const void *buf,size_t nbytes);
其返回值通常与参数nbytes的值相同,否则表示出错。write出错的一个常见的原因是磁盘已写满,或者超过了一个给定进程的文件长度限制。
对于普通文件,写操作从文件的当前偏移量处开始,如果打开该文件时,指定了O_APPEND选项,则在每次写操作之前,将文件偏移量设置在文件的当前结尾处。
在一次成功写之后,该文件偏移量增加实际写得字节数。
文件共享
进程为打开文件维护3张表
如果两个独立进程各自打开了同一个文件,则有下图所示的关系
函数dup和dup2
#include <unistd.h> int dup(int fd); int dup2(int fd,int fd2);
两个函数都可用来复制一个现有的文件描述符
dup返回的新文件描述符一定是当前可用文件描述符中的最小数值。
dup2可以用fd2参数指定新描述符的值。如果fd2已经打开,则先将其关闭。若fd等于fd2,则返回fd2,而不关闭它。
这些函数返回的新文件描述符与参数fd共享一个文件表项,如下图所示:
digit1>&digit2表示要将描述符digit1重定向至描述符digit2的同一文件
理解./a.out > outfile 2>&1与./a.out 2>&1 >outfile的区别
fcnt函数
#include <fcntl.h> int fcntl(int fd,int cmd,.../* int arg */);
fcntl函数可以改变已经打开文件的属性,它有以下5种功能
1 复制一个已有的描述符(cmd=F_DUPFD或F_DUPFD_CLOEXEC)
2 获取/设置文件描述符标志(cmd=F_GETFD或F_SETFD)
3 获取/设置文件状态标志(cmd=F_GETFL或F_SETFL)
4 获取/设置异步I/O所有权(cmd=F_GETOWN或F_SETOWN)
5 获取/设置记录锁(cmd=F_GETLK、F_SETLK或F_SETLKW)
我们先说明cmd中的前面8种
F_DUPFD 复制文件描述符fd,返回新的文件描述符。它是尚未打开的各描述符中大于或等于第三个参数值中各值的最小值
F_DUPFD_CLOEXEC 复制文件描述符,设置与新描述符关联的FD_CLOEXEC文件描述符标志的值,返回新的文件描述符
F_GETFD 对应于fd的文件描述符标志做为函数值返回,当前只定义了一个文件描述符标志FD_CLOEXEC
F_SETFD 对于fd设置文件描述符标志。
F_GETFL 对应于fd的文件状态标志作为函数值返回,下图列出fcntl的文件状态标志
其中,3种访问方式标志(O_RDONLY,O_WRONLY,O_RDWR)并不各占一位,因此首先必须用屏蔽字O_ACCMODE取得访问方式位,然后将结果与这3个值得每一个比较。
F_SETFL 将文件状态标志设置为第三个参数的值。可以更改的几个标志是O_APPEND、O_NONBLOCK、O_SYNC、O_DSYNC、O_RSYNC、O_FSYNC和O_ANSYNC
F_GETTOWN 获取当前接收SIGIO和SIGURG信号的进程ID或进程组ID。
F_SETOWN 设置接收SIGIO和SIGURG信号的进程ID或进程组ID
下面程序将打印文件状态标志说明
#include "apue.h" #include <fcntl.h> int main(int argc, char *argv[]) { int val; if (argc != 2) err_quit("usage: a.out <descriptor#>"); if ((val = fcntl(atoi(argv[1]), F_GETFL, 0)) < 0) err_sys("fcntl error for fd %d", atoi(argv[1])); switch (val & O_ACCMODE) { case O_RDONLY: printf("read only"); break; case O_WRONLY: printf("write only"); break; case O_RDWR: printf("read write"); break; default: err_dump("unknown access mode"); } if (val & O_APPEND) printf(", append"); if (val & O_NONBLOCK) printf(", nonblocking"); if (val & O_SYNC) printf(", synchronous writes"); #if !defined(_POSIX_C_SOURCE) && defined(O_FSYNC) && (O_FSYNC != O_SYNC) if (val & O_FSYNC) printf(", synchronous writes"); #endif putchar(' '); exit(0); }
下面函数用于设置文件状态标志
#include "apue.h" #include <fcntl.h> void set_fl(int fd, int flags) /* flags are file status flags to turn on */ { int val; if ((val = fcntl(fd, F_GETFL, 0)) < 0) err_sys("fcntl F_GETFL error"); val |= flags; /* turn on flags */ if (fcntl(fd, F_SETFL, val) < 0) err_sys("fcntl F_SETFL error"); }