第三章 文件I/O
在一个操作系统上,最常见的操作是针对文件的,本章描述了在UNIX系统上对文件的操作方法及与文件相关的一些概念。
大多数UNIX文件I/O只需用到5个函数:open,read、write、lseek 以及close(还有create函数功能以及在open上实现,并且可以更为方便地创建文件,故不推荐使用)。
3.1 文件描述符
对于内核而言,所有打开的文件都由文件描述符引用,即内核根据文件描述符来定位打开的文件。文件描述符是一个非负整数,当打开一个文件或新创建一个文件时,内核向进程返回一个文件描述符。按照惯例,UNIX将文件描述符0与标准输入相结合,文件描述符1与标准输出相结合,文件描述符2同标准出错相结合。在POSIX系统中,幻数0,1,2被换成符号常数STDIN_FILENO,STDOUT_FILENO,STDERRO_FILENO。
3.2 操作函数
open
调用open函数可以打开或创建一个文件。由open返回的文件描述符数字一定是最小的文件描述符数字。可以由dup2函数指定在特定的文件描述符上打开文件。
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int open(const char*pathname, int oflag,.../*, mode_t mode * / ) ;
返回:若成功为文件描述符,若出错为- 1
create
也可以由create函数创建一个新文件。但一般不再使用。
Create的一个不足之处是create只以只读方式打开所创建的文件。
Close
用close关闭一个打开的文件。
#include <unistd.h>
int close (int filedes);
关闭一个文件也同时释放该进程加在该文件上的锁有记录锁。当一个进程终止时,它所有打开的文件都由内核自动关闭。很多程序都用这一功能,而不使用close显示关闭打开的文件。
Lseek
每个打开的文件都有一个与之关联的“当前文件偏移量”。它是一个非负整数,用以度量文件从文件打开开始处的计算的偏移量。通常,文件读写都从当前文件偏移量处开始,并使位移量增加所读或写的字节数。按系统默认,当打开一个文件时,除非指定O_APPEND,否则该文件偏移量被指定为0。
可以调用l s e e k显式地定位一个打开文件。
#include <sys/types.h>
#include <unistd.h>
off_t lseek(int filedes, off_t offset, int whence) ;
返回:若成功为新的文件位移,若出错为- 1
Read
用read函数从一个打开的文件中读取数据。
#include <unistd.h>
ssize_t read(int filedes, void *buff, size_t nbytes) ;
返回:读到的字节数,若已到文件尾为0,若出错为- 1
Write
用write函数向一个打开的文件中写数据。
#include <unistd.h>
ssize_t write(int filedes, const void * buff , size_t nbytes) ;
返回:若成功为已写的字节数,若出错为- 1
3.3 文件共享
Unix支持在不同进程间共享打开文件。每个进程都有一个记录项,该记录项记录着该进程的信息,当然要包括该进程所打开的文件,这由记录项中的文件描述符表维护。
图3.1 打开文件的内核数据结构
由上图可知,进程表项中有文件描述符表,每一个文件描述符指向所对应文件的文件表,文件表记录着文件状态标志、当前文件位移量,而文件的物理信息,如文件名、路径、所有者以及文件大小则由V节点指针指向一个v节点表确定;实际上,是i节点信息包含了文件的物理信息。
图3.2描述了两个进程各自打开同一个文件的情况。每个进程都有自己文件表项的理由是,这样可以维护各自的当前文件位移量。
3.4 原子操作
原子操作的意思是指这种操作是不可分的,否则将会带来不可预知的结果。在操作系统上,操作不可分的意思是指调度进程不应该在该操作未完成时进行进程切换。
如有两个进程A和B,都对同一个文件进行添加操作。A先调用lseek函数将当前文件描述符定到1500字节处(当前文件尾),但未等A开始写,内核切换进程使进程B开始执行,B也lseek到1500字节处,并调用write函数写了100字节。这样文件的长度变为1600字节,内核更新v节点信息(注意v节点信息是A和B共享的)。然后,内核又进行进程切换使进程A恢复执行,此时A调用write也写了100字节。这样的后果就是导致文件的实际内容都不是进程A和B所期望的。
为了解决这一问题,办法就是将lseek和write作为一个原子操作,不允许在这两个操作未完成时进行进程切换。Unix提供了这种原子操作,就是在打开文件时指定O_APPEND。