05-open()调用中的flags参数
4.3.1 open()调用中的flags参数
在程序清单4-2展示的一些open()调用例子中,flags参数除了使用文件访问标志外,还使用了其他操作标志(O_CREAT、O_TRUNC和O_APPEND)。现在将详细介绍flags参数。表4-3总结了可参与flags参数逐位或运算(|)的一整套常量。最后一列显示常量标准化于SUSv3还是SUSv4。
| 标 志 | 用 途 | 统一UNIX规范版本 | | :----- | :----- | :----- | :----- | :----- | | O_RDONLY | 以只读方式打开 | v3 | | O_WRONLY | 以只写方式打开 | v3 | | O_RDWR | 以读写方式打开 | v3 | | O_CLOEXEC | 设置close-on-exec标志(自Linux 2.6.23版本开始) | v4 | | O_CREAT | 若文件不存在则创建之 | v3 | | O_DIRECT | 无缓冲的输入/输出 | | O_DIRECTORY | 如果pathname不是目录,则失败 | v4 | | O_EXCL | 结合O_CREAT参数使用,专门用于创建文件 | v3 | | O_LARGEFILE | 在32位系统中使用此标志打开大文件 | | O_NOATIME | 调用read()时,不修改文件最近访问时间(自Linux 2.6.8版本开始) | | O_NOCTTY | 不要让pathname(所指向的终端设备)成为控制终端 | v3 | | O_NOFOLLOW | 对符号链接不予解引用 | v4 | | O_TRUNC | 截断已有文件,使其长度为零 | v3 | | O_APPEND | 总在文件尾部追加数据 | v3 | | O_ASYNC | 当I/O操作可行时,产生信号(signal)通知进程 | | O_DSYNC | 提供同步的I/O数据完整性(自Linux 2.6.33版本开始) | v3 | | O_NONBLOCK | 以非阻塞方式打开 | v3 | | O_SYNC | 以同步方式写入文件 | v3 |
表4-3中常量分为如下几组。
- 文件访问模式标志:先前描述的O_RDONLY、O_WRONLY和O_RDWR标志均在此列,调用open()时,上述三者在flags参数中不能同时使用,只能指定其中一种。调用fcntl()的F_GETFL操作能够检索文件的访问模式(见5.3节)。
- 文件创建标志:这些标志在表4-3中位于第二部分,其控制范围不拘于open()调用行为的方方面面,还涉及后续I/O操作的各个选项。这些标志不能检索,也无法修改。
- 已打开文件的状态标志:这些标志是表4-3中的剩余部分,使用fcntl()的F_GETFL和F_SETFL操作可以分别检索和修改此类标志。有时干脆将其称之为文件状态标志。
始于内核版本2.6.22,读取位于/proc/PID/fdinfo目录下的linux系统专有文件,可以获取系统内任一进程中文件描述符的相关信息。针对进程中每一个已打开的文件描述符,该目录下都有相应文件,以对应文件描述符的数值命名。文件中的pos字段表示当前的文件偏移量(4.7节)。而flags字段则为一个八进制数,表征文件访问标志和已打开文件的状态标志。(该数字的解码需要参考这些标志在C语言函数库头文件中所定义的数值。)
如下是flags常量的详细描述。
O_APPEND
总是在文件尾部追加数据,5.1节将讨论此标志的意义。
O_ASYNC
当对于open()调用所返回的文件描述符可以实施 I/O 操作时,系统会产生一个信号通知进程。这一特性,也被称为信号驱动I/O,仅对特定类型的文件有效,诸如终端、FIFOS及socket。(在SUSv3中并未规定O_ASYNC标志,但大多数UNIX实现都支持此标志或者老版本中与其等效的FASYNC标志。)在Linux中,调用open()时指定O_ASYNC标志没有任何实质效果。要启用信号驱动I/O特性,必须调用fcntl()的F_SETFL操作来设置O_ASYNC标志(见5.3节)。(其他一些UNIX系统的实现有类似行为。)关于O_ASYNC标志的更多内容请参考63.3节。
O_CLOEXEC(自Linux 2.6.23版本开始支持)
为新(创建)的文件描述符启用close-on-flag标志(FD_CLOEXEC)。27.4节将描述FD_CLOEXEC标志。使用O_CLOEXEC标志(打开文件),可以免去程序执行fcntl()的F_GETFD和F_SETFD操作来设置close-on-exec标志的额外工作。在多线程程序中执行fcntl() 的F_GETFD和F_SETFD操作有可能导致竞争状态,而使用O_CLOEXEC标志则能够避免这一点。可能引发竞争的场景是:线程某甲打开一文件描述符,尝试为该描述符标记close-on-exec标志,于此同时,线程某乙执行fork()调用,然后调用exec()执行任意一个程序。(假设在某甲打开文件描述符和调用fcntl()设置close-on-exec标志之间,某乙成功地执行了fork()和exec()操作。)此类竞争可能会在无意间将打开的文件描述符泄露给不安全的程序。(更多关于竞争状态的内容请参考5.1节。)
O_CREAT
如果文件不存在,将创建一个新的空文件。即使文件以只读方式打开,此标志依然有效。如果在open()调用中指定O_CREAT标志,那么还需要提供mode参数,否则,会将新文件的权限设置为栈中的某个随机值。
O_DIRECT
无系统缓冲的文件I/O操作。该特性将在13.6节中详述。为使O_DIRECT标志的常量定义在<fcntl.h>中有效,必须定义_GNU_SOURCE功能测试宏。
O_DIRECTORY
如果pathname参数并非目录,将返回错误(错误号errno为ENOTDIR)。这一标志是专为实现opendir()函数(18.8节)而设计的扩展标志。为使O_DIRECTORY标志的常量定义在<fcntl.h>中有效,必须定义_GNU_SOURCE功能测试宏。
O_DSYNC(自Linux 2.6.33版本开始支持)
根据同步I/O数据完整性的完成要求①来执行文件写操作。参见13.3节中关于内核I/O缓冲的讨论。
O_EXCL
此标志与 O_CREAT 标志结合使用表明如果文件已经存在,则不会打开文件,且 open()调用失败,并返回错误,错误号errno为EEXIST。换言之,此标志确保了调用者(open( )的调用进程)就是创建文件的进程。检查文件存在与否和创建文件这两步属于同一原子操作。5.1节将讨论原子操作的概念。如果在flags参数中同时指定了O_CREAT和O_EXCL标志,且pathname参数是符号链接,则open()函数调用失败(错误号errno为EEXIST)。SUSv3之所以如此规定,是要求有特权的应用程序在已知目录下创建文件,从而消除了如下安全隐患,使用符号链接打开文件会导致在另一位置创建文件(例如,系统目录)。
O_LARGEFILE
支持以大文件方式打开文件。在32位操作系统中使用此标志,以支持大文件操作。尽管在SUSv3中没有规定这一标志,但其他一些UNIX实现都支持这一特性。此标志在诸如Alpha、IA-64之类的64位Linux实现中是无效的。更多的内容将在5.10节中讨论。
O_NOATIME(自Linux 2.6.8版本开始)
在读文件时,不更新文件的最近访问时间(15.1 节中所描述的st_atime属性)。要使用该标志,要么调用进程的有效用户ID必须与文件的拥有者相匹配,要么进程需要拥有特权(CAP_FOWNER)。否则,open()调用失败,并返回错误,错误号errno为EPERM。(事实上,如9.5节所述,对于非特权进程,当以O_NOATIME标志打开文件时,与文件用户ID必须匹配的是进程的文件系统用户ID,而非进程的有效用户ID。)此标志是Linux特有的非标准扩展。要从<fcntl.h>中启用此标志,必须定义_GNU_SOURCE功能测试宏。O_NOATIME标志的设计旨在为索引和备份程序服务。该标志的使用能够显著减少磁盘的活动量,省却了既要读取文件内容,又要更新文件i-node结构中最近访问时间的繁琐,进而节省了磁头在磁盘上的反复寻道时间(14.4 节)。mount()函数中MS_NOATIME标志(14.8.1节)和FS_NOATIME_FL标志(15.5节)与O_NOATIME标志功能相似。
O_NOCTTY
如果正在打开的文件属于终端设备,O_NOCTTY标志防止其成为控制终端。34.4节将讨论控制终端。如果正在打开的文件不是终端设备,则此标志无效。
O_NOFOLLOW
通常,如果pathname参数是符号链接,open()函数将对pathname参数进行解引用。一旦在open()函数中指定了O_NOFOLLOW标志,且pathname参数属于符号链接,则open()函数将返回失败(错误号errno为ELOOP)。此标志在特权程序中极为有用,能够确保open()函数不对符号链接进行解引用。为使O_NOFOLLOW标志在<fcntl.h>中有效,必须定义_GNU_SOURCE功能测试宏。
O_NONBLOCK
以非阻塞方式打开文件,参照5.9节。
O_SYNC
以同步I/O方式打开文件,参见13.3节针对内核I/O缓冲的讨论。
O_TRUNC
如果文件已经存在且为普通文件,那么将清空文件内容,将其长度置0。在Linux下使用此标志,无论以读、写方式打开文件,都可清空文件内容(在这两种情况下,都必须拥有对文件的写权限)。SUSv3对O_RDONLY与O_TRUNC标志的组合未作规定,但多数其他UNIX实现与Linux的处理方式相同。