当前位置:Linux教程 - Linux - LINUX核心 之 (第九章)

LINUX核心 之 (第九章)



         第 9 章 文件系统


    这章描述 Linux内核怎么在它支持的文件系统中维护文件,描述虚拟文件系统 ( VFS ) 讲述了Linux
    内核的真实文件系统是怎么被支持的。

    Linux 的最重要的特徵之一是它的为许多不同的文件系统的支持。这使其非常灵活从而与许多另外
    的操作系统可以很好的共存。 在本书写作的时候, Linux已支持 15 种文件系统; ext , ext2 ,
    xia , minix , umsdos ,msdos , vfat , proc , smb , ncp , iso9660 , sysv , hpfs , affs 及
    ufs , 并且没有疑问, 将来支持的文件类型将被增加的更多。

    在Linux中 ,因为它是Unix的一种,系统可以使用的不同文件系统, 不能向Windows或DOS一样通过设
    备标识符存取 ( 例如一个驱动器数字或一个驱动器命名 ), 而是它们被构建成为一个单一的层次树
    状结构以作为代表文件系统的实体。 Linux 通过安装一个文件系统将该新文件系统加入它的文件系
    统树中。所有的文件系统, 不管是什么类型,都安装在文件系统树的一个目录上并且该文件系统之上
    的文件将掩盖掉这个安装目录中原来存在的内容。这个目录称为安装目录或安装点。当文件系统被卸
    掉之后,安装目录中原来的文件才再次可见。

    当磁盘被初始化时 ( 使用 fdisk , 例如 ) 磁盘上存在着一个分区结构把物理的磁盘划分成很多逻辑
    的分区结构。每个分区可以拥有一个单个的文件系统, 例如一 EXT2 文件系统。文件系统通过在物理
    设备上的目录,软联接等等来组织文件以形成一个逻辑的层次结构。能包含文件系统的设备称为块设
    备。 IDE 磁盘分区 /dev/hda1 , 系统中的第一个IDE 磁盘驱动器分区, 是一个块设备。 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 是一个安装的 VFAT 文件系统位于系统的第一个
    IDE 磁盘上。在上述例子中, /E 是在第二 个IDE 控制器上的主IDE 磁盘。第一 IDE 控制器是否是一
    个PCI控制器,第二个控制器是否是一个ISA控制器(控制 IDE CDROM 的那个),在这里没有关系。我能拨
    号上网进入我工作的机器,使用一个调制解调器和 PPP协议。在这种情况中我能远程地装我的 Alpha
    AXP Linux系统的文件系统在上本地的/mnt/remote 目录上.
    (译者注:作者在这里解释了半天,其目的是让读者理解:文件系统(File System)是操作系统中抽象出来
    的一个概念。其具体的物理结构组织对于用户和用户进程是透明的,不用关心的。)

    一个文件系统的文件是数据的集合。一个文件系统不仅含有文件系统中的文件而且含有文件系统的结构。
    它包含Linux用户和进程所能看见的文件,目录联结,文件保护信息等等。 而且它必须安全地保持那个信
    息,操作系统的基本完整取决于它的文件系统。没人将使用随机丢失数据和文件的一个操作系统。

    Minix , Linux 的第一个文件系统有相当的局限性并且缺乏很好的性能。

    它的文件名不能比 14 个字符长 ( 它仍然比 8.3 文件名好一些 ) 并且最大的文件大小是 64MBytes 。
    64Mbytes 可能乍看之下似乎足够大但是大文件大小是必要的以用来保持数据库系统。第一个,具体地说,
    为Linux 设计的,文件系统, 扩充文件系统, 或 EXT , 在 1992 年 4 月被引入,其解决了很多问题但
    是仍然缺乏一个很好的性能。

    因此,在 1993 ,第二扩大文件系统, 或 EXT2 , 被增加到Linux文件系统中。

    这个文件系统将在本章被详细描述。

    当 EXT 文件系统被增加进 Linux 时,一个重要的关于文件系统的开发技术发生了。真实的文件系统通过
    一个叫做虚拟文件系统(VFS)的接口层,而从操作系统和系统服务中被逻辑地分离开来。

    VFS 允许 Linux 支持许多不同的, 文件系统。每一个文件系统提交一个相同的软件接口给 VFS。 Linux
    文件系统的所有细节被软件解释从而所有的,不同的,文件系统对Linux 内核,对在系统运行的程序而言
    显得相同。 Linux 的虚拟的文件系统层允许你同时透明地安装许多不同的文件系统。
    (译者注:细心的读者不难发现,在工业界,通过提供一个接口(Interface)标准,透明地屏蔽掉实现的
    不同性,多样性是一个常用的方法。如POSIX标准,PCI标准等等。这个思路几乎可以在任何一个技术中
    得到采用。标准是整合工业界竞争的必然手段和结果。)

    Linux虚拟文件系统的实现要使得对文件的存取要尽可能的快和高效。文件和文件中的数据要正确地被维护。
    上述这两个要求是互相限制的。 当文件系统被安装和使用时,Linux VFS 在内存中保存其信息。当文件和
    目录被创造,写和删除时,在这些缓存里的数据要被修改更新,以正确地更新文件系统。如果在运行的
    核心中观察文件系统的数据结构,你将能看到数据块正被文件系统读和写。描述正在被存取的文件和目录
    的数据结构,在核心中被创建和删除。设备驱动程序总是在那里存取和保存数据。这些缓存(Cache)中最重
    要的是是缓冲区缓存(Buffer Cache), 它是一个文件系统存取底层块设备的方法和途径。当数据块被存取时,
    它们被放进缓冲区缓存并且根据它们的状态而放入各种各样的队列中。缓冲区缓存不仅缓存数据缓冲区, 它
    也与块设备驱动程序一起管理异步接口。

    9.1 第二扩充文件系统 ( EXT2 )




    图 9.1 : EXT2 文件系统的物理的布局

    第二扩充文件系统被设计 ( 由 Remy Card)作为 Linux 的一个可扩展的,强有力的文件系统。它
    也是到目前为止在 Linux领域最成功的文件系统并且被当前Linux 的所有的分发(Distribution)所支持。

    EXT2 文件系统, 象很多文件系统一样,其构造的前提假设是文件中保持的数据被放在数据块中。这些
    数据块大小都一样,并且, 尽管块大小在不同的 EXT2 文件系统之间可以变化,但当它被创造时(使用mke2fs),
    EXT2 中块大小就被定下来 。每个文件的大小都被调整为块大小的整数倍。如果块大小是 1024 个字节,那么
    1025个字节的一个文件将占据 2 1024字节的块。不幸的是,这意味着平均而言每个文件将浪费半个数据块。
    通常在考虑计算时,存储器和磁盘空间的使用率与CPU的使用效率之间是一种折衷(Trade off)。Linux ,与大
    多数操作系统一样,选择相对低效的磁盘使用以便在 CPU 上减少负载(workload)。文件系统中,不是所有的块
    都含有文件数据, 其中一些必须被用来描述文件系统的结构信息。 EXT2 通过inode 数据结构描述每个文件。
    并已此定义文件系统的拓扑。一个inode 描述一个文件中的数据占据哪些块,文件的修正时间,存取权利和文
    件类型等等。EXT2 文件系统中,每个文件被 一个inode描述并且每个 inode 有一个唯一的数字标识。文件系统
    的inodes 一起被放在一个 inode 表中。 EXT2 目录是一种特殊的文件 ( 也被inodes 描述),包含一些指针,
    指向目录入口的各个文件或子目录的 inodes 。

    图9.1 给出了一个在一个块设备上占据一系列块的EXT2文件系统布局。就每个文件系统而言,块设备只是能被读
    并且写的一系列数据块而已。一个文件系统不需要担心一个数据块放在物理的媒介上的何处。物理分布是设备的
    设备驱动程序的工作。无论何时一个文件系统需要从包含它的块设备读信息或数据,它请求设备驱动程序读出一
    个整数倍数的数据块。EXT2文件系统划分其占据的逻辑分区成为数据块组(Block Group)。

    除了保持其中的文件和目录的信息之外,每个组还复制那些对于文件系统的完整性至关重要的信息和数据。这个
    信息的备份是非常需要的当灾难发生并且文件系统需要恢复的时候。下面的章节将更详细的描述每个数据块组的
    内容。

    9.1.1 EXT2 Inode





    图 9.2 : EXT2 Inode


    在 EXT2 文件系统中, inode 是最基本的积木;文件系统的每个文件和目录被一个并且仅仅被一个inode所描述。
    每个块组的inodes被存放在一个inode表中。该表与系统中的一张位图一起,使得系统可以追踪分配了的indoes和
    没有分配的inodes的情况。图 9.2 显示出一个 EXT2 inode 的格式, 在其包含的信息之中,它包含下列域:

    模式(Mode) 这个域含有两个信息;这inode描述什么并且用户拥有的允许。对EXT2而言 ,一个inode能描述文件,
    目录,符号连接,块设备,字符设备或 FIFO。
    拥有者信息(Owner Information)
    这个文件或目录的主人的用户和组标识符。这允许文件系统正确允许的该inode的各项存取。
    大小(Size) 以字节为单位的文件的大小。
    时间戳(Timestamps )
    inode 被创造的时间和它最后一次被修改的时间。
    数据块(Datablocks )
    到包含这个inode描述的数据的块的指针。第一12个指针是到包含这inode所描述的数据的块的指针。
    最后3个指针包含间接的,越来越多层的,最后描述了数据的,物理块的间接指针。例如, 两倍间接块指针
    指向一个数据块。该数据块中每个入口又是指向一个数据块指针的指针。这种方式意味着小于或等于12个数
    据块的文件比更大的文件存取起来要更快些。

    应该注意的是, EXT2 inodes 可以描述特殊的设备文件。这些不是真实的文件而是程序能够使用来存取设备的
    句柄。所有在/dev下的设备文件都在那里以允许程序存取 Linux 的设备。例如 mount 程序将想要安装的设备文件
    作为一个参数来引用。

    9.1.2 EXT2 超级块(Superblock)

    Superblock 包含一个文件系统的基本大小和其形状的描述。文件系统管理器使该信息来维持文件系统。
    当文件系统被安装时,通常仅仅在数据块组 0 的 Superblock 被读进内存。在系统的每个其他块组中也含有一个
    超级块的副本拷贝以防止文件系统崩溃。在其含有的信息之中:

    Magic Number
    这允许安装软件根据这个域来检查此确实是一个 EXT2 文件系统的 Superblock 。当前的EXT2版本
    是 0xEF53 .
    Revision Level
    主和次Revision Level 使得安装代码可以决定这个文件系统是否只是特别地支持某个版本的文件系统性能。
    其特徵相容性域值可以帮助安装代码决定哪些新特徵可以在这个文件系统上被使用,
    Mount Count and Maximum Mount Count
    这些域在一起,允许系统决定是否文件系统应该被检查。文件系统被安装时,安装数每次被增加,并且
    当它等于最大的安装数时,系统将显示警告消息“到达最大的安装数目, 推荐运行e2fsck”。
    Block Group Number
    拥有该Superblock 的这个拷贝的块组数字,
    Block Size
    这个文件系统的块的大小, 例如 1024 个字节,
    Blocks per Group
    在一个组中块的数目。当文件系统被创造时,象块大小一样这个值是被固定下来的,
    Free Blocks
    在当前文件系统中的空余的块的数目,
    Free Inodes
    在当前文件系统中的空余的 Inodes 的数目。
    First Inode
    文件系统中的第一个 inode 的 inode 号码。在一个 EXT2 根文件系统的第一个inode 将是目录入
    口项 / 目录。

    9.1.3 EXT2 组描述符

    每个块组(Block Group)有一个数据结构来描述它。象 Superblock 一样,所有块组的组描述符在每个块组中有一份
    拷贝以防止在文件系统崩溃。

    每个组描述符包含下列信息:

    块位图(Blocks Bitmap)
    当前块组中块的分配位图的块号码。在块分配和回收期间被使用。
    Inode 位图
    当前块组的 inode 分配位图的块号码。这在 inode 分配和回收期间被使用,
    Inode 表
    这个块组的 inode 表的开始块的块号码。每个 inode 由一个 EXT2 inode 数据结构来描述。
    空余块数, 空余 Inodes 数, 已使用的目录数(Free Block count,Free Inodes count,Used directory count)

    组描述符挨个儿存放并且一起组成为描述符表。每个块组,在其Superblock 的拷贝以后包含组描述符的全部表项。
    系统中仅仅第一个拷贝 ( 块组 0 ) 实际上被 EXT2 文件系统使用。另外的拷贝, 象 Superblock 的拷贝一样,
    只是以防主拷贝崩溃.(译者注:读者可以回忆DOS中的两个FAT表的使用;当查找一个文件时,系统只用第一个FAT表;
    另外一个FAT表作备份使用以防FAT链表指针混乱。有趣的是DOS中并不保存多个0扇区。总的来说,多个超级块和组
    描述符数据结构的使用是为了保证数据结构的一致性。如当使用fsck检查文件系统时,如两个相应的数据结构不一致,
    那就说明文件系统非正常的操作发生,如调电,非正常关机等等。)

    9.1.4 EXT2 目录



    图 9.3 : EXT2 目录


    在 EXT2 文件系统中,目录是被用来创造并且在文件系统中保持存取路径到文件的特殊文件。图 9.3
    显示出内存中一个目录入口的布局。

    一个目录文件是一系列目录入口的一张表, 每一个人口项包含下列信息:

    inode 为这个目录入口项的 inode 号码。这是被保存在块组Inode 表中的inodes 的数组的索引。
    在图 9.3 中, 文件 file 的目录入口 是一个指向inode i1 的指针。
    名字长度(name length)
    这个目录入口的以字节记的长度,如16字节等等。(译者注:换句话说,每个目录项的长度是不定长的)
    名字(name) 这个目录入口的名字,如文件的名字或子目录的名字等等。

    每个目录的起先两个入口项总是是“ . ”并且“ .. ”,分别意味着这个当前目录和“上一级目录”
    的入口。

    9.1.5 在一个 EXT2 文件系统中寻找一个文件

    一个 Linux 文件名的格式和 Unix 一样。它是一系列由“ / “” )分开的目录名组成,最后以文件的名字结束。
    例如一个文件名是 /home/rusling/.cshrc, 在这里/home 及 /rusling 是目录名字。文件的名字是 .cshrc .
    象所有的其他的 Unix 系统一样, Linux并不特别着重对文件名的格式本身。它可以是任何长度,由可打印的字符组成。
    为了发现代表一个文件inode, 一个EXT2 系统必须一个目录一次的逐层分析这个组合的文件名的直到我们最终找到该
    文件。

    我们需要的第一个 inode 是文件系统根(root)的 inode。我们可以得到它的值在文件系统的 superblock中 。
    为了读取一个 EXT2 inode ,我们必须在适当的块组的 inode 表从寻找它。如果,例如, 根 inode 号码是 42
    , 我们将从块组0的 inode 表中读取第 42 个inode 。根 inode 为一个 EXT2 目录, 换句话说 inode 作
    为一个目录。其指向的数据块包含 EXT2 目录入口的数据。

    home只是\"/\"中许多目录入口的一个。 从其在“/”中的入口项,我们可以得知描述其的inode 的号码。我们必须读
    这个目录 ( 首先读它的 inode,然后从该inode指向的数据块读取\"/home\"下的目录入口数据)来发现
    rusling。从得到的数据中,我们得到/home/rusling 目录 inode 的号码的入口 。最后我们读入指向描述目录
    /home/rusling的inode数据。并从其指向的数据块中发现.csshrc的 inode 数值。从该inode中,我们可以定位包含
    该文件数据的数据块。

    9.1.6 在一个 EXT2 文件系统中改变一个文件的大小

    文件系统一个普编的问题是文件数据块组织的碎片趋势(译者注:数据块物理存放位置的不连续性,离散性)。保持
    文件的数据的块在整个文件系统中分布。这使得顺序存取一个文件的数据块的效率随着数据块的分离越来越差。 EXT2
    文件系统通过将一个新分配的数据块放在靠近当前块的地方,或至少在一个同样的块组,来克服上述效率的问题。
    只有当上述行为失败时(译者注:如当前块组已满),文件系统才分配在另外的块组的数据块。

    无论何时进程试图写数据进一个文件, Linux 文件系统检查看数据将写入的位置是否已越过文件的最后分配的数据块。
    如果是的,它必须为这个文件分配新数据块。直到分配完成,进程不能运行;必须等到文件系统分配一个新数据块并且
    将余下数据写入到这个新的数据块中之后。EXT2数据块分配算法要做的第一件事情是锁住EXT2文件系统的 Superblock。
    分配和释放数据块都要改变superblock内的域值,文件系统不能允许超过一个的 Linux 进程同时这种变化。如果另外
    的进程更需要分配数据块,它将必须等待直到这进程完成了。等待 superblock 的进程被挂起, 不能继续运行,直到
    superblock 的控制被它的当前的占有者所放弃。 superblock 的存取基于先来, 先服务的基础(FIFO)并且一旦进程
    获得 superblock 的控制,它拥有该控制直到它完成了操作。获得并锁住了superblock后 , 进程检查文件系统中是否有
    足够的自由数据块。如果没有足够的可分配物理数据块, 分配块的尝试将失败并且进程将放弃这个文件系统的
    superblock 控制。(译者注:superlock或inode被读进内存后是共享的数据区,所以在存取时要加锁。在操作系统中,
    文件系统是个非常需要保护的资源。不同的文件句柄可以指向同一个文件。对文件的操作是一个完全并发的操作过程。
    在一个进程读一个文件的同时,其内容可以被其他进程或同一个进程内部的线程(Thread)所改写。因此操作系统核心
    的锁机制是非常重要的。译者强烈建议读者阅读相关内部算法。可参见贝齐的著作。)

    如果在文件系统中有足够的自由块, 进程试着分配一个。

    如果 EXT2 文件系统被设计成有预先分配数据块的功能,我们可以从中取一个。预先分配的数据块其实并不实
    际上存在, 它们只是在分配的块位图中被预先保留而已。代表正在试图分配数据块给那个文件的 VFS inode的新数
    据块有两个EXT2 特定的域, prealloc_block 及 prealloc_count , preallocated 是第一个预先分配的数据块
    的块号码。preallocated是当前预先分配的数据块已经有多少。如果当前没有预先分配的块或块preallocation功能没
    被打开, EXT2 文件系统必须从头开始分配一个新块。EXT2文件系统首先查看在该文件的最后那个数据块之后的数据块是
    否是空余的。从逻辑上而言, 这是分配方案中最有效的块因为它使得做顺序存取更加快捷。如果该块不是空余的,系统
    扩大搜索范围并且在该理想块的64 块范围内寻找数据块。这个寻找到的块, 尽管不是最理想的,但还是相当靠近并且与
    另外属于这个文件的其他数据块属于同一个数据块组。

    如果甚至上述块也不是空余的,进程开始依次在其他的块组里进行查找直到它发现空余的块。块分配代码在块组中寻找
    一个 有 8 个空余的数据块簇。如果它不能发现 8 个数据块在一起, 它将要求设置较少些。如果需要或启动了块预分配
    (preallocation)功能,它将更新 prealloc_block 及 prealloc_count位值(译者注:系统总是尽力的要把文件的数据块
    放在相邻的物理位置以提高文件数据查找效率。这里的机关是,如果一个连续8个数据块或少点的连续数据块被发现,
    即使不预先分配占有,在下一次分配空间时,极有可能系统得到最佳的分配方案---连续分配。如果预约功能打开,
    则确保下次分配的最佳性)

    无论哪里系统发现空余的块, 块分配代码更新该目标块组的块位图(译者注:标记该物理块已被占用。请回忆在PC下使用
    NORTON 软件查看磁盘空间使用时的情景)并且在缓冲区缓存(Buffer cache)中分配一个数据缓冲区。那个数据缓冲区被文
    件系统支持的对应的设备标识符唯一定位(1:1)并且与刚刚分配的物理块号码也是唯一对应的。然后缓冲区的数据被清
    零-- zero\"d,而且缓冲区的状态被标记”dirty\" 以表示该缓冲区的内容还没被最后写入对应的物理磁盘块。最后,
    superblock自己被标记作为”dirty”以表示已被改变,然后被解锁。如果有任何进程正在等待superblock ,在队列中的
    第一个被允许再次运行并且将为它的文件操作获得 superblock 的独占控制。进程的数据被写到新数据块(译者注:其实
    是先写入其对应的数据缓冲区中),并且, 如果那个数据块已被充满,进程将重复上述块分配行为从而得到一个新的
    数据块. (译者注:这里讲的superblock,indoe, buffer cache全是核心中的共享数据结构,所以存在与物理磁盘上的
    映象的一致性问题。在文件系统中,一个非常重要的是:所有的块数据都是先写入Buffer Cache。而Buffer Cache也是被
    多进程,多线程所共享的。)

    9.2 虚拟文件系统 ( VFS )




    图 9.4 :虚拟的文件系统的逻辑图表


    图 9.4 显示了Linux 核心的虚拟文件系统与真实文件系统的关系。虚拟文件系统必须管理在任何时间被安装的,不同的
    文件系统。为了做到这一点,它在核心中维持描述全部的数据结构为整个( 虚拟 ) 文件系统和真实的, 安装的文件系统。

    值得注意的是,VFS,象EXT2文件系统一样, 同样使用 superblocks 和 inodes来描述系统的文件。象 EXT2 inodes 一样,
    VFS inodes 在系统内用来描述文件和目录;虚拟文件系统的内容和结构拓扑。从现在起, 为了避免混乱, 我们将用VFS
    indoes VFS superblocks 以区别 EXT2 inodes 和 superblocks。

    当每个文件系统被初始化时,它向 VFS 登记自己。这个过程通常发生在当操作系统在系统引导时间初始化自己的时候。真实
    的文件系统要么是被嵌入了核心或是作为可装载的模块。系统模块当系统需要它们时被装载, 因此,例如, 如果 VFAT 文件
    系统作为一个核心模块被实现, 那么只有当被装载(mount)的时候,一个 VFAT 文件系统才被装入核心。当一个基于块设备的文
    件系统被安装时(这包括根文件系统),VFS 必须读入它的 superblock 。每种文件系统类型的 superblock 读例程必须了
    解其相应文件系统的拓扑组织结构并将该信息映射到 VFS superblock 数据结构之上。 VFS 保持系统中所有已安装的文件系
    统的VFS superblocks 并组织成一个链表。每个 VFS superblock 包含相应的信息和能执行特殊功能的例程的指针。 例如,
    一个安装了的 EXT2 文件系统的 superblock 包含一个指向读取一个特定的 EXT2 inode 结构的例程指针。这个 EXT2 inode
    读取例程, 象文件系统的所有其他特定的 inode 读例程一样, 在一个 VFS inode 中填写相关域。文件系统中每个 VFS
    superblock 包含一个指针指向其相应的第一个 VFS inode 。对于根(“/”)文件系统,这是代表的“/” 目录的inode。
    这个信息映射的过程对于 EXT2 文件系统是很有效的但是对于另外其他的文件系统其效率要差些。

    当系统的进程存取目录和文件时,与 VFS inodes 处理相关的系统例程在系统核心中被调用。

    例如, 键入 ls 以显示一个目录或 cat 以显示一个文件导致虚拟文件系统查找代表那个文件系统的相应VFS inodes。
    因为在系统中每个文件和目录都对应于一个VFS inode,因此会有很多 inodes 将反复的被存取。这些 inodes 被存放在使
    它们的存取更快的 inode 缓存。如果一 inode 不在 inode 缓存,那么特定的例程必须被请的一个文件系统命令读适当的
    inode 缓冲中。如果一个inode不在inode缓冲中,则必须调用一个特定的例程来读入一个inode。读 inode 的行为导致一个
    inode被放进 inode 缓存中并且其他相续的对该inode的存取将使得该inode保持在缓存中。不常用的 VFS inodes 会从核心
    inode缓存中被挪走。

    所有的Linux文件系统使用相同的的缓冲区缓存(Buffer Cache)机制来缓冲来自底层的数据。这个机制使得文件系统对物理
    数据存储设备的存取得到加快。

    这个缓冲区缓存是独立于文件系统的,被集成入 Linux 核心机制中用来分配和读写缓冲区和。这个机制的最大优点是它使得
    Linux文件系统独立于底层的物理介质,独立于设备驱动程序。所有的块设备在Linux 核心中登记自己,提供一个一致的,基
    于块的, 异步的接口。即使复杂的SCSI 设备也如此。当真实的文件系统要从底层物理设备读取数据时,其结果是触发一个块
    设备驱动程序向它们控制的设备发出读物理块的请求。集成在块设备接口里的就是缓冲区缓存。当文件系统读入了数据块后,
    它们被存放在这个全局的缓冲区缓存中,被文件系统和 Linux核心所共享。在其内的缓冲区数据通过块号码和对应于其设备的
    标识符被系统唯一标识。因此,如果同样的数据经常被需要使用,数据将从缓冲区缓存被检索而非从磁盘读入。一些设备支持
    提前读取功能,系统“猜测”要被读取的数据块并事先将其读入到缓冲区缓冲中。

    VFS 也保留一个存放目录查找的缓存以便经常被使用的目录的 inodes 能快速被发现。

    作为一个试验,试着列出你最近没列出的一个目录。你列出它的第一次, 你可以注意响应时间有一点停顿,但是 第二次列此目
    录时速度却是非常快的。目录缓存并不存储目录的 inodes 本身;这些应该在 inode 缓存中, 目录缓存只简单地存储目录名到
    其相应的indoe 号码间的映射信息。

    9.2.1 VFS Superblock

    每个安装了的文件系统都被一个 VFS superblock 所表示;在其信息之中, VFS superblock 包含:

    设备(Device) 这是这个文件系统所依赖的块设备的设备标识符。例如, /dev/hda1 , 系统中的第
    一个 IDE 硬盘有一个设备标识符 0x301 ,
    Inode 指针
    mounted inode 指针指向这个文件系统的第一个 inode。 covered inode 指针指向代表这个文件系统安装点
    目录的 inode。根文件系统的 VFS superblock 没有 covered 指针,
    块大小(Blocksize )
    这个文件系统的字节的块大小, 例如 1024 个字节,
    Superblock 操作
    一个指向这个文件系统的一套 superblock 例程的一个指针。与其他信息在一起使用,这些例程被 VFS 用来
    读和写该文件系统的 inodes 和 superblocks 。
    文件系统类型(File System Type)
    一个指向被安装文件系统中file_system_type 数据结构的一个指针,
    File System specific 一个指针指向这个文件系统所特定需要的一些信息,

    9.2.2 VFS Inode

    象 EXT2 文件系统一样, VFS系统中每个文件, 目录等等被一个而且仅仅被一个 VFS inode 所表示。

    通过一些特殊的文件系统例程,每个 VFS inode 的构建信息都来自于底层的文件系统。 VFS inodes 仅仅在核心中,
    内存中才存在,并且只当他们对系统有用时才存在。 VFS inodes 包含下列域:

    设备(device) 这是保持该VFS inode 所代表的文件所在的设备的设备标识符
    inode 号码
    这是该 inode 的号码并且此号码在这个文件系统以内是唯一的。device 和 inode 号码的组合在虚拟文
    件系统中是唯一的,
    模式(mode) 象EXT2中这个域一样,它描述这个 VFS inode 代表了什么和相应的存取权利。
    用户 ids
    该VFS inode拥有者的标识符,
    时间 创造, 修正和写的时间,
    块大小
    这个文件的块的大小, 例如 1024 个字节,
    inode 操作
    指向一块例程地址的一个指针。这些例程对文件系统是特定的并且它们可以为这个 inode 完成相关操作, 例如, 截断
    被这个 inode 所代表的文件。
    count
    系统中当前使用这个 VFS inode 的统计数字。一个count 是0的inode 是空余的或可以被从内存中抛弃的。
    锁(lock) 这个域被用来锁住一个 VFS inode , 例如, 当它正从文件系统中被读取时,
    dirty
    显示这 VFS inode 是否被写了, 如果是,底层相应的文件系统需要修改,以保持一致性,
    文件系统特定的信息(file system specific information)

    9.2.3 登记文件系统



    图 9.5 :登记的文件系统


    当你构造 Linux 核心时,你会被问到你是否想要构建支持的每个文件系统。当核心被构造时,文件系统初始代码中含有
    所有被构造文件系统的初始化代码的调用入口。

    Linux 文件系统也可以作为模块来被构造,并且, 在这种情况中,它们可以是当需要时或手工安载时(使用insmod命令),
    才被装入。无论何时一个文件系统模块被装载,它向核心登记自己;当被卸掉时,从核心中撤消登记。每个文件系统的
    初始化代码在虚拟文件系统VFS中登记自己,通过提供 file_system_type 数据结构,在这个数据结构中,含有文件系统的名
    和一个指向其VFS superblock 读例程的指针。图 9.5 显示出 file_system_type 数据结构被放进 file_system的一个链表中。
    每个 file_system_type 数据结构包含下列信息:

    Superblock 读例程
    当一个文件系统的实例被安装时,该例程被 VFS 调用,
    文件系统名字
    这个文件系统的名字, 例如 ext2 ,
    设备需要(Device Needed)
    这个文件系统需要一台设备支持吗?不是所有的文件系统需要一台设备来支持。例如, /proc 文
    件系统, 不要求一个块设备,

    你可以通过查看/proc/filesystems来获知系统中什么文件系统被登记了。例如:

    ext2
    nodev proc iso9660

    9.2.4 安装一个文件系统

    当超级用户试图安装一个文件系统时, Linux 核心必须首先验证在系统调用中被传递的参数。尽管 mount 做一些基本的检查,
    它不知道核心中哪些文件系统是否已经被构建,不知道是否一个安装点实际上存在。考虑下列安装命令:

    $ mount - t iso9660 - o ro /dev/cdrom /mnt/cdrom

    本安装(mount)命令将传递给核心 3 个信息;文件系统的名字, 含有该文件系统的物理块设备和这个新要安装的文件系统将被安
    装在现有文件系统拓扑结构中的什么地方。

    虚拟文件系统必须做的第一事情是找到该文件系统。

    为了做到这一点,核心浏览上节讲述的文件系统链表,通过遍历由 file_systems 指向的 file_system_type 数据结构。

    如果发现一个匹配的名字,这表明核心当前支持这种文件系统类型并且得到如何读取这个文件系统的 superblock 的例程地址。
    如果它不能发现一匹配文件系统名字,系统核心会查看是否自己被构建为动态地装载核心模块。( 参见 模块章 )。在这种情况
    中核心将请求核心监控程序将相应的文件系统调入。

    下一步,如果指定的物理设备还没有被安装, 系统必须发现这个文件系统的安装点目录的 VFS inode。这个 VFS inode 可能已
    在核心的 inode 缓存中,或它需要从支持安装点的文件系统的块设备被读取进来。一旦 inode 被找到,系统将检查它是否一个
    目录并且没有其他的文件系统已经被安装在那里了。同一个目录不能为多个文件系统作为安装点。

    然后,VFS 安装代码必须分配一个 VFS superblock 数据结构并且将相关的安装信息传递给这个文件系统的superblock读例程。
    系统的所有 VFS superblocks结构被放在 super_blocks向量中。其元素是super_block 数据结构。 superblock 读例程必须基
    于它从物理设备读到的信息填写 VFS superblock 记录域。对于一个 EXT2 文件系统,这个映射或信息的翻译的过程是相当容易
    的, 它简单地读取 EXT2 superblock 并且相应填写 VFS superblock 。对于另外的文件系统,例如 MS DOS 文件系统,事情就不
    那么简单。无论什么文件系统, 填写 VFS superblock 意味着文件系统必须从支持它的块设备读入一些信息。 如果块设备不能被
    读或如果它不含有这类文件系统, 安装命令将失败。




    图 9.6 :一个安装的文件系统


    每个被安装的文件系统被一个 vfsmount 数据结构所描述;参见图 9.6,它们被链在一个 vfsmntlist的链表上。

    另外一个指针, vfsmnttail 指向上述链表的最后入口。mru_vfsmnt 指针指向最近最多使用了的文件系统。
    每个 vfsmount 结构包含该文件系统对应的底层设备的设备号,这个文件系统被安装的目录,和一个指针指向其 VFS superblock。
    如我们已经知道的, VFS superblock 指向这种文件系统的 file_system_type 数据结构,然后指向这个文件系统的根 inode。
    如果该文件系统没有被卸出核心,这个 root inode 一直呆在 VFS inode 缓冲中。

    9.2.5 在虚拟文件系统中查找一个文件

    为了在虚拟文件系统中发现一个文件的 VFS inode , VFS 必须一次一个目录地解释名字, 寻找代表名字中间的,那些目录的各个
    VFS inode。逐层找到父目录的inode是很容易的,因为我们总是可以由其 VFS superblock 得到每个文件系统的根的 VFS inode。
    每次当真实的文件系统探寻一个目录 inode 时,它首先在目录缓冲中探查这个目录。如果在当前目录缓存中没有入口,真实的文
    件系统就从底层的文件系统或从 inode 缓存获取其 VFS inode 。

    9.2.6 在虚拟文件系统创建一个文件

    9.2.7 (卸掉)Unmounting 一个文件系统

    如果系统还正在使用一个文件系统的文件,一个文件系统是不能被卸下的。例如, 你不能 umount /mnt/cdrom 如果进程正在使用
    它或它的子目录。如果一个将要被卸下的文件系统正在被使用,那么有可能在 VFS inode 缓存中存在属于这个文件系统的 VFS
    inodes;系统的代码在核心 inodes 的链表中进行查找这个文件系统占据的设备拥有的inodes。如果这个安装的文件系统的 VFS
    superblock 是dirty的,这说明它被修改了, 那么它必须被写回到在磁盘上的文件系统中。一旦它被写回磁盘,VFS superblock
    占据的存储空间就可以被释放。最后,最后对应于该文件系统的 vfsmount 数据结构也被从vfsmntlist 链表中断开并且释放其占
    据的空间。

    9.2.8 VFS Inode 缓存(cache)

    当一个安装的文件系统被浏览时,其VFS inodes 不断地被读或写。虚拟文件系统维持一个 inode 缓存以加快文件系统的存取。
    每次一个 VFS inode 从 inode 缓存中被读取,系统就可以节省读取物理设备的存取时间。

    VFS inode 缓存的实现是一个其入口是 VFS inodes 链表指针的一张哈希表。同一个链表中的inodes拥有相同的哈希值。一个inode
    的哈希值的计算是通过其inode的数值和包含其文件系统的物理设备的标识符。无论何时虚拟文件系统存取一个inode时,它首先
    查看VFS inode 缓存。为了在VFS inode 缓冲中查找一个inode, 系统首先计算它对应的哈希值然后将其作为索引值进入inode
    哈希表。然后通过读取这个拥有相同哈希值的inode链表并挨个儿比较每个inode的 inode 数字和一样的设备标识符直到发现为止。

    如果一个inode在inode缓存中被发现,该inode的计数(count)值被增加以显示出它还有另外的用户,然后系统接着继续存取文件。
    否则一个空余的 VFS inode 必须被发现以便文件系统能够从存储器读取 inode 。 至于VFS怎么得到一空余的inode 有很多选择。
    如果系统可以分配更多的 VFS inodes空间,事情就解决了;它分配一些核心存储页并且将它们分成一个个新的, 空余的inodes并
    且把它们放进核心中的 inode 表。系统中所有的 VFS inodes 都在一个被指针first_inode指向的一张链表中。当然也在那个哈
    希表中。如果系统已经拥有了它可以被允许有的 inodes 的数量,它必须发现一个好的候选inode被重用(resue)。好的候选inode
    是一个当前使用计数为0的inodes; 这表示当前系统不再需要这些inode。那些重要的 VFS inodes , 例如文件系统的根inodes的
    使用计数总是比零大,所以从来不会被选中作为重用候选的inode。一旦一个候选inode被选定,它将被清理。 这个VFS inode 有
    可能是dirty的,在这种情况中,它需要被写回到文件系统。这个inode也可能当前被加锁了,在这种情况中系统必须等待直到它
    被解锁。VFS inode 必须在重用之前被清理。

    当新的 VFS inode 被发现后, 一个特定的文件系统例程必须被调用,将从底层真实文件系统读取来的信息来填充这个已准备好了
    的inode数据结构。 当它正在被填写的过程中,这个新的 VFS inode 的使用计数值为1并且被加锁,从而其他的实体不能对这个
    数据结构进行任何操作直到它已含有完整的数据结构。

    为了得到一个实际上需要的 VFS inode, 文件系统可能需要存取若干个另外的 inodes 。当你读一个
    目录时,这种情况就会发生;仅仅那个最后的目录的 inode 是我们所需要得,但是那些中间目录的 inodes 也必须被读取。当
    VFS inode 缓存机制被使用并且被充满时, 那些较少被使用的 inodes 将被丢弃。较多被使用的 inodes 将在缓存中留下。
    (译者注:细心的读者不难发现,其实在文件系统中存在许多的机制都是为了克服读取物理设备带来的效率代价。译者在这里
    提出一个问题供大家思考:这些众多的缓冲机制的缺点是什么,特别是随着计算机系统内存价格越来越便宜的时候。这里有很多
    的工作和思考可以做。)

    9.2.9 目录缓存

    为了加快对那些被通常使用的目录的存取, VFS 维持目录入口的缓存。

    当目录被真实的文件系统查寻时,它们的细节被加进目录缓冲。当下一次同样的目录被查寻时,例如列目录或打开在其中的一个
    文件, 系统将在目录缓存中找到其信息。仅仅短的目录入口 ( 15字节长度) 才被缓冲。这是合理的因为短目录名字是被最经常
    使用的。例如, /usr/X11R6/bin, 当 X 服务器运行时,该文件通常被频繁地被存取。

    目录缓存由一张哈希表组成, 其每个入口指向具有同样哈希值的目录缓存的一个链表。
    哈希函数使用支持该文件系统的设备的设备标识和目录名来作为哈希值的计算□通过哈希表,可以使得一个目录项快速的被找到。
    一个需要花费很多查找时间的缓冲机制是没有意义的。

    为了保持一个最新的,正确的缓冲,VFS 维护一些基于LRU(Least Recently Used)算法的目录缓冲链表。当一个目录项第一次被
    放进这个缓存时(它第一次被查找时), 它被增加到第一层 LRU 链表的链尾。在一个已经充满的缓存区情况下,这将从 LRU 表的
    前面挤掉一个已经存在的目录入口项。当这个新目录项再次被存取时,它被放到第二层 LRU 缓存表的链尾。这个行为也可能
    挤掉一个在第二层 LRU 缓存表中链头的一个目录项。这种在LRU链表中的链头元素的替换或挪走是很好的策略。其原因是在链头
    的元素意味着它们在最近没有被存取。如果他们有被存取,它们的位置将是靠近链表的链尾。在第二层 LRU 中的缓存数据比在第
    一层的要安全。这里的内涵是在第二层的数据不仅仅是被查寻了一下而已,而是被经常地访
    发布人:netbull 来自:linuxeden