Linux 文件系统

Linux 内核支持的文件系统,描述虚拟文件系统  VFS ,讲述了 Linux 内核的真实文件系统是怎么被支持的

Linux 为许多不同的文件系统的支持 使其与许多另外的操作系统很好的共存
Linux 文件系统 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 月被引入,其解决了很多问题仍然缺乏一个很好的性能
第二扩大文件系统或EXT2 被增加到 Linux 文件系统中
当EXT文件系统被增加进Linux时,一个重要的关于文件系统的开发技术发生了
真实的文件系统通过 一个叫做虚拟文件系统 VFS 的接口层,而从操作系统和系统服务中被逻辑地分离开来

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

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


第二扩充文件系统 EXT2

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

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

除了保持其中的文件和目录的信息之外,每个组还复制那些对于文件系统的完整性至关重要的信息和数据
备份当灾难发生并且文件系统需要恢复的时候



EXT2 Inode

EXT2 Inode在 EXT2 文件系统中,inode 是最基本的积木 ,文件系统的每个文件和目录被一个并且仅仅被一个  inode 所描述 每个块组的 inodes 被存放在一个 inode 表中 该表与系统中的一张位图一起,使得系统可以追踪分配了的 indoes 和没有分配的 inodes 的情况
显示出一个EXT2 inode 的格式,在其包含的信息之中
模式Mode 两个信息 这 inode 描述什么并且用户拥有的允许
对 EXT2 而言 ,一个 inode 能描述文件目录,符号连接,块设备,字符设备或 FIFO

拥有者信息Owner Information

文件或目录的主人的用户和组标识符 这允许文件系统正确允许的该 inode 的各项存取

大小Size

以字节为单位的文件的大小

时间戳Timestamps

inode 被创造的时间和它最后一次被修改的时间

数据块Datablocks

到包含这个 inode 描述的数据的块的指针
第一 12 个指针是到包含这 inode 所描述的数据的块的指针 最后 3 个指针包含间接的,越来越多层的,最后描述了数据的,物理块的间接指针 如,两倍间接块指针指向一个数据块 该数据块中每个入口又是指向一个数据块指针的指针 这种方式意味着小于或等于 12 个数据块的文件比更大的文件存取起来要更快些应该注意的是, EXT2 inodes 可以描述特殊的设备文件 这些不是真实的文件而是程序能够使用来存取设备的句柄 所有在 /dev 下的设备文件都在那里以允许程序存取  Linux 的设备 mount程序将想要安装的设备文件 作为一个参数来引用


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 将是目录入口项 / 目录


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 链表指针混乱
多个超级块和组 描述符数据结构的使用是为了保证数据结构的一致性
如当使用 fsck 检查文件系统时,如两个相应的数据结构不一致, 那就说明文件系统非正常的操作发生,如调电,非正常关机等


EXT2目录

在 EXT2 文件系统中,目录是被用来创造并且在文件系统中保持存取路径到文件的特殊文件 内存中目录入口的布局 一个目录文件是一系列目录入口的一张表  , 每一个人口项包含下列信息:

inode

为这个目录入口项的 inode 号码 这是被保存在块组 Inode 表中的 inodes 的数组的索引 在图 9.3 中  , 文件 file 的目录入口 是一个指向 inode i1的指针

名字长度name length

这个目录入口的以字节记的长度,如 16 字节等等   换句话说,每个目录项的长度是不定长的

名字name

目录入口的名字,如文件的名字或子目录的名字等等

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

在一个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 中,我们可以定位包含该文件数据的数据块


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 也是被 多进程,多线程所共享的


虚拟文件系统 VFS

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 号码间的映射信息


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

一个指针指向这个文件系统所特定需要的一些信息

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


登记文件系统

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

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

每个 file_system_type 数据结构包含下列信息:

Superblock read routine

此例程载文件系统的一个实例被安装时由 VFS 调用

File System name

文件系统的名称如 ext2

Device needed

文件系统是否需要设备支持 并不是所有的文件系统都需要设备来保存它 如 /proc 文件系统不需要块设备支持

可以通过查阅 /proc/filesystems 可找出已注册的文件系统,如:

ext2
nodev proc
iso9660

安装文件系统

当超级用户试图安装一个文件系统时,  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 意味着文件系统必须从支持它的块设备读入一些信息 如果块设备不能被读或如果它不含有这类文件系统, 安装命令将失败

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


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

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

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

卸掉Unmounting一个文件系统

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

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 将在缓存中留下   细心的读者不难发现,其实在文件系统中存在许多的机制都是为了克服读取物理设备带来的效率代价 译者在这里 提出一个问题供大家思考:这些众多的缓冲机制的缺点是什么,特别是随着计算机系统内存价格越来越便宜的时候 这里有很多

