File System Overview
文件是什么
文件是一组有意义的信息或数据集合。用户通常通过文件名、路径和应用程序来使用文件;操作系统则需要进一步记录文件的属性、外存位置、访问权限和打开状态。
常见文件属性:
| 属性 | 含义 |
|---|---|
| 文件名 | 由用户创建,便于用户查找;同一目录下通常不允许重名 |
| 标识符 | 系统内部唯一标识,对用户不要求可读 |
| 类型 | 指明文件类型,如文本、图片、目录、链接文件等 |
| 位置 | 用户看到的是路径;操作系统还要记录文件在外存中的位置 |
| 大小 | 文件当前占用的数据量 |
| 时间 | 创建时间、上次修改时间等 |
| 所有者 | 文件所属用户或用户组 |
| 保护信息 | 访问权限、口令、加密信息等 |
文件系统要解决的问题
文件管理不是只保存“文件内容”。它至少要回答五类问题:
- 文件内部的数据如何组织。
- 文件之间如何组织成目录树。
- 用户和应用程序如何创建、打开、读写、关闭、删除文件。
- 文件数据如何落到外存的磁盘块上。
- 如何实现共享、保护、空闲空间管理和设备 I/O。
从路径名到磁盘块的过程通常会经过多层:
| 层次 | 主要任务 |
|---|---|
| 用户接口 | 处理 create、open、read、write、close、delete 等系统调用 |
| 文件目录系统 | 根据路径查找目录项、FCB 或 inode |
| 存取控制模块 | 检查用户是否有访问权限 |
| 逻辑文件系统与文件信息缓冲区 | 将记录号、文件偏移转换为逻辑地址 |
| 物理文件系统 | 将逻辑块号转换为物理块号 |
| 设备管理模块 | 进行磁盘调度、启动设备、完成块读写 |
| 辅助分配模块 | 分配和回收外存空闲块 |
其中,目录、FCB、inode 和分配结构属于文件系统自己的管理对象;真正启动磁盘读写时,会继续下沉到 磁盘调度与磁盘管理、I/O 控制方式 和设备驱动程序。也就是说,文件系统决定“读哪个文件块”,I/O 子系统负责“怎样让设备把这个块传到主存”。
文件的基本操作
文件操作看起来是在操作“文件内容”,但操作系统实际处理的是三类对象:
- 目录项、FCB 或 inode:记录文件名、属性、权限、外存位置等元数据。
- 打开文件表:记录本次打开的状态,例如读写指针、访问模式、打开计数。
- 文件数据块:真正保存文件内容的外存块。
因此,open 和 close 不是把整个文件搬入或搬出内存。它们主要建立或撤销访问文件所需的内存数据结构。真正把文件内容读入内存的是 read、缺页调入,或其他 I/O 过程。
创建文件
创建文件时,用户通常给出文件路径、文件名和初始属性。操作系统需要:
- 根据路径找到目标目录。
- 检查同一目录下是否已有同名文件。
- 在目录中建立目录项、FCB 或 inode。
- 记录文件名、类型、权限、时间、所有者等属性。
- 必要时为文件分配初始外存块;若创建的是空文件,也可以暂不分配数据块。
创建文件的核心是建立文件名到文件控制信息的映射。它不一定立即写入大量文件数据。
删除文件
删除文件时,用户通常给出路径和文件名。操作系统需要:
- 根据路径找到目标目录文件。
- 找到文件名对应的目录项。
- 检查删除权限。
- 根据目录项、FCB 或 inode 记录的物理位置回收文件占用的磁盘块。
- 删除目录项,并更新空闲空间管理结构。
删除文件通常表示文件系统不再把这些磁盘块视为该文件所有;它不等于立刻把磁盘上的每个字节清零。
打开文件
系统要求读写前先 open。打开文件时,操作系统会查找目录项、检查权限,并把文件相关信息复制到内存中的打开文件表。
打开后,系统返回一个编号,也就是文件描述符。之后 read 和 write 用文件描述符指明文件,不必每次重新用路径查目录。
打开文件并不是把文件数据全部读入内存。open 主要把目录项、FCB 或 inode 中的必要信息调入内存,并建立进程打开文件表和系统打开文件表。
| 结构 | 保存内容 |
|---|---|
| 进程打开文件表 | 文件描述符、访问模式、当前读写指针、指向系统打开文件表的指针 |
| 系统打开文件表 | 文件外存位置、文件属性、打开计数、指向 inode 或 FCB 的指针 |
若多个进程打开同一个文件,系统打开文件表中的同一项可以被多个进程表项引用。打开计数用来判断最后一次关闭何时发生。
读文件
read 通常需要指明:
- 文件描述符。
- 要读入的数据量。
- 数据放入内存的目标地址。
操作系统根据文件描述符找到进程打开文件表,再找到系统打开文件表和文件物理结构。随后根据当前读写指针计算逻辑块号,再把逻辑块号转换成物理块号,从外存读入用户指定的内存区域。
读完后,读写指针通常向后移动。若读到文件末尾,实际读入的数据量可能小于请求的数据量。
写文件
write 通常需要指明:
- 文件描述符。
- 要写出的数据量。
- 数据在内存中的来源地址。
操作系统从用户指定的内存区域取数据,写到当前读写指针对应的文件位置。若写入超过文件原有长度,文件可能需要扩展;这时文件系统还要分配新的磁盘块,并更新 FCB、inode、索引表、FAT 或其他分配结构。
写操作还会影响一致性问题。若数据先进入缓冲区,磁盘上的文件内容可能稍后才真正更新。
read 和 write 是显式文件 I/O。mmap 则把文件区间映射到进程虚拟地址空间,程序用普通内存访问触发文件页调入或写回。它仍然依赖文件描述符建立映射,但映射建立后,访问路径进入虚拟存储和缺页处理流程。
关闭文件
进程使用完文件后执行 close。操作系统会:
- 删除该进程打开文件表中的对应表项。
- 回收与本次打开相关的内存资源。
- 将系统打开文件表中的打开计数减 1。
- 若打开计数变为 0,释放系统打开文件表中的对应表项。
close 关闭的是本进程的这次打开关系。若其他进程仍打开同一个文件,文件不会因为当前进程执行 close 而不可用。
普通 open 本来就不会把整个文件读入内存,所以 close 也谈不上把整个文件搬回磁盘。
取决于文件系统、页缓存、缓冲区和同步策略。
| 时机 | 含义 |
|---|---|
| 后台回写 | 操作系统周期性把脏页或脏缓冲区写回外存 |
| 缓存压力 | 内存紧张或缓存需要回收时,脏页必须先写回才能释放 |
| 显式同步 | 程序调用 fsync(fd)、fdatasync(fd) 或 sync(),要求同步数据或元数据 |
| 解除映射 | 对 mmap 的共享映射,msync()、后台回写、munmap() 或进程退出都可能与脏页写回有关 |
| 卸载文件系统 | 卸载前需要把尚未落盘的脏数据和元数据处理完 |
写回不只包括文件内容。文件大小、时间戳、目录项、inode、位示图、FAT、索引结构等元数据也可能被缓存并延迟写回。
文件共享让多个目录项、用户或进程访问同一份文件数据。常见方式包括基于 inode 的硬链接,以及保存目标路径的软链接。
硬链接通常用于普通文件,不允许普通用户随意给目录文件建立硬链接。若目录可以任意硬链接,目录结构可能出现环,路径遍历、递归删除、备份和引用计数回收都会变得不可控。目录中的 . 和 .. 是由文件系统维护的特殊目录项,不是普通用户创建的目录硬链接。
文件保护决定谁能读、写、执行或删除文件。常见方式包括口令保护、加密保护和访问控制表。更具体的共享、保护机制见 文件共享 与 文件保护。
文件系统的全局结构
磁盘成为可用文件系统通常要经历:
- 物理格式化:划分扇区,检测坏扇区,并用备用扇区替换坏扇区。
- 磁盘分区:把物理磁盘划分为一个或多个分区,也称文件卷、逻辑卷或逻辑盘。
- 逻辑格式化:在分区上创建文件系统,初始化目录区、文件区和空闲空间管理结构。
文件系统在外存中通常包括:
| 区域 | 作用 |
|---|---|
| 主引导记录 MBR | 与系统启动有关 |
| 引导块 | 保存启动相关信息 |
| 超级块或卷控制块 | 保存文件系统总体信息 |
| 目录结构 | 保存目录和目录项 |
| 文件控制结构 | 保存 FCB、inode 等元数据 |
| 数据区 | 保存文件数据块 |
| 空闲空间管理结构 | 记录哪些磁盘块空闲 |
文件系统在内存中会缓存近期使用的信息,例如目录文件、打开文件表、系统打开文件表、挂载表和文件系统控制块。目录缓存可以减少重复目录检索造成的磁盘 I/O。
用户进程执行 read(fd, ...) 时,fd 先作为索引号查本进程的打开文件表;进程打开文件表再指向系统范围的打开文件表;系统打开文件表保存或指向文件控制信息,并据此定位外存中的数据块。
VFS 与挂载
不同文件系统的内部数据结构不同,但用户进程希望使用统一接口,例如 open、read、write。
VFS 是虚拟文件系统。它向上提供统一系统调用接口,向下要求具体文件系统实现规定的函数。一个新文件系统要接入操作系统,就要满足 VFS 的接口要求。
VFS 的关键作用:
- 屏蔽不同文件系统的实现差异。
- 要求下层文件系统提供函数地址,如
open、read、write。 - 文件打开后,在主存中建立统一的
vnode表示。
vnode 只存在于主存中,用于让 VFS 统一表示已打开文件。inode 既可以在外存中保存,也可以被调入主存。
- 在 VFS 中注册新文件系统,更新挂载表。
- 向 VFS 提供函数地址列表。
- 把文件系统挂到某个挂载点,也就是某个父目录下。