第10章 系统级I/O
在某些重要情况下,使用高级I/O函数不太可能。例如,标准I/O库没有提供读取文件元数据的方式,如文件大小或文件创建时间。另外,I/O库还存在一些问题,使用它来进行网络编程非常冒险。
10.1 Unix I/O
所有的I/O设备,如网络、磁盘和终端,都被模型化为文件,而所有的输入和输出都被当做相应文件的读和写。
- 打开文件
- 改变当前的文件位置:文件位置是从文件开头起始的字节偏移量.
- 读写文件
- 关闭文件:无论进程以何种原因终止,内核都会关闭所有打开的文件并释放它们的存储器资源.
10.2 打开和关闭文件
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int open(char *filename, int flags, mode_t mode); 返回:若成功则为新的文件描述符,否则为-1
flags参数:
- O_RDONLY: 只读
- O_WRONLY: 只写
- O_RDWR: 可读可写
- O_CREAT: 若文件不存在,则创建
- O_TRUNC: 若文件已存在,就截断它.
- O_APPEND: 追加
mode参数: 指定新文件的访问权限位
关闭文件:
#include <unistd.h>
int close(int fd); 返回:若成功则为0,若出错为-1
10.3 读和写文件
#include <unistd.h>
ssize_t read(int fd, void *buf, size_t n);
ssize_t write(int fd, const void *buf, size_t n);
read函数: 从描述符为fd的当前文件位置拷贝最多n个字节到存储器位置buf,返回值-1表示错误,返回值0表示EOF, 否则,返回实际传送的字节数量.
write函数: 从存储器位置buf拷贝至多n个字节到描述符fd的当前文件位置.
某些情况,read和write传送的字节比应用程序要求的要少,出现原因如下:
- 读时遇到EOF
- 从终端读文本行: 若打开文件是与终端相关联的,如键盘和显示器,那么每个read函数将一次传送一个文本行,返回值为文本行的大小.
- 读和写网络套接字(socket): 如果打开的文件对应于网络套接字,那么内部缓冲约束和较长的网络延迟会引起read和write返回不足值. 对unix管道调用read和write时,也有可能引起不足值.
10.4 RIO包健壮地读写(略)
10.5 读取文件元数据
可调用stat和fstat函数,检索到关于文件的信息.
#include <unistd.h>
#include <sys/stat.h>
int stat(const char *filename, struct stat *buf);
int fstat(int fd, struct stat *buf);
返回:若成功则为0,出错则为-1
stat函数以文件名作为输入,而fstat以文件描述符作为输入.
st_size成员包含文件的字节数大小,st_mode成员编码了文件访问许可位和文件类型。
Unix提供宏指令来根据st_mode成员来判断文件类型.
宏指令 | 描述 |
---|---|
S_ISREG() | 是否为普通文件 |
S_ISDIR() | 是否为目录文件 |
S_ISSOCK() | 是否为网络套接字 |
10.6 共享文件
内核用相关的数据结构来表示打开的文件:
- 描述符表: 每个进程都有它独立述符表,每个打开的描述符表项指向文件表中的一个表项。
- 文件表: 打开文件的集合由一张文件表来表示,所有的进程共享这张表。每个文件表的表项包括当前的文件位置,引用计数(即当前指向该表项的描述符表项数),以及一个指向v-node表中对应表项的指针。关闭一个文件描述符会减少相应的文件表表项中的引用计数。内核不会删除这个文件表表项,直到它的引用计数为0.
- v-node表: 所有进程共享v-node表。每个表项包含相应打开文件的属性信息,包括stat结构的的大部分信息,诸如st_mode和st_size成员.
图10-11是典型的打开文件的内核数据结构,描述符1和4通过不同的打开文件表表项来引用两个不同的文件。没有共享文件。
多个描述符也可以通过不同的文件表表项来引用同一个文件,如图10-12所示。例如,如果以同一个filename调用open函数两次,就会发生这种情况。关键思想是每个描述符都有它自己的文件位置,所以对不同描述符的读操作可以从文件的不同位置获取数据.
对于fork的情况: 假设fork前,父进程有图10-11所示的打开文件。图10-13展示了调用fork之后的情况。子进程有一个父进程描述符的副本。父子进程共享相同的打开文件表几个,因此共享相同的文件位置。因而,在内核删除相应文件表表项之前,父子进程必须都关闭了它们的描述符.
10.7 I/O重定向
Unix Shell提供了I/0重定向操作符,允许用户将磁盘文件和标准输入输出联系起来。如,
unix> ls > foo.txt
使得外壳加载和执行ls程序,将标准输出重定向到磁盘文件foo.txt。
Unix I/O提供dup2函数支持重定向
#include <unistd.h>
int dup2(int oldfd, int newfd);
返回: 若成功则为非负的描述符,出差则为-1
dup2拷贝描述符表表项oldfd到描述符表表项newfd,覆盖描述符表表项newfd以前的内容。若newfd已经打开,dup2会在拷贝oldfd之前关闭newfd.
调用dup2(4,1)之前,初始状态如图10-11所示,调用之后如图10-14所示.