Linux 操作系统的一个重要特点是它支持许多不同类型的文件系统。Linux 中最普遍使用的文件系统是 Ext2,但 Linux 也能够支持 FAT、VFAT、FAT32、MINIX 等不同类型的文件系统。
磁盘在经过分区之后,单个的物理磁盘就被划分为多个逻辑分区,每个分区上可存在一个文件系统。我们知道,块设备实际是可以包含文件系统的设备,不管块设备的具体构造如何,Linux 文件系统均将它们当作线性块的集合而访问。每个块设备驱动程序的任务,就是将逻辑块的请求转化为对应块设备可以理解的命令。类似地,对文件系统来说,不管基础的块设备是什么,它都能够进行一致的操作。利用 Linux 的文件系统,用户根本不用关心由不同硬件控制器控制的,处于不同物理介质上的不同文件系统之间的区别。如果将网络服务器的磁盘或分区挂装到本地文件系统上,用户可以和操作本地文件系统一样操作它们。
文件系统中的文件实际是数据的集合。文件系统不仅包含了这些文件中的数据,而且还包含文件系统的结构、符号链接以及文件的安全保护等信息。
MINIX 是 Linux 最初使用的文件系统,但是由于 MINIX 文件系统实际是教学用操作系统 MINIX 的文件系统,因此其功能尚不完备,并且性能欠佳。因此,1992 年 4 月份,专为 Linux 的设计的第一个文件系统 EXT(Extended File System)应运而生,但是,EXT 文件系统仍然有些粗糙,而且性能也同样是个问题。1993 年,在许多人的努力下,EXT 2 (Second Extended File System)文件系统作为 EXT 文件系统的兄弟诞生了。EXT 2 完全具备了高级文件系统的特点,性能也非常好。
在 EXT 文件系统的开发过程中,引入了一个非常重要的概念,即虚拟文件系统 (VFS)。VFS 作为实际文件系统(EXT)和操作系统之间的接口,将实际的文件系统和操作系统隔离开来。在 VFS 的帮助下,Linux 可以支持除 EXT 之外的许多文件系统类型。各文件系统为 VFS 提供一致的接口,从而将不同文件系统的细节隐藏了起来。对操作系统的其他部分,以及运行在操作系统之上的程序而言,所有的文件系统都是一样的。
对 VFS 来说,它一方面要保证快速访问实际文件系统中的数据,一方面还要保证文件和数据能够正确保存。这两个方面实际是互相矛盾的,Linux 通过高速缓存协调这两个需求。在高速缓存中,Linux 不仅缓存数据,而且还管理着操作系统和块设备之间的接口。
本章主要讲述 Linux 的 Ext2 文件系统、虚拟文件系统以及缓冲区高速缓存。
13.1 Ext2 文件系统
Ext2 作为 Linux 可扩展的、功能强大的文件系统而设计,其发明者为 Remy Card。在 Linux 中,Ext2 是迄今为止最为成功的文件系统,而且作为所有 Linux 发行版本的基本文件系统。
和其他文件系统一样,Ext2 文件系统中的文件保存在数据块中。对同一个 Ext2 文件系统而言,其上所有的数据块大小都是一样的。和某些文件系统不同的是,不同的 Ext2 文件系统的数据块大小可以不一样,数据块大小实际在建立 Ext2 文件系统时指定,并且作为文件系统的基本参数保存在文件系统中。单个文件所占用的空间按数据块为单位分配,因此,如果某个文件的大小为 1025 字节,而数据块的大小为 1024 字节,则该文件将占用两个数据块。平均而言,每个文件会浪费半个数据块的空间。但是,鱼和熊掌不可兼得,我们为了提高文件系统的访问速度,为了文件系统便于管理,而不得不浪费一些磁盘空间。
块设备上的大多数数据块用来保存文件的实际数据,而部分数据块则用来定义文件系统的结构。Ext2 文件系统利用索引节点(inode)来描述文件系统的拓扑结构。在单个文件系统中,每个文件对应一个索引节点,而每个索引节点有一个唯一的整数标识符。文件系统中所有文件的索引节点保存在索引节点表中。Ext2 文件系统中的目录实际是一种特殊文件,它们也有对应的索引节点,索引节点指向的数据块中包含在该目录中所有的目录项(文件、目录、符号链接等),每个目录项对应自己的索引节点。
7d6671d62b6bdc1206088b24.jpg
                   图 13-1 Ext2 文件系统的物理布局
