今天说下文件这个主题。

为什么说到这个?一个重要的原因,UNIX几乎把所有东西都看成文件对待。这也是UNIX简洁的一个体现,可以用一套理念理解系统内大部分原理。另外,越常见越常用的东西其实越难掌握可理解。所谓大道至简,而看似简单其实并不代表容易。

接下来会先描述内核读取文件的数据结构,再总结文件操作函数等。在APUE中,文件I/O这章是先通过文件函数引入进行说明的,这种组织方式便于实际应用以及开发。而本文作为总结就没有按这种方式。

I/O数据结构

内核通过3种数据结构表示打开的文件:

  1. 每个进程的进程表中,包含一张打开的文件描述符表,每项包括:
    • 文件描述符
    • 文件描述符标识
    • 指向文件表的指针
  2. 内核为所有打开的文件维持一张文件表,每项包括:
    • 文件状态标识:读、写、添写、同步和非阻塞等
    • 当前文件偏移量
    • 指向该文件v节点表项的指针
  3. 每个打开的文件或设备都有一个v节点(v-node)结构,其包括:
    • 文件类型
    • 对文件进行各种操作函数的指针
    • i节点(i-node,索引节点)
      • 文件的所有者
      • 文件长度
      • 指向文件实际数据块在磁盘上所在位置的指针

文件描述符

对于内核而言,所有打开的文件都是通过文件描述符引用。

  • 文件描述符是一个非负整数
  • 当打开一个现有文件或者创建一个新文件时,内核向进程返回一个文件描述符。

按照惯例,文件描述符0、1和2会被UNIX系统shell默认关联

  • 文件描述符0:标准输入
  • 文件描述符1:标准输出
  • 文件描述符2:标准错误

通过openopenat函数返回的文件描述符,一定是最小的未用描述符值

进程终止时,内核自动关闭它打开的所有文件。

文件偏移量

每个打开文件都有一个与其相关联的”当前文件偏移量”。通常是一个非负整数,用以度量从文件开始处计算的字节数。

lseek可以显式地为一个打开文件设置偏移量。

  • 如果一个文件描述符指向的是一个管道、FIFO或网络套接字,则lseek返回-1,并将errno设置为ESPIPE。
  • lseek仅将当前的文件偏移量记录在内核中,并不引起任何I/O操作。

文件偏移量可以大于文件的当前长度。在这种情况下,对该文件的下一次写将加长该文件,并在文件中构成一个空洞

  • 位于文件中但没有写过的字节都被读为0。
  • 文件中的空洞并不要求在磁盘上占用存储区。

尽管可以实现64位文件偏移量,但能否创建一个大于2GB(2^31 -1字节)的文件依赖于底层文件系统的类型。

文件共享

每个进程都获得自己的文件表项

  • 每个进程都有它自己的对该文件的当前偏移量
  • lseek函数只修改文件表项中的当前文件偏移量,不进行任何I/O操作

原子操作

一般而言,原子操作指的是由多步组成的一个操作。如果该操作原子地执行,则要么执行完所有步骤,要么一步也不执行。不可能只执行所有步骤的一个子集。

缓冲区

传统的UNIX系统实现在内核中设有缓冲区高速缓存页高速缓存,大多数磁盘I/O都通过缓冲区进行。

本文作者:Tobin
本文地址http://www.thirteenyu.com/2018/04/12/tech-apue-file-io/
版权声明:本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明出处!