文件I/O
一、文件描述符
对于内核而言,所有打开的文件都通过文件描述符引用。文件描述符为一个非负整数。当打开一个现有文件或创建一个新文件时,内核向进程返回一个文件描述符。当读或写一个文件时,使用open或create返回的文件描述符标识该文件,将其作为参数传送给read或write。
Unix系统shell使用文件描述符0与进程的标准输入相关联,文件描述符1与文件的标准输出相关联,文件描述符2与文件的标准出错输出相关联。在POSIX应用程序中,幻数0,1,2要分别替换成STDIN_FILENO, STDOUT_FILENO, STDERR_FILENO。这些常量定义在头文件<unistd.h>中。
二、open函数
调用open函数可打开或创建一个文件。
#include <fcntl.h>
Int open(const char *pathname, int oflag, /*mode_t mode(FILE_MODE)*/)
返回值:成功则返回文件描述符,出错返回-1.
参数介绍:
1. Pathname指要打开或创建文件的名字(路径名)。
2. oflag参数用来说明函数的多个选项,用下列一个或多个常量进程或运算构成oflag参数:
O_RDONLY---------->只读打开文件;
O_WRONLY---------->只写打开文件;
O_RDWR---------->读写打开文件。(以上3个选项常量中有且只能选择一种)
以下常量则可选择:
O_APPEND---------->每次写时追加到文件的尾端。
O_CREAT---------->若此文件不存在则创建它;使用该选项时需要第三个参数mode,用其指定该新文件的访问权限位((S_IRUSR|S_IWUSR|S_IXUSR)|(S_IRGRP| S_IWGRP|S_IXGRP)|(S_IROTH|S_IWOTH| S_IXOTH))。
O_EXCL---------->测试一个文件是否存在,如果不存在则创建它;其不能与选项O_CREAT同时使用。
O_TRUNC---------->若文件存在,且为只写或读写成功打开,则将其长度截短为0。
O_NOCTTY---------->如果pathname指的是终端设备,则不将该设备分配作为进程的控制终端。
O_NONBLOCK---------->如果pathname指的是一个FIFO、一个快特殊文件按或一个字符特殊文件,则此选项为文件的本次打开操作和后续的I/O操作设置为非阻塞模式。
以下3个选项则是Single UNIX Specification(以及POSIX.1)中同步输入和输出的选项的一部分(很少用):
O_DSYNC---------->使每次write等待物理I/O操作完成,但如果写操作不影响读取刚写入的数据,则不等待文件属性更新。
O_RSYNC---------->使每一个以文件描述符作为参数的read操作等待,直至任何对文件同一部分进行的未决写操作完成。
O_SYNC---------->使每次write都等到物理I/O操作完成,包括由write操作引起的文件属性更新所需的I/O。
3. FILE_MODE指文件的访问权限,其在第二个参数中有选项O_CREAT时才能用到,选择下列中的几个即可:
((S_IRUSR|S_IWUSR|S_IXUSR)|(S_IRGRP| S_IWGRP|S_IXGRP)|(S_IROTH|S_IWOTH| S_IXOTH))。
说明:
由open返回的文件描述符一定是最小的未用描述符值。
三、creat函数
也可调用creat函数创建一个文件。
#include <fcntl.h>
Int creat(const char *pathname, mode_t mode (FILE_MODE))
返回值:成功返回为只写打开的文件描述符,出错返回-1
参数介绍:
1. pathname为所要创建的文件的路径名(包括文件名)。
2. FILE_MODE表示文件的访问权限位,选择下列中的几个即可:
((S_IRUSR|S_IWUSR|S_IXUSR)|(S_IRGRP| S_IWGRP|S_IXGRP)|(S_IROTH|S_IWOTH| S_IXOTH))。
说明:
1. 该函数等效于open(pathname, O_WRONLY|O_CREAT|O_TRUNC,mode);
2. creat的一个不足之处是它以只写方式打开所创建的文件。
而由于open的新版本可以通过open(pathname,O_RDWR|O_CREAT|O_TRUNC,mode)创建文件导致creat函数很少用。
四、close函数
可调用close函数来关闭一个打开的文件。
#include <unistd.h>
Int close(int filedes)
返回值:若成功则返回0,若出错返回-1。
参数介绍:
Filedes为打开文件得到的文件描述符,标识要关闭之的那个文件。
说明:
关闭一个文件时会释放该进程加在该文件上的所有记录锁。当一个进程终止时,内核会自动关闭它所有打开的文件;故而很多程序利用这一点而不显示地用close关闭打开的文件。
五、lseek函数
系统默认情况下,打开一个文件其偏移量被设置为0;除非指定O_APPEND选项则不同。
可以调用lseek函数显式地为一个打开的文件设置其偏移量。
#include <unistd.h>
Off_t lseek(int filedes, off_t offset, int whence);
返回值:成功则返回新的文件偏移量,出错返回-1。
参数介绍:
1. 其中fildes为打开文件所得到的文件描述符,标识要对其设置文件偏移量的那个文件。
2. 对参数offset的解释与参数whence的值有关:
1. 若whence是SEEK_SET,则将该文件的偏移量设置为距文件开始处offset个字节;
2. 若whence是SEEK_CUR,则将该文件的偏移量设置为其当前值加offset,offset可为正或负;
3. 若whence是SEEK_END,则将该文件的偏移量设置为文件长度加offset,offset可为正或负。
说明:
1. 若lseek成功执行,则返回新的文件的偏移量,为此可以用下列方式确定打开文件的当前偏移量:
ff_t currpos; currpos=lseek(fd,0,SEEK_CUR);从而获得文件的当前偏移量。
2. 虽然对于普通文件其偏移量为非负值,但由于某些设备可能允许负的偏移量,所以在比较lseek的返回值时应谨慎,不要测试它是否小于0,而要测试它是否等于-1。
3. 文件空洞:文件偏移量可以大于文件的当前长度,对文件的下一次写将加长该文件并在文件中构成一个空洞(这一点是允许的)。文件中的空洞并不要求在磁盘上占用存储区。具体处理方式与文件系统实现有关,当定位到超出文件尾端之后写时,对于新写的数据需要分配磁盘块,但对于源文件尾端和新开始写位置之间的部分则不需要分配磁盘块。
六、read函数
调用read函数从打开文件中读数据。
#include <unistd.h>
Ssize_t read(int filedes, void *buf, size_t nbytes);
返回值:若成功则返回读到的字节数,若已到文件结尾则返回0,若出错则返回-1。
参数介绍:
1. fileses为打开文件返回得到的文件描述符,标识要向其读数据的那个文件;
2. buf标识用来存放从文件读到的数据的缓冲区;
3. nbytes标识所读取的字节数。
说明:
以下情况可能导致实际读到的字节数少于要求读的字节数:
1. 读普通文件时,在读到要求字节数之前已到达了文件尾端;
2. 当从终端读时,通常一次最多读一行;
3. 当从网络读时,网络中的缓冲机构可能造成返回值小于所要求读的字节数;
4. 当从管道或FIFO读时,若管道包含的字节少于所需的数量,则read将只返回实际可用的字节数;
5. 当从某些面向记录的设备读时,一次最多返回一个记录;
6. 当某一信号造成中断,而已经读了部分数据量时。
七、write函数
调用write函数向打开的文件写数据。
#include <unistd.h>
Ssize_t write(int filedes, const void *buf, size_t nbytes);
返回值:成功返回已写的字节数,出错返回-1。
参数介绍:
1. filedes为打开文件返回得到的文件描述符,标识要向其写数据的那个文件;
2. buf标识存放将其中数据写到文件中的缓冲区,存放要写到文件中的数据;
3. nbytes标识要写的数据字节数。
说明:
1. 其返回值通常与参数nbytes值相同,否则表示出错;write出错的一个常见的原因是:磁盘已写满或者超过了一个给定进程的文件长度限制。
2. 对于普通文件,写操作从文件的当前偏移量处开始。若在打开文件时,指定了O_APPEND选项,则在每次写操作前,将文件偏移量设置在文件的当前结尾处。成功写之后文件偏移量增加实际写的字节数。
八、文件共享
1.说明内核用于I/O的数据结构:
(1)每个进程在进程表中都有一个记录项,记录项中包含有一张打开文件描述符表,可将其视为一个矢量,每个描述符占用一项。与每个文件描述符相关联的是:
(a)文件描述符标识;
(b) 指向一个文件表项的指针;
(2)内核为所有打开的文件维持一张文件表。每个文件表项包含:
(a) 文件状态标志(读、写、添写、同步和非阻塞);
(b) 当前文件偏移量;
(c) 指向该文件v节点表项的指针;
(3)每个打开文件(或设备)都有一个v节点结构。V节点包含:
(a) 文件类型和对比文件进行各种操作的函数指针等信息;
(b) 对于大多数文件,v节点还包含了该文件的i节点信息(这些信息是在打开文件时从磁盘上读入内存的,所以所有关于文件的信息都是快速可供使用的),如i节点包含了文件的所有者、文件长度、文件所在设备、指向文件实际数据块在磁盘上所在位置的指针等等信息。
2. 文件共享, 若两个独立进程各自打开了同一个文件,则有:
假定第一个进程在文件描述符0上打开该文件,而另一个进程则在文件描述符2上打开该文件。打开该文件的每一个进程都得到一个文件表项,但对于一个给定的文件只有一个v节点表项。每个进程都有自己的文件表项的一个理由是:这种安排使每个进程都有它自己的对该文件的当前偏移量。
3. 说明:
(1) 在完成每个write后,在文件表项中的当前文件偏移量即增加所写的字节数。若这使当前偏移量超过了当前文件长度,则在i节点表项中的当前文件长度被设置为当前文件偏移量(也即文件加长了)。
(2) 若用O_APPEND标识打开了一个文件,则相应标识也被设置到文件表项的文件状态标志中。每次对这种具有添写标志的文件执行写操作时,在文件表项中的当前文件偏移量首先被设置为i节点表项中的文件长度。从而使每次写的数据都添加到文件的当前尾端处。
(3) 若一个文件用lseek定位到文件当前的尾端,则文件表项中的当前文件偏移量被设置为i节点表项中的当前文件长度。Lseek函数只修改文件表项中的当前文件偏移量,没有进行任何的I/O操作。
4. 注意:
文件描述符标志和文件状态标志在作用域方面的区别,前者只用于一个进程的一个描述符,而后者则适用于指向该给定文件表项的任何进程中的所有描述符。
九、原子操作
1.定义:原子操作指的是由多步组成的操作。如果该操作原子地执行,则要么执行完所有步骤,要么一步也不执行,不可能只执行所有步骤中的一个子集。
2.示例:Single UNIX Specification包括了XSI扩展,该扩展允许原子性地定位搜索和执行I/O。pread和pwrite就是这种扩展。
#include <unistd.h>
Ssize_t pread(int filedes, void *buf, size_t nbytes, off_t offset);
返回值:成功返回读到的字节数,若已到文件结尾则返回0,若出错则返回-1;
Ssize_t pwrite(int filedes, const void *buf, size_t nbytes, off_t offset);
返回值:成功返回已写的字节数,出错返回-1
调用pread相当于调用lseek和read,但pread又与这种顺序调用有以下重要区别:
(a) 调用pread时无法中断其定位和读操作。
(b) 不更新文件指针
十、dup和dup2函数
以下两个函数可用来复制一个现存的文件描述符。
#include <unistd.h>
Int dup(int filedes)
Int dup2(int filedes, int filedes2);
两函数的返回值:成功返回新的文件描述符,出错返回-1。
参数介绍:
Filedes表示现存的文件描述符。
说明:
1. 由dup返回的新文件描述符一定是当前可用文件描述符中的最小值。
而用dup2则可以用filedes2参数指定新描述符的值,若filedes2已经打开,则先将其关闭。若filedes等于filedes2,则dup2返回filedes而不关闭它。
2. 这些函数返回的新文件描述符与参数filedes共享同一个文件表项。
十一、sync、fsync和fdatasync函数
1. 延迟写的概念
大多数磁盘I/O都通过缓冲进行。当将数据写入文件时,内核通常先将该数据复制到其中一个缓冲区中,若该缓冲区尚未写满,则并不将其排入输出队列,而是等待其写满或者当内核需要重用该缓冲区以便存放其它磁盘块数据时,再将该缓冲排入输出队列,然后待其到达队首时才进行实际的I/O操作。这种输出方式称为延迟写。
2. sync、fsync和fdatasync函数的引入
延迟写减少了磁盘读写次数,但却降低了文件内容的更新速度,使得欲写到文件中的数据在一段时间内并没有写到磁盘上。当系统发生故障时,这种延迟可能造成文件更新内容的丢失。为了保证磁盘上实际文件系统与缓冲区高速缓存中内容的一致性,unix系统提供了sync、fsync和fdatasync三个函数。
3. sync、fsync和fdatasync三个函数
#include <unistd.h>
Int fsync(int filedes);
Int fdatasync(int filedes);
Void sync(void);
返回值:成功返回0,出错返回-1
参数介绍:
Filedes表示要修改同步方式的那个文件的文件描述符。
说明:三个函数的区别:
1. sync函数只是将所有修改过的块缓冲排入写队列,然后就返回,它并不等待实际写磁盘操作结束。通常称为update的系统守护进程会周期性地调用sync函数从而保证定期冲洗内核的块缓冲区。(放入队列返回不等待)
2. fsync函数只对由文件描述符filedes指定的单一文件起作用,并且等待写磁盘操作结束,然后再返回。其一般用于需要确保将修改过的块立即写到磁盘上的场合。(放入队列等待,数据+属性)
3. fdatasync函数类似于fsync,但其只影响文件的数据部分。而除数据外,fsync还会同步更新文件的属性。(数据)
十二、fcntl函数
Fcntl函数可以改变已打开的文件的性质。
#incldue <fcntl.h>
Int fcntl(int filedes, int cmd, …./*int arg*/);
返回值:成功则依赖于cmd,出错返回-1。
Fcntl函数有5种功能:
(1) 赋值一个现有的描述符(cmd=F_DUPFD)。
(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)。
参数介绍:
1. filedes表示要改变其性质的那个文件的文件描述符。
2. cmd表示操作标识。以下一一介绍前7种(后3中以后介绍):
F_DUPFD--------->赋值文件描述符filedes,新文件描述符作为函数值返回,新描述符与filedes共享同一文件表项。
F_GETFD--------->对应于filedes的文件描述符标志作为函数值返回。
F_SETFD--------->对于filedes设置文件描述符标志,新标志值按第三个参数(整型值)设置。
F_GETFL--------->对应于filedes的文件状态标志作为函数值返回。Fcntl的文件状态标志如下表所示:
文件状态标志 | 说明 |
O_RDONLY | 只读打开0 |
O_WRONLY | 只写打开1 |
O_RDWR | 读写打开2 |
O_APPEND | 每次写时追加 |
O_NONBLOCK | 非阻塞模式 |
O_SYNC | 等待写完成(数据和属性) |
O_DSYNC | 等待写完成(仅数据) |
O_RSYNC | 同步读、写 |
O_FSYNC | 等待写完成(仅FressBSD和Mac OS X) |
O_ASYNC | 异步I/O(仅FressBSD和Mac OS X) |
其中O_RDONLY,O_WRONLY,O_RDWR三个标志互斥,则需用屏蔽字O_ACCMODE取得访问模式位,然后将结果与这三个值中的任一个做比较。
F_SETFL--------->将文件状态标志设置为第三个参数的值(整型值)。
F_GETOWN--------->取当前接收SIGIO和SIGURG信号的进程ID或进程组ID。
F_SETOWN--------->设置接收SIGIO和SIGURG信号的进程ID或进程组ID。
注意:
在修改修改文件描述符标识或文件状态标志时必须谨慎,先要取得现有的标志值,然后根据需要修改它,最后设置新标志值。不能只是执行F_SETFD或F_SETFL命令,这样会关闭以便设置的标志位。