一、文件控制操作
#include <unistd.h>
#include <fcntl.h>
int fcntl(int fd, int cmd, ... /* arg */ );
fcntl()的用途之一是针对一个打开的文件,获取或修改其访问模式和状态标志(这些值是通过指定 open()调用的 flag 参数来设置的)。要获取这些设置,应将 fcntl()的 cmd 参数设置为F_GETFL。举例如下:
int flags;
flags= fcntl(fd,F_GETFL);
if(flags == -1) errExit("fcntl");
if(flags & O_SYNC) printf("writes are synchronized\n")
可以使用 fcntl()的 F_SETFL 命令来修改打开文件的某些状态标志。允许更改的标志有O_APPEND、O_NONBLOCK、O_NOATIME、O_ASYNC 和 O_DIRECT。系统将忽略对其他标志的修改操作。fcntl()修改文件状态标志,尤其适用于如下场景:
①文件不是由调用程序打开的,所以程序也无法使用 open()调用来控制文件的状态标志(例如,文件是 3 个标准输入输出描述符中的一员,这些描述符在程序启动之前就被打开)。
② 文件描述符的获取是通过 open()之外的系统调用。比如 pipe()调用,该调用创建一个管道,并返回两个文件描述符分别对应管道的两端。再比如 socket()调用,该调用创建一个套接字并返回指向该套接字的文件描述符。
下例展示如何获取及修改文件的标识
int flags;
flags= fcntl(fd,F_GETFL);
if(flags == -1) errExit("fcntl");
flags |= O_APPEND;
if(fcntl(fd,F_SETFL,flags) == -1) errExit("fcntl");
二、文件描述符
内核会维护3个与文件描述符相关的数据结构:
1.进程级的文件描述符表
2.系统级的打开文件表
3.文件系统的i-node表
针对每个进程,内核为其维护打开文件的描述符表,该表的每一条目录都记录了单个文件描述符的相关信息,具体内容如下:
a.控制文件描述符操作的一组标志(目前,此类标志仅定义了一个,即 close-on-exec 标志)。
b.对打开文件句柄的引用
内核对所有打开的文件维护有一个系统级的描述表格(open file description table)。有时,也称之为打开文件表(open file table),并将表中各条目称为打开文件句柄(open file handle)。一个打开文件句柄存储了与一个打开文件相关的全部信息,如下所示。
a. 当前文件偏移量(调用 read()和 write()时更新,或使用 lseek()直接修改)。
b. 打开文件时所使用的状态标志(即,open()的 flags 参数)。
c. 文件访问模式(如调用 open()时所设置的只读模式、只写模式或读写模式)。
d. 与信号驱动 I/O 相关的设置。
e. 对该文件 i-node 对象的引用。
每个文件系统都会为驻留其上的所有文件建立一个 i-node 表,具体如下所示。
a. 文件类型(例如,常规文件、套接字或 FIFO)和访问权限。
b. 一个指针,指向该文件所持有的锁的列表。
c. 文件的各种属性,包括文件大小以及与不同类型操作相关的时间戳。
文件描述符、打开的文件句柄以及 i-node 之间的关系如下图所示。在该图中,每个进程都拥有多个打开的文件描述符。进程A中,文件描述符1和20都指向同一个打开的文件句柄(对应标号23),这可能是通过调用dup()、dup2()或 fcntl()而形成的。进程A的fd2和进程B的fd2都指向同一个打开的文件句柄(标号为73)。这种情形可能在调用 fork()后出现(即,进程 A 与进程 B 之间是父子关系),或者当某进程通过UNIX 域套接字将一个打开的文件描述符传递给另一进程时,也会发生。此外,进程A的描述符0和进程B的描述符3分别指向不同的打开文件句柄,但这些句柄均指向i-node表中的相同条目(1976),即指向同一个文件。发生这种情况是因为每个进程各自对同一文件发起了 open()调用。同一个进程两次打开同一文件,也会发生类似情况。
其特性总结如下:
① 两个不同的文件描述符,若指向同一打开文件句柄,将共享同一文件偏移量。因此,如果通过其中一个文件描述符来修改文件偏移量(由调用 read()、write()或 lseek()所致),那么从另一文件描述符中也会观察到这一变化。无论这两个文件描述符分属于不同进程,还是同属于一个进程,情况都是如此。
② 要获取和修改打开的文件标志(例如,O_APPEND、O_NONBLOCK 和 O_ASYNC),可执行 fcntl()的 F_GETFL 和 F_SETFL 操作,其对作用域的约束与上一条颇为类似。
③ 相形之下,文件描述符标志(亦即,close-on-exec 标志)为进程和文件描述符所私有。对这一标志的修改将不会影响同一进程或不同进程中的其他文件描述符。
#include <unistd.h>
int dup(int oldfd);
int dup2(int oldfd, int newfd);
#define _GNU_SOURCE /* See feature_test_macros(7) */
#include <fcntl.h> /* Obtain O_* constant definitions */
#include <unistd.h>
int dup3(int oldfd, int newfd, int flags);
dup 创建了oldfd 文件描述符的一份拷贝,使用未用的最小整数文件描述符作为新的描述符。二者都指向同一个打开的文件句柄,因此两个文件描述符共享文件offset和文件status flags。但不共享文件描述符标志(close-on-exec 标志),close-on-exec 标志在dup中是关闭的。
dup2 作用于dup相同,但是用指定的newfd 来作为新的文件描述符,如果newfd先前被打开,他将会静默的关闭它,并重新使用。关闭和重新使用newfd步骤是原子的执行。注意事项:
①如果oldfd是一个无效的文件描述符,将会调用失败,newfd不会被关闭。
②如果oldfd有效,newfd和oldfd值相同,dup2将不做任何事,直接返回newfd。
dup3和dup2作用相同,除了以下两点:
①调用者通过指定在flags参数O_CLOEXEC强制设置close-on-exec到newfd。
②如果oldfd 和newfd相等,dup3将会失败并返回错误EINVAL。