如图 13-1 所示,EXT 2 文件系统分布在块结构的设备上。文件系统不必了解数据块的物理存储位置,它保存的是逻辑块的编号。实际上,由块设备驱动程序完成逻辑块编号到物理存储位置的转换。Ext2 文件系统将逻辑分区划分为块组(Block Group)。每个块组重复保存着对文件系统的完整性非常关键的信息,而同时也用来保存实际的文件和目录数据。文件系统关键信息的重复存储有助于文件系统在发生故障时还原。
13.1.1 Ext2 索引节点
在 Ext2 文件系统中,索引节点是基本的数据块,每个文件和目录有且只有一个索引节点与之对应。每个块组中的索引节点保存在索引节点表中,同时也保存有跟踪节点分配情况的位图。图 13-2 是 Ext2 索引节点的结构图,其中的各个数据域在表 13-1 中描述。
4ea820881128c5a9a5c27227.jpg
                                  图 13-2 Ext2 索引节点

         表 13-1 Ext2 索引节点数据域的描述
47bdb9c2a946c708e4dd3b34.jpg
这里需要对上表中的数据块信息作进一步解释。前 12 个数据块指针直接指向包含文件数据的数据块,而其后的三个数据块则是间接指针。这样的安排是为了适应文件的大小变化。假如文件系统可保存的最大文件为4MB,而每个数据块的大小为 1024 字节。如果索引节点全部利用直接数据块指针的话,则需要 4096 个数据块指针,而实际上,文件系统中大部分的文件不可能有如此之大。如果要保存 1 MB 的文件,实际只需 1024 个数据块指针,因此大量的数据块指针被浪费了。
为了解决上述问题,多数利用索引节点的文件系统利用多级数据块指针。参见图 13-2,假设数据块大小为 1024 字节。利用前 12 个直接指针,可保存最大为 12 KB 的文件,对这些文件的访问将非常迅速。如果文件大小超过了 12 KB,则利用单级间接指针,这一指针指向的数据块保存有一组数据块指针,这些指针依次指向包含实际数据的数据块。如果每个数据块指针占用 4 个字节,则每个单级间接指针数据块可包含 1024 / 4 = 256 个数据块指针,因此,利用直接指针和单级间接指针可保存 12 * 1024 + 256 * 1024 = 268 KB 的文件。当文件超过 268 K 字节时,再利用二级间接指针。类似地,可计算利用直接指针、单级间接指针和二级间接指针时可保存的最大文件为:
12 * 1024 + 256 * 1024 + 256 * 256 * 1024 = 65804 KB
约为 65 MB 大小。依次类推,得出利用三级间接指针时,可保存的最大文件为16842764 KB 即 16 GB。
13.1.2 Ext2 文件系统的超块
文件系统的超块(Superblock)包含了文件系统的基本大小和形式。其中包含的数据由文件系统管理程序用来进行文件系统的维护。每个块组中包含有相同的超块信息,但通常只需读取块组 0 的超块。超块中包含的信息在表 13-2 中列出。
             表 13-2 Ext2 超块中的信息
6aedccf369b6ab6d342acc39.jpg
13.1.3 Ext2 块组描述符
每个块组中包含有一个数据结构描述该块组。和超块类似,所有块组的块组描述符重复出现在所有的块组中。每个块组描述符包含的信息如表 13-3 所示。
                      表 13-3 块组描述符
d219edd5615a9ff950da4b04.jpg
分配位图在分配或释放数据块、索引节点时使用。
块组描述符保存在每个块组超块的后面,每个块组中包含所有块组的描述符。块组 0 中包含的超块信息和块组描述符是实际使用的数据,由于文件系统被破坏而需要还原时使用其他块组中重复的超块和块组描述符。
13.1.4 Ext2 目录
967d00038fb4eb4a3812bb02.jpg
            图 13-3 目录项的布局
如图 13-3 所示是内存中的目录项布局。在 Ext2 文件系统中,目录实际是特殊文件。目录文件中包含了目录项的列表,而每个目录项包含的信息如表 13-4 所示。
                 表 13-4 目录项信息