的工作和思考可以做


目录缓存

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

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

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

为了保持一个最新的,正确的缓冲, VFS 维护一些基于 LRU Least Recently Used 算法的目录缓冲链表 当一个目录项第一次被放进这个缓存时  它第一次被查找时  , 它被增加到第一层 LRU 链表的链尾 在一个已经充满的缓存区情况下,这将从 LRU 表的前面挤掉一个已经存在的目录入口项 当这个新目录项再次被存取时,它被放到第二层 LRU 缓存表的链尾 这个行为也可能挤掉一个在第二层 LRU 缓存表中链头的一个目录项 这种在 LRU 链表中的链头元素的替换或挪走是很好的策略 其原因是在链头

的元素意味着它们在最近没有被存取 如果他们有被存取,它们的位置将是靠近链表的链尾 在第二层 LRU 中的缓存数据比在第一层的要安全 这里的内涵是在第二层的数据不仅仅是被查寻了一下而已,而是被经常地访问


缓冲区缓存Buffer Cache

当安装的文件系统被使用时,产生很多对块设备的数据块的读和写请求 所有的块数据读和写请求以 buffer_head 数据结构的形式传递给设备驱动程序经由一些标准的核例程调用 这个数据结构里含有了块设备驱动程序所需要的所有信息 ,标识一个设备的设备标识符和要读取得数据块的号码 所有的块设备都是具有同样大小的数据块的线性组合 为了加快物理块设备的存取,Linux 维持一个块缓冲区的缓存 系统中所有的块缓冲区都被放在这个缓冲区缓存的每个地方,甚至包括最新的,还没被使用的缓冲区  这个缓存被系统中所有的物理块设备所共享 ,在任何一个时间里,在缓存 cache 中都有许多块缓冲区 Block Buffer , 它们可能属于系统中的任何一个块设备而且这些数据处于不同的状态 如果从缓冲区缓存中可以得到有效的数据,这就将节省系统去访问物理设备的时间 任何一个被用来从块设备读取或写数据的块缓冲区都进入这个缓冲区缓存 Buffer Cache
将来它可能从缓存被移走以为那些更合适的缓冲区 Buffer , 当然如果它 a Block Buffer 经常被存取就可以在缓存里留下

在缓存内的块缓冲区都通过其对应块设备的标识符合其块号码来唯一标识 缓冲区缓存由两个功能部份组成 第一部份是空余的块

缓冲区的链表 对应于每种支持的缓冲区大小,系统中有一个相应的链表 当系统中的块缓冲被创建和被丢弃时,它们就被挂到这

些相应的链表上 当前 Linux 系统支持的缓冲区大小是 512 ,  1024 , 2048 , 4096 和  8192 字节 第二个功能部份是其缓存

cache 本身 这是一张哈希表 每个入口是指向由指针串起来的缓冲区链表的指针 哈希值索引的计算是有数据块拥有的设备标识

符和块号码来产生 图 9.7 所示是这个哈希表和几个入口 块缓冲区要么是在空余的链表之中,或在哈希缓冲区缓存中 当他们在

缓冲区缓存中时,它们也被链在 LRU 链中 对每种缓冲区类型都有一张 LRU 链表 它们被系统用来执行对这种类型缓冲相关的操作

写缓冲区中的新数据到磁盘中 缓冲区的类型反映它的状态 Linux 当前支持下列类型:

乾净 clean 闲置的,新的缓冲区,

锁 locked 被锁的缓冲区,等待被写,

脏 dirty 脏的缓冲区 这些包含新,有效的数据,将被写但是到目前为止没被安排写到磁盘上去,

分享 shared 共享的缓冲区,unshared

曾经是共享的缓冲区但是目前不是,

无论何时当一个文件系统需要从它底层的物理设备读一个缓冲区 Buffer 时,它试着从缓冲区缓存 Buffer cache 得到 如果它不能

从缓冲区缓存得到一个缓冲区,然后它将从适当大小的空余的链表中得到一个乾净的 clean 新的缓冲区 这个缓冲区将被放入将缓

冲区缓存 如果它需要的缓冲区在缓冲区缓存中,它可能已含有最新的数据 如果它不是最新的,或如果它仅仅是一个新块缓冲区,

文件系统必须请求设备驱动程序从磁盘读取适当的数据块

象所有的缓存一样,缓存必须被高效地维持以便它有效的,公平地为块设备分配缓存入口 Linux 使用 bdflush 监控程序执行这些

cache 的看护工作


bdflush 核心监控程序

