在 Linux 中,象 Unix 一样,系统可以使用的不同的文件系统不是通过设备标识符(例如驱动器编号或设备名称)访问,而是连接成一个单一的树型的结构,用一个统一的单个实体表示文件系统。 Linux 在文件系统安装的时候把它加到这个单一的文件系统树上。所有的文件系统,不管什么类型,都安装在一个目录,安装的文件系统的文件掩盖了这个目录原来存在的内容。这个目录叫做安装目录或安装点。当这个文件系统卸载的时候,安装目录自己的文件又可以显现出来。
当磁盘初始化的时候(比如用 fdisk ),利用一个分区结构把物理磁盘划分成一组逻辑分区。每一个分区可以放一个文件系统,例如一个 EXT2 文件系统。文件系统在物理设备的块上通过目录、软链接等把文件组织成逻辑的树型结构。可以包括文件系统的设备是块设备。系统中的第一个 IDE 磁盘驱动器的第一个分区, IDE 磁盘分区 /dev/hda1 ,是一个块设备。 Linux 文件系统把这些块设备看成简单的线性的块的组合,不知道也不去关心底层的物理磁盘的尺寸。把对设备的特定的块的读的请求映射到对于设备有意义的术语:这个块保存在硬盘上的磁道、扇区和柱面,这是每一个块设备驱动程序的任务。一个文件系统不管它保存在什么设备上,都应该用同样的方式工作,有同样的观感。另外,使用 Linux 的文件系统,是否这些不同的文件系统在不同的硬件控制器的控制下的不同的物理介质上都是无关紧要的(至少对于系统用户是这样)。文件系统甚至可能不在本地系统上,它可能是通过网络连接远程安装的。考虑以下的例子,一个 Linux 系统的根文件系统在一个 SCSI 磁盘上。
A E boot etc lib opt tmp usr
C F cdrom fd proc root var sbin
D bin dev home mnt lost+found
不管是操作这些文件的用户还是程序都不需要知道 /C 实际上是在系统的第一个 IDE 磁盘上的一个安装的 VFAT 文件系统。本例中(实际是我家中的 Linux 系统), /E 是次 IDE 控制器上的 master IDE 磁盘。第一个 IDE 控制器是 PCI 控制器,而第二个是 ISA 控制器,也控制着 IDE CDROM ,这些也都无关紧要。我可以用一个 modem 和 PPP 网络协议拨号到我工作的网络,这时,我可以远程安装我的 Alpha AXP Linux 系统的文件系统到 /mnt/remote 。
文件系统中的文件包含了数据的集合:包含本章源的文件是一个 ASCII 文件,叫做 filesystems.tex 。一个文件系统不仅保存它包括的文件的数据,也保存文件系统的结构。它保存了 Linux 用户和进程看到的所有的信息,例如文件、目录、软链接、文件保护信息等等。另外,它必须安全地保存这些信息,操作系统的基本的一致性依赖于它的文件系统。没有人可以使用一个随机丢失数据和文件的操作系统(不知道是否有,虽然我曾经被拥有的律师比 Linux 开发者还多的操作系统伤害过)。
Minix 是 Linux 的第一个文件系统,有相当的局限,性能比较差。它的文件名不能长于 14 个字符(这仍然比 8.3 文件名要好),最大的文集大小是 64M 字节。第一眼看去, 64M 字节好像足够大,但是设置中等的数据库需要更大的文件大小。第一个专为 Linux 设计的文件系统,扩展文件系统或 EXT ( Extend File System ),在 1992 年 4 月引入,解决了许多问题,但是仍然感到性能低。所以, 1993 年,增加了扩展文件系统第二版,或 EXT2 。这种文件系统在本章稍后详细描述。
当 EXT 文件系统增加到 Linux 的时候进行了一个重要的开发。真实的文件系统通过一个接口层从操作系统和系统服务中分离出来,这个接口叫做虚拟文件系统或 VFS 。 VFS 允许 Linux 支持许多(通常是不同的)文件系统,每一个都向 VFS 表现一个通用的软件接口。 Linux 文件系统的所有细节都通过软件进行转换,所以所有的文件系统对于 Linux 核心的其余部分和系统中运行的程序显得一样。 Linux 的虚拟文件系统层允许你同时透明地安装许多不同的文件系统。
Linux 虚拟文件系统的实现使得对于它的文件的访问尽可能的快速和有效。它也必须保证文件和文件数据正确地存放。这两个要求相互可能不平等。 Linux VFS 在安装和使用每一个文件系统的时候都在内存中高速缓存信息。在文件和目录创建、写和删除的时候这些高速缓存的数据被改动,必须非常小心才能正确地更新文件系统。如果你能看到运行的核心中的文件系统的数据结构,你就能够看到文件系统读写数据块,描述正在访问的文件和目录的数据结构会被创建和破坏,同时设备驱动程序会不停地运转,获取和保存数据。这些高速缓存中最重要的是 Buffer Cache ,在文件系统访问它们底层的块设备的时候结合进来。当块被访问的时候它们被放到 Buffer Cache ,根据它们的状态放在不同的队列中。 Buffer Cache 不仅缓存数据缓冲区,它也帮助管理块设备驱动程序的异步接口。
9.1 The Second Extended File System (EXT2)
EXT2 被发明( Remy Card )作为 Linux 一个可扩展和强大的文件系统。它至少在 Linux 社区中是最成功的文件系统,是所有当前的 Linux 发布版的基础。 EXT2 文件系统,象所有多数文件系统一样,建立在文件的数据存放在数据块中的前提下。这些数据块都是相同长度,虽然不同的 EXT2 文件系统的块长度可以不同,但是对于一个特定的 EXT2 文件系统,它的块长度在创建的时候就确定了(使用 mke2fs )。每一个文件的长度都按照块取整。如果块大小是 1024 字节,一个 1025 字节的文件会占用两个 1024 字节的块。不幸的是这一意味着平均你每一个文件浪费半个块。通常计算中你会用磁盘利用来交换 CPU 对于内存的使用,这种情况下, Linux 象大多数操作系统一样,为了较少 CPU 的负载,使用相对低效率的磁盘利用率来交换。不是文件系统中所有的块都包含数据,一些块必须用于放置描述文件系统结构的信息。 EXT2 用一个 inode 数据结构描述系统中的每一个文件,定义了系统的拓扑结构。一个 inode 描述了一个文件中的数据占用了哪些块以及文件的访问权限、文件的修改时间和文件的类型。 EXT2 文件系统中的每一个文件都用一个 inode 描述,而每一个 inode 都用一个独一无二的数字标识。文件系统的 inode 都放在一起,在 inode 表中。 EXT2 的目录是简单的特殊文件(它们也使用 inode 描述),包括它们目录条目的 inode 的指针。
图 9.1 显示了一个 EXT2 文件系统占用了一个块结构的设备上一系列的块。只要提到文件系统,块设备都可以看作一系列能够读写的块。文件系统不需要关心自身要放在物理介质的哪一个块上,这是设备驱动程序的工作。当一个文件系统需要从包括它的块设备上读取信息或数据的时候,它请求对它支撑的设备驱动程序读取整数数目的块。 EXT2 文件系统把它占用的逻辑分区划分成块组( Block Group )。每一个组除了当作信息和数据块来存放真实的文件和目录之外,还复制对于文件系统一致性至关重要的信息。这种复制的信息对于发生灾难,文件系统需要恢复的时候是必要的。下面对于每一个块组的内容进行了详细的描述。
9.1.1 The EXT2 Inode ( EXT2 I 节点)
在 EXT2 文件系统中, I 节点是建设的基石:文件系统中的每一个文件和目录都用一个且只用一个 inode 描述。每一个块组的 EXT2 的 inode 都放在 inode 表中,还有一个位图,让系统跟踪分配和未分配的 I 节点。图 9.2 显示了一个 EXT2 inode 的格式,在其他信息中,它包括一些域:
Magic Number 允许安装软件检查这是否是一个 EXT2 文件系统的超级块。对于当前版本的 EXT2 是 0xEF53 。
Revision Level major 和 minor 修订级别允许安装代码确定这个文件系统是否支持只有在这种文件系统特定修订下才有的特性。这也是特性兼容域,帮助安装代码确定哪些新的特征可以安全地使用在这个文件系统上。
Mount Count and Maximum Mount Count 这些一起允许系统确定这个文件系统是否需要完全检查。每一次文件系统安装的时候 mount count 增加,当它等于 maximum mount count 的时候,会显示告警信息“ maximal mount count reached , running e2fsck is recommended ”。
无论何时一个进程试图象一个文件写入数据, Linux 文件系统检查数据是否会超出文件最后分配块的结尾。如果是,它必须为这个文件分配一个新的数据块。直到这个分配完成,该进程无法运行,它必须等待文件系统分配新的数据块并把剩下的数据写入,然后才能继续。 EXT2 块分配例程所要做的第一个事情是锁定这个文件系统的 EXT2 超级块。分配和释放块需要改变超级块中的域, Linux 文件系统不能允许多于一个进程同一时间都进行改变。如果另一个进程需要分配更多的数据块,它必须等待,直到这个进程完成。等待超级块的进程被挂起,不能运行,直到超级块的控制权被它的当前用户释放。对于超级块的访问的授权基于一个先来先服务的基础( first come first serve ),一旦一个进程拥有了超级块的控制,它一直维持控制权到它完成。锁定超级块之后,进程检查文件系统是否有足够的空闲块。如果没有足够的空闲块,分配更多的尝试会失败,进程交出这个文件系统超级块的控制权。