第三章: 文件 I/O
绝大部分 UNIX 文件 I/O 操作都可以只通过使用如下系统调用完成:
open
read
write
close
lseek
本章中讨论的系统调用都是 unbuffered I/O
, 即功能本身不提供缓冲区, 缓冲区设置都是由程序员自己决定. 这和标准库中的 I/O 区分开(标准库中的也称为 buffered I/O
)
这些操作并非 ISO C 中的, 而是 POSIX.1 及 SUS 中的.
由于可能出现跨进程共享资源的情况, 因此操作共享资源时, 原子操作就非常重要了(这个时候需要内核参与, 且使用特定的内核数据结构).
本章另外还讨论了这些函数: dup
, fcnctl
, sync
, fsync
, ioctl
.
文件描述符
对内核而言所有打开的文件都通过 file descriptor 表示, 即文件描述符. 文件描述符为非负整数, 当打开现有文件或创建新文件时, 内核会将对应的文件描述符返回给进程. 当我们需要对此文件进行读写操作时, 就可以使用对应的文件描述符作为参数传给操作函数.
根据约定, 有如下三个文件是默认打开的, 对应文件描述符 0, 1, 2:
0
: 标准输入1
: 标准输出2
: 标准错误
这个约定是针对绝大部分 Shell 而言的, 而非内核的功能. 当然如果这个约定被破坏, 可能大部分程序都不能正常运行. 虽然这三个文件描述符在 POSIX.1 中被标准化了, 但实际使用时, 还是建议使用它们对应的符号常量:
STDIN_FILENO
STDOUT_FILENO
STDERR_FILENO
它们被定义在 <unistd.h>
中.
文件描述符的范围是: 0
~ OPEN_MAX - 1
.
常用文件操作函数
- 打开文件
open
和openat
- 创建文件
creat
- 关闭文件
close
- 设置文件当前偏移量
lseek
(每个打开文件都有一个 "当前文件偏移量", 即从文件开始位置按字节计算的偏移值, 读写文件时都会触发偏移量更新)-
小技巧: 可以通过 SEEK_CUR 并设置 offset 为 0, 从而获取当前文件偏移量的值
off_t currpos = lseek(fd, 0, SEEK_CUR)
这个办法也可以用来测试某个文件是否支持修改偏移, 如果文件是
pipe
,FIFO
,socket
等, 这个调用会返回 -1 并且errno
会是ESPIPE
, 即表示无法设置偏移量. 但判断时我们不能只判断负数返回值, 而应严格判断 -1, 因为某些设备实际是有负数偏移可能的. 比如 FreeBSD 的/dev/kmem
设备就支持负数的 offset.lseek 调用不会导致任何的 I/O 动作, 它只是对内核记录的 offset 进行操作, 这样下次文件操作就可以在此 offset 值的基础上进行.
-
文件的 offset 可以大于文件的当前大小, 这样下一次写文件操作就可以扩展这个文件的大小. 这样的动作被称为 "制造文件空洞", 并且是被允许的行为, 空洞中的内容在被读回时都会是
0
. 文件空洞可能不会导致磁盘实际的占用(根据文件系统实现的不同有不同) -
可以使用
od -c file
查看文件的十六进制内容:
-
- 读文件
read
...待续...