6ff64651f927c425377abe0e.jpg
每个目录的前两个目录项始终是标准的“.”和“..”,分别代表“本目录”和“父目录”。
当用户需要打开某个文件时,首先要指定文件的路径和名称,根据路径和名称,文件系统可搜索到文件的对应索引节点,从而可读取文件中的数据。例如,假定要搜索文件 /home/WeiYM/mbox,在 Ext2 中,该文件的定位过程如下(参照图 13-4):
1. 文件系统按照超块中根目录的索引节点,以及索引节点中的数据块编号信息,可找到根目录的目录项列表。
2. 在目录项列表中搜索名称为 home 的目录项,得到对应索引节点编号为 6。
3. 按照索引节点 6 中的数据块编号,可找到 /home 目录的目录项。
4. 在 /home 目录项列表中找出目录项 WeiYM,得到对应索引节点编号为 26。
5. 按照索引节点 26 中的数据块编号,可找到 /home/WeiYM 目录的目录项。
6. 从 /home/WeiYM 目录的目录项列表中可以获得文件 mbox 的索引节点。
获得目标文件的索引节点之后,随后就可以利用该索引节点访问文件中的数据。
13.1.5 Ext2 文件系统中数据块的分配和释放
我们知道,在频繁的文件创建和删除过程中,极容易使文件系统碎片化。过分碎片化的文件系统中,逻辑上连续的数据块在物理上处于不连续的位置,从而导致文件的存取速度减慢,最终影响系统性能。Ext2 文件系统通过为文件分配物理上相近的数据块,或者至少让数据块保持在同一块组而避免文件系统的过分碎片化。只有在不得已时,Ext2 文件系统才为单个文件在不同的块组中分配数据块。
60d3473596b9c0b6a71e120b.jpg
                                图 13-4 文件的定位过程
当某个进程试图在文件中写入数据时,Linux 文件系统首先检查要写入的数据是否已超过文件的末尾,如果已超过,则文件系统要分配新的数据块。在文件系统为该文件分配数据块并将数据写入数据块之前,进程处于等待状态,完成实际的数据写入之后,进程才能继续运行。因为数据块的分配或释放最终要修改超块中的信息,因此,进程在分配数据块之前,首先要锁定超块,这样,其他要写数据的进程也必须等待当前写数据的进程结束写过程。对超块的访问依照“先到先服务”的排队原则。
锁定超块之后,进程首先检查是否有足够的空闲数据块,如果没有足够的空闲块,则数据的写入就要失败,进程最终会放弃对文件系统超块的锁定控制。
如果文件系统中有足够的空闲空间,则系统尝试分配新的数据块。如果 Ext2 文件系统具备数据块的预分配功能,则只需从预分配块中获取一个数据块。预分配块实际是在已分配数据块位图中预先保留的数据块。代表要分配数据块的文件的 VFS 索引节点中包含两个专用于 Ext2 文件系统的数据域:prealloc_block 和 prealloc_count。这两个数据域分别代表第一个预分配数据块的编号和预分配数据块的数目。通过指定文件的预分配信息,可加速文件的数据块分配,并可保证在一定的文件大小范围内,保持文件数据块的连续性。如果没有预分配的数据块,或者预分配功能被关闭,则 Ext2 文件系统必须分配新的数据块。Ext2 文件系统首先查看和文件最后一个数据块相邻的数据块是否空闲。逻辑上讲,这个数据块是最理想的,因为连续的数据块可保证最高的存取效率。如果相邻数据块已被占用,则搜索范围加宽到和理想块相邻的 64 个块范围之内。如果能够在相邻连续的 64 个块中找到空闲数据块,虽然该数据块并不是最理想的,但至少比较接近,且处于同一块组中。
如果和理想块相邻连续的 64 个块中没有空闲块,则文件系统在其他块组中寻找空闲块。块分配代码首先以 8 个块为单位(1 簇)分配空闲块,如果没有空闲簇,则寻找更小的簇。上述过程持续进行,直到分配到数据块为止。如果数据块的预分配被打开,则还需要更新prealloc_block 和 prealloc_count 数据域。
不管在哪里分配到了数据块,块分配代码需要更新新块所在块组的块位图,并在缓冲区缓存中分配一个数据缓冲区。数据缓冲区由文件系统的支持设备标识符和块编号唯一标识。缓冲区中的数据被清零,而缓冲区被标志为“脏”,表明其中的数据尚未写入物理磁盘。最后,超块本身也被标志为“脏”,表明已被改动,最后解锁超块。
如果其他进程正在等待该超块解锁,则等待队列中的第一个进程可开始运行,并获得对超块的互斥访问。进程数据写入新的数据块中,如果尚有更多的数据写入,则整个分配过程再次开始。
数据块的释放过程相对直接,但仍然需要获得对超块的互斥访问。