6. 文件相关

底层文件系统的组织

操作系统的一个重要功能就是管理文件,操作系统面对的是一个一个的磁盘块,因此操作必须要把这些磁盘块给管理起来,他得知道哪个文件从哪个磁盘块开始,又到哪个磁盘块结束。
unix 类的操作系统使用 inode 节点来实现这个目的,在 ext 类的文件系统中,磁盘被划分为固定大小的几个组,每个组的开始的位置是固定使用的,不能被用于存储用户的文件,而是存储这个组的元信息,整个组被划分为如下区域:

注意,不包含文件名和文件的实际内容。
文件的实际内容在数据块指针指向的位置。
而文件名则存储在文件类型=目录的这种特殊文件的数据块中(对应的文件的 inode 也在目录文件的数据块中,文件名和 Inode 的 mapping 关系统一称之为目录项)。
即 Unix 的万物皆文件的思想,这些目录类的文件中存储的内容就是他之下的文件名和该文件对应的 Inode 编号

有了 Inode 这一套数据结构之后,OS 已经可以在众多的磁盘块之中准确的管理文件了。

文件的打开和进程间的共享

接下来就是操作文件,操作文件需要解决两个问题,一个是 OS 得对这些文件做一个统一的管理,以便应对多个进程同时访问同一个文件的场景,另外一个问题就是一个进程可能会同时打开多个文件,需要给进程一个更容易的视角。

OS 的系统级别使用的是「系统文件打开表(system wide Open file Table)」,当进程通过系统调用 open 打开文件的时候,OS 会在这个表中增加一条数据(除非是子进程 fork() 父进程,或者是显性的通过 dup() 创建复用)。
系统文件打开表中的每行包含以下信息

字段名 字段说明
编号 系统文件打开表的主键,自增编号,用于索引
inode 地址 被打开文件的 inode 指针(即地址)
打开模式 rw(读写)/ r(只读)
当前偏移量 120 当前的偏移量,当多个进程(例如父子)同时访问一个文件的时候,可以共享这个信息
状态标志 文件是否阻塞
引用计数 一个数字,例如2,代表被多少个进程引用了
指向的文件路径 /home/users/a.txt
即 inode 是全局复用的,不会重复创建,每个系统进程打开的时候会有独立的系统文件打开表表项,用于单独管理该进程的打开模式、偏移量。

在进程粒度,每个进程会有自己的进程描述符表 File Descriptor Table ,存储在 OS 的内核中,每个进程独立。这个表的作用是给进程一个独立的视角,使用连续的文件描述符(整数),而不是 OS 提供的 Inode 来访问/操作文件。
表的内容如下:

字段 解释
文件描述符(fd) 一个整形变量,不同值代表不同文件。
指向系统文件表项编号 对应的系统文件打开表的主键

完整地流程

一个进程要打开一个文件

  1. 该进程通过 open 系统调用打开文件
  2. OS 进入内核态,系统调用会检查必要的权限、文件是否存在等校验。
  3. 如果检查通过,该文件之前没被打开过,则 OS 将对应的 Inode 调入内存,并在系统文件打开表中增加一条记录,指向该 Inode,如果已经打开过,则 Inode 肯定在内存,只需要在系统文件打开表中新增一条记录即可。
  4. 在该进程的文件描述符表中增加一条记录,并指向系统文件打开表中的那个记录。
  5. 系统调用返回文件描述符
  6. 该进程可以使用文件描述符进行操作。