Skip to main content

第三章: 文件 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.

常用文件操作函数

  1. 打开文件 openopenat
  2. 创建文件 creat
  3. 关闭文件 close
  4. 设置文件当前偏移量 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 查看文件的十六进制内容: Alt text

  5. 读文件 read ...待续...