一个当系统中有太多 “dirty” 缓冲区时,提供动态相应的一个简单的核心监控进程  ;
含有数据的缓冲区必须

在一定的时间内写入磁盘 它在系统启动时间作为一个核心线程而运行,它把自己称为 “ kflushd ” 进程 如果用 ps 命令在系统

中显示进程列表,大多数情况下,这个监控进程在系统中睡眠直到系统中 dirty 的缓冲区的数目变得太大 每次当

缓冲区被分配和丢弃时,系统检查 dirty 的缓冲区的数目 如果系统中的缓冲区 dirty 的数目超过了一个百分比, bdflush 将被弄醒

缺省阀值是 60%  如果系统急需缓冲区, bdflush 将被无条件地唤醒 这个域值可以被观察和修改,通过 update 命令

# update -d

bdflush version 1.4

0: 60 Max fraction of LRU list to examine for dirty blocks

1: 500 Max number of dirty blocks to write each time bdflush activated

2: 64 Num of clean buffers to be loaded onto free list by refill_freelist

3: 256 Dirty block threshold for activating bdflush in refill_freelist

4: 15 Percentage of cache to scan for free clusters

5: 3000 Time for data buffers to age before flushing

6: 500 Time for non-data  dir, bitmap, etc buffers to age before flushing

7: 1884 Time buffer cache load average constant

8:  2 LAV ratio  used to determine threshold for buffer fratricide .

系统中所有的 dirty 缓冲区都被链进一个 BUF_DIRTY LRU 链表中一旦它们变得 dirty  bdflush 试着将一定合理数目的这些数据写入磁盘

再次强调的是这个数目是可以通过 update 命令来调节的 其缺省值是 500


更新进程 update 命令不仅仅是一个命令 ,也是监控进程 当作为超级用户运行时 在系统初始化期间 ,周期性地刷新所有旧的 dirty 的缓冲区到磁盘上 它通过调用一个与 bdflush 功能差不多的系统服务例程来完成这个任务

无论何时一个 dirty 的缓冲区出现时,系统给它标识上一个它应该被写入磁盘的系统时间 每次 update 运行时,它查看系统中所有的 dirty

的缓冲区并将已经到期的刷入磁盘


/proc 文件系统

显示 Linux 虚拟文件系统的强大 其实并不实际存在 /proc 目录,其子目录和它的文件实际上也不存在

但你如何能 cat /proc/devices 呢 ? /proc 文件系统,就象一个真实的文件系统一样,在虚拟文件系统中登记自己 然而,当其下的文件

或目录被 open 的时候, VFS 对它进行调用请求 inodes 时候, /proc 文件系统利用核心中的信息来创造那些文件和目录 如,核心的

/proc/devices 文件是从核心中描述设备的数据结构中产生

/proc 文件系统提供给一个用户了解核心内部工作的可读窗口 一些 Linux 子系统,如 Linux 核心模块,在 /proc 文件系统中都有

信息入口项


设备特殊文件

Linux将硬件设备作为特殊文件来对待  /dev/null空设备 一个设备文件不占有文件系统的任何数 据空间 仅是对设备驱动程序的一个存取点
EXT2 文件系统和 Linux VFS 都使用特殊类型的 inode 来实现设备文件 系统中有两种特殊设备文件类型 ,字符和块设备文件 在核心自己内部,设备驱动程序实现文件的语义:你能打开他们,关上他们等等 字符设备允许以字

符模式进行所有的 I/O 操作;块设备要求 I/O 操作通过缓冲区缓存 Buffer Cache 的模式 当一个针对设备文件的 I/O 请求到来时,该请求

被提交给相应的设备驱动程序 经常这并不是系统的一个真正的设备驱动程序,而是一个针对一些子系统的伪设备驱动程序 如 SCSI

子系统设备驱动层 设备文件通过一个主设备号  它标明设备类型  ,和一个次设备号  主设备类型的一个实例  来标识 如,系统的第一个 IDE 控制器上的 IDE 磁盘的主设备号是 3 一个 IDE 磁盘的第一个分区的次设备号是 1 因此, ls - l /dev/hda1 将给出:

$ brw-rw ---- 1 root 3 ,NOV 24 15:09 /dev/hda1

每台设备都唯一的由一个 kdev_t 数据类型来描述,这是一个 2 个字节的整数,第一个字节包含次设备号;第二个字节包含主设备号

上述 IDE 设备在核心中的数据就是 0x0301 一个代表块或字符设备的 EXT2 inode 在它的第一个直接的块指针中保持着该设备的主设备和次

设备号 当它被 VFS 读取时,代表它的 VFS inode 数据结构在其 i_rdev 域中设定上述正确的设备标识符信息