4.3文件系统
在学习本节时,请读者思考以下问题:
- 什么是文件系统?
- 文件系统要完成哪些功能?
本节除“外存空闲空间管理”外,其余内容均为2022年统考大纲新增考点。这些内容较为 抽象,属于“看不见、摸不着”的底层原理,在常见的国内《操作系统》教材中鲜有涉及。若读者感到理解困难,建议结合王道最新课程进行学习,以更好地掌握这些抽象机制。
4.3.1文件系统布局
1.文件系统在磁盘中的结构
文件系统存放在磁盘上。磁盘通常被划分为一个或多个分区,每个分区中有一个独立的文件系统。文件系统一般包含以下关键信息:引导操作系统的机制、磁盘总块数、空闲块的数量与位置、目录结构,以及所有具体文件的数据。
其主要组成部分简述如下:
主引导记录(Master Boot Record,MBR),位于磁盘的0号扇区,负责引导计算机。MBR后紧随一个分区表,用于记录各分区的起始与结束地址,并标记其中一个为活动分区。系统加电后,BIOS将MBR读入内存并执行;MBR首先识别活动分区,随后读取该分区的第一个扇区,即引导块。引导块(boot block),MBR将控制权移交至引导块中的程序,由其负责加载并启动该分区中的操作系统。注意,每个分区均以引导块开头,即使当前未安装可启动的操作系统,也保留此区域,以便未来部署系统。在Windows中,该区域称为分区引导扇区。除引导块外,磁盘分区的后续布局因文件系统类型而异。超级块(super block),存储文件系统的所有关键信息。当系统启动或首次挂载该文件系统时,超级块会被读入内存。超级块中的典型内容包括:分区的总块数、块大小、空闲块数量及其指针、空闲i节点(inode)数量及指针等。空闲块信息,记录未分配的磁盘块,通常以位示图或空闲链表实现。紧随其后的是一组i节点,每个文件对应一个i节点,其中完整描述了文件的各种属性。之后是根目录,作为整个目录树的起点。磁盘的其余空间则用于存放所有其他文件和目录的实际数据。
2.文件系统在内存中的结构
为高效管理文件系统并提升性能,系统在内存中维护一组数据结构。这些信息在文件系统挂载时加载,在运行期间动态更新,并在卸载时释放。典型的内存结构包括:
- 内存中的
挂载表(mount table),记录每个已挂载文件系统分区的有关信息,如设备标识、挂载点、文件系统类型及挂载选项等。 - 内存中的
目录项的缓存,缓存最近访问过的目录内容,以减少对磁盘的重复读取,加快路径解析速度。 系统级的打开文件表,为每个当前打开的文件维护一个FCB的副本,包含文件状态、访问权限、读/写指针、打开计数等全局信息。进程级的打开文件表,每个进程拥有独立的打开文件表,包含该进程所使用的文件描述符及指向系统级打开文件表中对应表项的指针,从而实现进程与全局资源的关联。
4.3.2文件存储空间管理
如4.2.6节所述,文件的物理结构包含两个方面:文件的分配方式与文件存储空间管理。后者专门负责记录磁盘中的哪些块是空闲的,并提供对这些块进行分配与回收的机制。由于系统以固定大小的物理块为单位进行数据交换,存储空间管理的核心任务是高效组织和管理空闲块。为此,系统需要维护描述空闲块状态的数据结构,而这正是本节要介绍的几种管理方法所要解决的问题。
1.空闲表法
空闲表法属于连续分配方式,其思想源于内存的动态分区分配:为每个文件分配一块物理上连续的磁盘区域。系统为外存中的所有空闲区维护一张空闲表,每个空闲区对应一个表项,包含序号、起始盘块号和空闲盘块数等信息,并按起始盘块号递增顺序排列,如表4.2所示。
表4.2 空闲盘块表
| 序号 | 第一个空闲盘块号 | 空闲盘块数 |
|---|---|---|
| 1 | 2 | 4 |
| 2 | 9 | 3 |
| 3 | 15 | 5 |
| 4 | — | — |
盘块的分配:
空闲盘区的分配策略与内存动态分区分配一致,通常采用首次适应(First Fit)、最佳适应(Best Fit)等算法。当系统为新创建的文件分配空间时,会顺序扫描空闲表,查找第一个大小满足需求的空闲区;一旦找到,便将其分配给用户,并立即更新空闲表。
盘块的回收:
回收用户释放的空间时,也借鉴内存回收机制:需要判断回收区是否与空闲表中相邻的前区或后区在物理上连续。若相邻,则将它们合并为一个更大的空闲区,以减少碎片。
空闲表法的优点:具有较高的分配效率,能够显著降低磁盘I/O次数。尤其对于小型文件(如占用1~5个盘块),连续分配不仅实现简单,还能充分发挥磁盘顺序读/写的性能优势。
2. 空闲链表法
空闲链表法将所有空闲盘区组织成一条链表,根据链表节点的不同,可分为以下两类:
(1) 空闲盘块链
空闲盘块链以单个盘块为单位,将所有空闲盘块链接成一条链,每个盘块包含一个指向下一个空闲盘块的指针。分配时:从链首依次摘下所需数量的盘块分配给用户。回收时:将释放的盘块逐个插入链尾。
空闲盘块链的优点:单个盘块的分配与回收操作极为简单。缺点:为一个文件分配多个盘块需要多次访问链表,效率较低;且由于管理粒度细,空闲盘块链通常较长,管理开销大。
(2) 空闲盘区链
空闲盘区链以空闲盘区(一组连续的盘块)为单位构建链表。每个盘区包含两项信息:本盘区的盘块数和指向下一个空闲盘区的指针。分配时:采用与内存动态分区分配类似的策略(如首次适应算法),查找满足需求的空闲盘区进行分配。回收时:需要判断回收区是否与链表中相邻的空闲盘区在物理上连续,若相邻则合并,从而有效减少外部碎片。
空闲盘区链的优缺点与空闲盘块链恰好相反,优点:分配与回收的效率较高,且空闲盘区链的长度较短。缺点:分配与回收的过程较为复杂,需要处理盘区的分割与合并操作。
3. 位示图法
位示图法利用二进制的一位来表示磁盘中一个盘块的使用状态:磁盘上的每个盘块均对应位示图中的一个比特位。当该位为“0”时,表示对应盘块空闲;为“1”时,表示已被分配。因此,一个由 位组成的位示图,恰好可描述 个盘块的使用情况。
盘块的分配:
- 顺序扫描位示图,查找一个(或一组)值为“0”的位。
- 将找到的一个(或一组)比特位转换为对应的盘块号。假设该位位于位示图中的第 行、第 列(行、列编号从1开始),每行包含 位,则对应盘块号 按下式计算:
- 修改位示图,置 。
盘块的回收:
- 将回收盘块的盘块号 转换为位示图中的行号和列号。计算公式为
- 修改位示图,置 。
NOTE本书中位示图的行和列均从1开始编号。若题目指定从0开始,则上述公式需要相应调整。
位示图法的优点:查找高效,能快速在位示图中找到一个或一组相邻的空闲盘块,便于实现连续分配;位示图结构紧凑,占用内存空间极小,可常驻内存,避免频繁访问磁盘。缺点:位示图大小会随着磁盘容量的增加而增大,因此更适用于小型或中等规模的系统。
4. 成组链接法
空闲表法和空闲链表法均不适用于大型文件系统,因其管理结构随磁盘容量增大而过度膨胀。为此,UNIX系统采用成组链接法,它融合了二者的思想,有效控制了管理结构的规模。
成组链接法的思想:将所有空闲盘块划分为若干组(例如每组100个盘块)。除最后一组外,每组的第一个盘块用作索引块,用于记录下一组中所有空闲盘块的块号及其数量;这些索引块依次链接,形成一条链。文件系统挂载时,第一组的空闲盘块信息(包括块号列表和数量)被预先加载到内存的一个专用栈结构,称为空闲盘块号栈。例如,若系统空闲区为第 号盘块,则分组如下:第一组为 号……次末组为 号,最末组为 号(共99个盘块)。最末组的块号由前一组的最后一个盘块(7900号)记录,而该盘块中存放的第一个盘块号为“0”,用作整条空闲链的结束标志。
简而言之,成组链接法通过“分组+索引+链接”的设计,既避免了长链表的遍历开销,又无须维护完整的空闲表,从而在可扩展性与性能之间取得了良好平衡。
盘块的分配:
从空闲盘块号栈顶取出一个盘块号并分配给用户,栈指针相应上移,栈中空闲盘块数减1。若此时栈指针到达栈底(表示当前组已分配完毕),则需要读取该栈底盘块号所对应的物理盘块内容——其中保存的是下一组的空闲盘块号列表。此时,先将该内容整体读入栈以更新空闲盘块号栈,再将原栈底所指的盘块(其索引作用已完成)分配出去。
例如, 系统先依次分配 号盘块;当需要分配300号盘块时,首先将其内容(下一组的块号列表)读入空闲盘块号栈,随后将300号盘块本身分配给用户。
盘块的回收:
系统将释放的盘块号压入空闲盘块号栈顶部,栈指针相应下移,栈中空闲盘块数加1。若此时栈已满(如达到100项),则需要将当前栈中的全部盘块号写入该新回收的盘块,使其成为新的索引块;随后,将该盘块号设为新的栈底,并将栈中空闲盘块数重置为1。
描述空闲空间的数据结构(如空闲盘块号栈)以及文件系统的关键信息,通常存储在磁盘的固定位置。在UNIX系统中,这一区域称为超级块。文件系统挂载时会将其读入内存,并在运行过程中持续维护内存副本与磁盘副本的一致性,从而确保空闲空间管理的正确性与可靠性。
4.3.3 虚拟文件系统
现代操作系统通常需要支持多种文件系统,如 ext4、NTFS、FAT等。这些文件系统在磁盘组织方式、元数据格式和操作方法上各不相同。若应用程序为每种文件系统分别编写代码,则不仅开发困难,系统的可维护性也会显著降低。为此,操作系统引入了虚拟文件系统(VFS);它位于用户程序与具体文件系统之间,向上提供统一的文件操作接口。有了VFS,用户程序就无须关心底层使用的是哪种文件系统。无论文件存储在何种设备上、采用何种格式,程序只需调用标准系统调用[如 open()、write() 等]即可完成操作。VFS则负责将这些通用请求转发给相应的文件系统进行处理,进而让用户无须察觉底层文件系统的差异。
VFS采用面向对象的设计思想,抽象出一个通用的文件系统模型,并将其中的公共特性封装为四种对象。每个对象都包含数据成员和一组操作函数指针,这些函数由具体的文件系统提供。只要新的文件系统实现了VFS定义的接口,就能被系统识别、挂载并使用。这四种对象如下。
(1) 超级块对象
超级块对象表示一个已挂载的文件系统。它对应磁盘上的超级块,记录该文件系统的全局信息,如块大小、总块数、空闲块数量以及文件系统类型等。当文件系统挂载时,VFS将其超级块读入内存,创建对应的超级块对象,并设置好相关的操作函数,如分配inode、同步元数据等。
(2) V节点对象
V节点(vnode)对象是VFS中最重要的抽象对象,用于在内存中表示一个具体的文件或目录。每个 vnode对象关联底层某个具体文件系统的索引节点(如ext4的 inode),本质上是对各类文件系统元数据的统一封装:向上为系统调用提供统一的文件属性(如权限、大小、时间戳等),向下通过指针指向该文件系统私有的元数据结构。vnode 对象仅在文件首次被访问时动态创建于内存,引用计数归零后自动释放;其内部包含一组标准操作函数指针(如 read、write 等),VFS通过这些指针将上层请求分发至相应文件系统的具体实现,以屏蔽不同文件系统的差异。
NOTEV节点并不等同于磁盘上的 inode。V节点是VFS为统一管理各类文件而创建的内存对象,它与inode通过指针关联,协同完成文件操作:V节点提供统一接口,inode提供实际存储信息。
(3) 目录项对象
目录项对象表示路径中的一个组成部分,如 /home/user/file 中的“user”。目录项对象并不直接对应磁盘上的某个固定结构,而是VFS在解析路径时动态生成的内存缓存;每个目录项保存文件名、指向父目录项和子目录项的指针,以及指向对应V节点的指针;多个目录项组合形成逻辑上的目录树;VFS通过缓存常用目录项,有效加快后续路径查找的速度。
(4) 文件对象
文件对象表示一个进程所打开的文件,可理解为“文件在进程中的运行实例”,正如进程是程序的运行实例。当进程调用 open() 时,内核为其创建一个文件对象;当调用 close() 且引用计数归零时,该对象被销毁。文件对象保存当前读/写位置(文件指针)、访问模式(读/写或追加)和引用计数等信息,并包含指向对应V节点和目录项的指针;它提供的 read、write、seek 等操作接口最终由V节点转发至底层文件系统执行。
下面以 write() 为例说明VFS的工作过程:当进程调用 write(fd, buf, n) 时,内核首先进入VFS层,执行 sys_write() 函数;随后根据文件描述符 fd 定位到对应的文件对象,并通过该对象获取其关联的V节点;接着,VFS利用V节点中预置的写操作函数指针,调用目标文件系统的具体写方法;该文件系统将数据写入缓冲区,最终通过设备驱动完成磁盘写入。write() 系统调用操作。
总结: 对用户程序而言,所有文件操作均通过VFS提供的统一接口完成,无须关心底层使用的是ext4、NTFS还是其他文件系统。VFS借助前述四类对象,屏蔽不同文件系统的实现细节,使其在系统中呈现出一致的行为。需要强调的是,VFS本身并不是一种真正的文件系统——它仅存在于内存之中,不占用磁盘空间,随系统启动而建立,随系统关闭而释放。
4.3.4 文件系统挂载
如同文件在使用之前必须先打开一样,文件系统在被进程访问前也必须先安装,也称挂载(Mounting)。将某个设备上的文件系统挂载到目录树中的一个目录后,即可通过该目录访问该设备上的文件。此处的设备是指逻辑上的设备,如同一磁盘的不同分区可视为多个独立设备。
Windows系统采用驱动器号(如C:、D:)标识分区(也称卷),每个分区拥有独立的目录树结构,并关联一个已挂载的文件系统。文件路径通常表示为 drive-letter:\path\to\file。访问时,操作系统根据驱动器号定位对应的文件系统,再在其目录结构中查找目标文件。较新版本的Windows也支持将文件系统挂载到目录树下的任意目录(称为装入点),其行为与UNIX类似。系统启动时,Windows 会自动探测所有存储设备,并挂载其所识别的文件系统。
UNIX系统则以单一的根文件系统为基础,该文件系统在系统启动时由内核直接挂载,通常包含内核映像等关键文件。除根文件系统外,所有其他文件系统都必须挂载到根文件系统中的某个目录下才能被访问。这些文件系统可在系统初始化阶段自动挂载,也可由用户手动挂载。用于挂载的目录称为挂载点(mount point)。需要注意的是:同一个设备可以被挂载到多个不同的挂载点;但同一挂载点在同一时刻只能挂载一个设备。
例如,将位于磁盘 /dev/fd0 上的ext2文件系统,通过 mount 命令挂载到目录 /flp:
mount -t ext2 /dev/fd0 /flp若需要卸载该文件系统,则使用 umount 命令。
贯穿本章有两条主线:其一,引入一种新的抽象数据类型——文件,从逻辑结构(如流式、记录式)和物理结构(如连续、链接、索引)两个维度展开;其二,阐述操作系统如何管理文件,包括多文件的组织方式(目录结构)、用户请求的处理机制(如系统调用、VFS分发)以及底层存储的调度策略(磁盘管理)。仅了解宏观框架是远远不够的,其目的正是为了更好地掌握微观细节;读者应通过反复做题与思考,不断深化对知识点的理解与运用。
4.3.5 本节小结
本节开头提出的问题的参考答案如下。
-
什么是文件系统?
操作系统中负责管理和存储文件信息的软件机构称为
文件管理系统,简称文件系统。文件系统由三部分组成:与文件管理相关的软件、被管理的文件,以及实现文件管理所需要的数据结构。 -
文件系统要完成哪些功能?
对用户而言,文件系统最主要的功能是支持对文件的基本操作,使用户能够按名存取和查找文件,将其组织为合适的逻辑结构,并提供基本的文件共享与保护机制。对操作系统而言,文件系统还需要管理与磁盘之间的信息交换,完成文件逻辑结构到物理结构的映射,合理组织文件在磁盘上的存放,并采用高效的文件布局策略与磁盘调度方法,以提升系统整体性能。
4.4 本章疑难点
1. 文件的物理分配方式的比较
文件的三种物理分配方式的比较如表4.3所示。
表4.3 文件三种分配方式的比较
| 优点 | 缺点 | 访问第n条记录 | |
|---|---|---|---|
| 连续分配 | 顺序存取时速度快,文件定长时可根据文件起始地址及记录长度进行随机访问 | 要求连续的存储空间,会产生碎片,不利于文件的动态扩充 | 需要访问磁盘1次 |
| 链接分配 | 可解决外存的碎片问题,提高外存空间的利用率,动态增长较方便 | 只能按照文件的指针链顺序访问,查找效率低,指针信息存放消耗外存空间 | 需要访问磁盘n次 |
| 索引分配 | 可以随机访问,文件易于增删 | 索引表增加存储空间的开销,索引表的查找策略对文件系统效率影响较大 | m级需要访问磁盘m+1次 |
2. 文件打开的过程描述
- 检索目录,要求打开的文件应该是已经创建的文件,它应登记在文件目录中,否则会出错。在检索到指定文件后,就将其磁盘 inode 复制到活动 inode 表中。
- 将参数 mode 所给出的打开方式与活动 inode 中在创建文件时所记录的文件访问权限相比较,若合法,则此次打开操作成功。
- 当打开合法时,为文件分配用户打开文件表表项和系统打开文件表表项,并为后者设置初值,通过指针建立表项与活动 inode 之间的联系,再将文件描述符 fd 返回给调用者。
