4.1 通用的 I/O 模型
本章重点:
- 文件描述符
- 通用I/O模型的系统调用
1. 文件描述符
概念:
- 一个非负整数,指代打开的文件
- 用以表示所有类型的已打开文件,包括管道,FIFO,socket,终端,设备和普通文件
- 针对每个进程,文件描述符都自成一套
标准输入输出
- shell 代表程序打开 3 个文件描述符
- 程序继承了 shell 文件描述符的副本
文件描述符 | 用途 | POSIX名称 | stdio流 |
---|---|---|---|
0 | 标准输入 | STDIN_FILENO | stdin |
1 | 标准输出 | STDOUT_FILENO | stdout |
2 | 标准错误 | STDERR_FILENO | stderr |
2. 通用 I/O 模型
UNIX I/O 模型特点
- 四个基本的系统调用可以对所有类型的文件执行 I/O 操作, 包括终端之类的设备
- 程序需要访问文件系统或设备的专有功能时,可以使用 ioctl()
- ioctl() 为通用 I/O 模型之外的专有特性提供了访问接口
3. I/O 操作的系统调用
3.1 open
fd = open(pathname, flags, mode):
- 作用:打开一个已存在文件,或创建并打开一个新文件
- 返回:
- 成功打开文件,返回文件描述符(未用文件描述符中数值最小者)
- 发生错误返回 -1,并将 error 设置为相应的错误标识
- 参数:
- pathname:文件路径,如果 pathname 是符号连接,将对其解引用
- flags: 指定文件的打开方式
- mode: 指定 open() 调用创建文件的访问权限,如果未创建文件,可以忽略 mode 参数
mode
附注:
- open() 未指定 O_CREATE 标识,可以省略 mode 参数
- 新建文件的权限,不仅仅依赖于参数 mode,而且受到进程的 umask 值和父目录的默认访问权限控制列表的影响
flags
分类:
- 文件访问模式标识:
- 包括:O_RDONLY, O_WRONLY, O_RDWR
- 特性:不能同时使用,只能指定其一
- 检索:调用 fcntl() 的 F_GETFL 操作可以检索文件的访问模式
- 文件创建标识:
- 特性:这些标识不能检索,也无法修改
- 已打开文件的状态标识
- 特性:调用 fcntl() 的 F_GETFL 和 F_SETFL 可以检索和修改此类标识
标识 | 用途 |
---|---|
O_RDONLY | 以只读方式打开文件 |
O_WRONLY | 以只写方式打开文件 |
O_RDWR | 以读写方式打开文件 |
O_ASYNC | 当I/O操作可行时,产生信号通知进程 |
O_NONBLOCK | 以非阻塞方式打开 |
O_SYNC | 以同步方式写入文件 |
O_DSYNC | 提供同步的I/O数据完整性 |
O_DIRECT | 无缓冲的输入/输出 |
O_APPEND | 总在文件尾部追加数据 |
O_TRUNC | 截断已有文件,使其长度为 0 |
O_CREAT | 若文件不存在则创建 |
O_LARGEFILE | 在 32 位系统中,使用此标志打开大文件 |
O_EXCL | 结合 O_CREAT,专门用于创建文件 |
O_CLOEXEC | 设置 close-no-exec 标识 |
O_NOATIME | 调用 read()时,不修改文件最近访问时间 |
O_NOCTTY | 不要让 pathname 成为控制终端 |
O_NOFOLLOW | 不对符号链接解引用 |
O_DIRECTORY | 如果 pathname 不是目录,则失败 |
同步异步,阻塞非阻塞
O_ASYNC:
- 章节:63.3
- 作用:信号驱动 I/O
- 特性:仅对特定类型的文件有效,包括 终端,FIFOS,socket
- 附注:Linux 中指定 O_ASYNC 没有任何效果,要启动信号驱动 I/O 特性,必须调用 fcntl() 的 F_SETFL 操作来设置 O_ASYNC
O_NONBLOCK:
- 章节:5.9
- 作用:以非阻塞方式打开文件
O_SYNC:
- 章节:13.3
- 作用:以同步 I/O 方式打开文件
O_DSYNC:
- 章节:13.3
- 作用:根据同步 I/O 数据完整性的完成要求来执行文件写操作
O_DIRECT:
- 章节:13.6
- 作用:无系统缓冲的文件 I/O 操作
- 特性:为使此标志的常量定义在
中有效,必需定义 _GNU_SOURCE 功能测试宏
文件创建
O_APPEND
- 章节:5.1
O_TRUNC:
- 作用:如果文件已经存在且为普通文件,那么将清空文件内容,将其长度置为 0
- 特性:
- Linux 下使用此标志,无论以读写方式打开文件,都可清空文件内容 (两种情况下,都必须拥有对文件的写权限)
- SUSv3 O_RDONLY 与 O_TRUNC 标志的组合未作规定,但多数其他 UNIX 实现与 Linux 的相同
O_CREAT
- 作用:如果文件不存在,将创建新的空文件
- 特性:即使文件以只读方式打开,此标志依然有效
- 附注:open 调用还需要提供 mode 参数,否则会将新文件的权限设置为栈中的某个随机值
O_LAGEFILE:
- 章节:5.10
- 作用:32 位操作系统中使用此标志,支持大文件操作
- 特性:此标志在 64 位 Linux 中是无效的
原子操作
O_CLOEXEC:
- 章节:27.4
- 作用:
- 为新创建的文件描述符启用 close-on-flag 标志(FD_CLOEXEC)
- 免去程序执行 fcntl() 的 F_GETFL 和 F_SETFL 操作来设置 close-on-exec 的额外工作
- 在多线程中,上述两个操作可能导致竞争状态,O_CLOEXEC 标识能避免这一点
O_EXCL:
- 作用:
- 与 O_CREAT 结合使用,表明如果文件已经存在,不会打开文件
- open() 调用失败,并返回错误,error 为 EEXIST
- 即此标志确保了调用者 (open() 的调用进程)就是创建文件的进程
- 检查文件存在与否和创建文件这两步属于同一原子操作
- 特性:
- 同时指定 O_EXCL,O_CREAT,且 pathname 是符号链接,open 调用失败(error 为 EEXIST)
- 之所以这样规定,是要求有特权的应用程序在已知目录下创建文件,消除安全隐患, 使用符号链接打开文件会导致另一位置创建文件
文件打开限制
O_DIRECTORY:
- 章节:18.8
- 作用:
- 如果 pathname 非目录,将返回错误(error 为 ENOTDIR)
- 是转为 实现 opendir() 函数而设计的扩展标识(18.8)
- 特性:为使此标志的常量定义在
中有效,必需定义 _GNU_SOURCE 功能测试宏
O_NOFOLLOW:
- 作用:
- 通常 如果 pathname 是符号链接,open 会对其解引用
- 指定此标志,且 pathname 是符号链接时,open 函数将返回失败,error 为 ELOOP
- 特性:
- 此标志在特权程序中极其有用,确保 open 不对符号链接解引用
- 为使此标志的常量定义在
中有效,必需定义 _GNU_SOURCE 功能测试宏
O_NOATIME:
- 作用:
- 读文件时,不更新文件的最近访问时间(15.1 节描述的 st_atime 属性)
- 不满足下列要求,open 调用失败,并返回错误,error 为 EPERM
- 要求:
- 要么调用进程的有效用户 ID 必需与文件的拥有者相匹配
- 要么进程需要拥有特权(CAP_FOWNER)
- 9.5 节所述,对于非特权程序,与文件用户 ID 必需匹配的是进程的文件系统用户 ID, 而非进程的有效用户 ID
- 特性:
- 比标志是 Linux 特有的非标准扩展
- 为使此标志的常量定义在
中有效,必需定义 _GNU_SOURCE 功能测试宏
- 原理:
- O_NOATIME 旨在为索引和备份程序服务
- 该标志的使用能够显著减少磁盘的活动量,省却了既要读取文件内容,又要更新文件 i-node 结构中最近访问时间的繁琐,进而省却了磁头在磁盘上的反复寻道时间(14.4节)
- mount() 函数中 MS_NOATIME标志(14.8.1) 和 FS_NOATIME_FL标志(15.5) 与 O_NOATIME 功能相似
O_NOCTTY:
- 作用:
- 如果正在打开的文件属于终端设备,此标志防止其成为控制终端
- 如果正在打开的文件不是终端设备,此标志无效
3.2 read
numread = read(fd, buffer, count)
- 作用:调用从 fd 所指代的打开文件中读取至多 count 字节的数据,存储到 buffer 中
- 返回:实际读取到的字节数,如果无字节可读,则返回 0
3.3 write
numwritten = write(fd, buffer, count)
- 作用:从buffer 中读取多达 count 字节的数据写入由 fd 所指代的已打开文件
- 返回:实际写入文件中的字节数,有可能小于 count
3.4 close
status = close(fd):
- 作用:释放文件描述符,以及与之相关的资源