发新话题
打印

LinuxKernel核心中文手册(9)--文件系统

LinuxKernel核心中文手册(9)--文件系统

  来自:蓝森林自由软件

Chapter 9

The File System (文件系统)

    本章描述 Linux 如何维护它支持的文件系统中的文件。描述了虚拟文件系统
( Virtual File System VFS )并解释了 Linux 核心中真实的文件系统如何被支


    Linux 的一个最重要的特点之一使它可以支持许多不同的文件系统。这让它非
常灵活,可以和许多其他操作系统共存。在写作本章的时候, Linux 可一直支持
15 种文件系统: ext 、 ext2 、 xia 、 minix 、 umsdos 、 msdos 、
vfat 、 proc 、 smb 、 ncp 、 iso9660 、 sysv 、 hpfs 、 affs 和 ufs ,
而且不容置疑,随着时间流逝,会加入更多的文件系统。


    在 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 的格式,在其他信息中,它包括一些域:

参见 include/linux/ext2_fs_i.h

    mode 包括两组信息:这个 inode 描述了什么和用户对于它的权限。对于

EXT2 ,一个 inode 可以描述一个文件、目录、符号链接、块设备、字符设备或
FIFO 。

    Owner Information 这个文件或目录的数据的用户和组标识符。这允许文件系
统正确地进行文件访问权限控制

    Size 文件的大小(字节)

    Timestamps 这个 inode 创建的时间和它上次被修改的时间。

    Datablocks 指向这个 inode 描述的数据的块的指针。最初的 12 个是指向这
个 inode 描述的数据的物理块,最后的 3 个指针包括更多级的间接的数据块。例
如,两级的间接块指针指向一个指向数据块的块指针的块指针。的这意味着小于或
等于 12 数据块大小的文件比更大的文件的访问更快。

    你应该注意 EXT2 inode 可以描述特殊设备文件。这些不是真正的文件,程序
可以用于访问设备。 /dev 下所有的设备文件都是为了允许程序访问 Linux 的设
备。例如 mount 程序用它希望安装的设备文件作为参数。

9.1.2 The EXT2 Superblock ( EXT2 超级块)

    超级块包括这个文件系统基本大小和形状的描述。它里面的信息允许文件系统

管理程序用于维护文件系统。通常文件系统安装时只有块组 0 中的超级块被读取
,但是每一个块组中都包含一个复制的拷贝,用于系统崩溃的时候。除了其他一些
信息,它包括:

参见 include/linux/ext2_fs_sb.h

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 ”。

Block Group Number 存放这个超级块拷贝的块组编号。

Block Size 这个文件系统的块的字节大小,例如 1024 字节。


Blocks per Group 组中的块数目。象块大小一样,这是文件系统创建的时候确定
的。

Free Blocks 文件系统中空闲块的数目

Free Inodes 文件系统中空闲的 inode

First Inode 这是系统中第一个 inode 的编号。一个 EXT2 根文件系统中的第一
个 inode 是‘ / ’目录的目录条目



9.1.3 The EXT2 Group Descriptor ( EXT2 组描述符)

每一个块组都有一个数据结构描述。象超级块,所有得亏组的组描述符在每一块组
都进行复制。每一个组描述符包括以下信息:

参见 include/linux/ext2_fs.h ext2_group_desc

Blocks Bitmap 这个块组的块分配位图的块编号,用在块的分配和回收过程中

Inode Bitmap 这个块组的 inode 位图的块编号。用在 inode 的分配和回收过程

中。

Inode Table 这个块组的 inode table 的起始块的块编号。每一个 EXT2 inode
数据结构表示的 inode 在下面描述

Free blocks count , Free Inodes count , Used directory count

    组描述符依次排列,它们一起组成了组描述符表( group descriptor
table )。每一个块组包括块组描述符表和它的超级块的完整拷贝。只有第一个拷
贝(在块组 0 )实际被 EXT2 文件系统使用。其他拷贝,象超级块的其他拷贝一
样,只有在主拷贝损坏的时候才使用。

9.1.4 EXT2 Directories ( EXT2 目录)

    在 EXT2 文件系统中,目录是特殊文件,用来创建和存放对于文件系统中的文
件的访问路径。图 9.3 显示了内存中一个目录条目的布局。一个目录文件,是一
个目录条目的列表,每一个目录条目包括以下信息:

参见 include/linux/ext2_fs.h ext2_dir_entry

这个目录条目的 inode 。这是个放在块组的 inode 表中的 inode 数组的索引。
图 9.3 叫做 file 的文件的目录条目引用的 inode 是 i1 。


Name length 这个目录条目的字节长度

Name 这个目录条目的名字

每一个目录中的前两个条目总是标准的“ . ”和“ .. ” , 分别表示“本目录”
和“父目录”。

9.1.5 Finding a File in a EXT2 File System (在一个 EXT2 文件系统中查找
一个文件)

    Linux 的文件名和所有的 Unix 文件名的格式一样。它是一系列目录名,用“
/ ”分隔,以文件名结尾。一个文件名称的例子是 /home/rusling/.cshrc ,其
中 /home 和 /rusling 是目录名,文件名是 .cshrc 。象其它 Unix 系统一样,
Linux 不关心文件名本身的格式:它可以任意长度,由可打印字符组成。为了在
EXT2 文件系统中找到代表这个文件的 inode ,系统必须逐个解析目录中的文件
名直到得到这个文件。

    我们需要的第一个 inode 是这个文件系统的根的 inode 。我们通过文件系统
的超级块找到它的编号。为了读取一个 EXT2 inode 我们必须在适当的块组中的
inode 表中查找。举例,如果根的 inode 编号是 42 ,那么我们需要块组 0 中的
inode 表中的第 42 个 inode 。 Root inode 是一个 EXT2 目录,换句话说

root inode 的模式描述它是一个目录,它的数据块包括 EXT2 目录条目。

    Home 是这些目录条目之一,这个目录条目给了我们描述 /home 目录的 inode
编号。我们必须读取这个目录(首先读取它的 inode ,然后读取从这个 inode
描述的数据块读取目录条目),查找 rusling 条目,给出描述 /home/rusling 目
录的 inode 编号。最后,我们读取描述 /home/rusling 目录的 inode 指向的目
录条目,找到 .cshrc 文件的 inode 编号,这样,我们得到了包括文件里信息的
数据块。

9.1.6 Changing the size of a File in an EXT2 File System (在 EXT2 文件
系统中改变一个文件的大小)

    文件系统的一个常见问题是它趋于更多碎片。包含文件数据的块分布在整个文
件系统,数据块越分散,对于文件数据块的顺序访问越没有效率。 EXT2 文件系统
试图克服这种情况,它分配给一个文件的新块物理上和它的当前数据块接近或者至
少和它的当前数据块在一个块组里面。只有这个失败了它才分配其它块组中的数据
块。

    无论何时一个进程试图象一个文件写入数据, Linux 文件系统检查数据是否
会超出文件最后分配块的结尾。如果是,它必须为这个文件分配一个新的数据块。
直到这个分配完成,该进程无法运行,它必须等待文件系统分配新的数据块并把剩
下的数据写入,然后才能继续。 EXT2 块分配例程所要做的第一个事情是锁定这个

文件系统的 EXT2 超级块。分配和释放块需要改变超级块中的域, Linux 文件系
统不能允许多于一个进程同一时间都进行改变。如果另一个进程需要分配更多的数
据块,它必须等待,直到这个进程完成。等待超级块的进程被挂起,不能运行,直
到超级块的控制权被它的当前用户释放。对于超级块的访问的授权基于一个先来先
服务的基础( first come first serve ),一旦一个进程拥有了超级块的控制,
它一直维持控制权到它完成。锁定超级块之后,进程检查文件系统是否有足够的空
闲块。如果没有足够的空闲块,分配更多的尝试会失败,进程交出这个文件系统超
级块的控制权。

    如果文件系统中有足够的空闲块,进程会试图分配一块。如果这个 EXT2 文件
系统已经建立了预分配的数据块,我们就可以取用。预分配的块实际上并不存在,
它们只是分配块的位图中的保留块。 VFS inode 用两个 EXT2 特有的域表示我们
试图分配新数据块的文件: prealloc_block and prealloc_count ,分别是预分
配块中第一块的编号和预分配块的数目。如果没有预分配块或者预分配被禁止,
EXT2 文件系统必须分配一个新的数据块。 EXT2 文件系统首先查看文件最后一个
数据块之后数据块是否空闲。逻辑上,这是可分配的效率最高的块,因为可以让顺
序访问更快。如果这个块不是空闲,继续查找,在随后的 64 块中找理想的数据块
。这个块,虽然不是最理想,但是至少和文件的其它数据块相当接近,在一个块组
中。

参见 fs/ext2/balloc.c ext2_new_block()


    如果这些块都没有空闲的,进程开始顺序查看所有其它块组直到它找到空闲的
块。块分配代码在这些块组中查找 8 个空闲数据块的簇。如果无法一次找到 8 个
,它会降低要求。如果希望进行块预分配,并允许,它会相应地更新
prealloc_block 和 prealloc_count 。

    不管在哪里找到了空闲的数据块,块分配代码会更新块组的块位图,并从
buffer cache 中分配一个数据缓冲区。这个数据缓冲区使用支撑文件系统的设备
标识符和分配块的块编号来唯一标识。缓冲区中的数据被置为 0 ,缓冲区标记为
“ dirty ”表示它的内容还没有写到物理磁盘上。最后,超级块本身也标记位“
dirty ”,显示它进行了改动,然后它的锁被释放。如果有进程在等待超级块,
那么队列中第一个进程就允许运行,得到超级块的排它控制权,进行它的文件操作
。进程的数据写到新的数据块,如果数据块填满,整个过程重复进行,再分配其它
数据块

9.2 The Virtual File System (虚拟文件系统 VFS )

    图 9.4 显示了 Linux 核心的虚拟文件系统和它的真实的文件系统之间的关系
。虚拟文件系统必须管理任何时间安装的所有的不同的文件系统。为此它管理描述
整个文件系统(虚拟)和各个真实的、安装的文件系统的数据结构。

    相当混乱的是, VFS 也使用术语超级块和 inode 来描述系统的文件,和
EXT2 文件系统使用的超级块和 inode 的方式非常相似。象 EXT2 的 inode ,

VFS 的 inode 描述系统中的文件和目录:虚拟文件系统的内容和拓扑结构。从现
在开始,为了避免混淆,我会用 VFS inode 和 VFS 超级块以便同 EXT2 的 inode
和超级块区分开来。

参见 fs/*

    当每一个文件系统初始化的时候,它自身向 VFS 登记。这发生在系统启动操
作系统初始化自身的时候。真实的文件系统自身建立在内核中或者是作为可加载的
模块。文件系统模块在系统需要的时候加载,所以,如果 VFAT 文件系统用核心模
块的方式实现,那么它只有在一个 VFAT 文件系统安装的时候才加载。当一个块设
备文件系统安装的时候,(包括 root 文件系统), VFS 必须读取它的超级块。
每一个文件系统类型的超级块的读取例程必须找出这个文件系统的拓扑结构,并把
这些信息映射到一个 VFS 超级块的数据结构上。 VFS 保存系统中安装的文件系统
的列表和它们的 VFS 超级块列表。每一个 VFS 超级块包括了文件系统的信息和完
成特定功能的例程的指针。例如,表示一个安装的 EXT2 文件系统的超级块包括一
个 EXT2 相关的 inode 的读取例程的指针。这个 EXT2 inode 读取例程,象所有
的和文件系统相关的 inode 读取例程一样,填充 VFS inode 的域。每一个 VFS
超级块包括文件系统中的一个 VFS inode 的指针。对于 root 文件系统,这是表
示“ / ”目录的 inode 。这种信息映射对于 EXT2 文件系统相当高效,但是对于
其他文件系统相对效率较低。




    当系统的进程访问目录和文件的时候,调用系统例程,游历系统中的 VFS
inode 。例如再一个目录中输入 ls 或者 cat 一个文件,让 VFS 查找代表这个文
件系统的 VFS inode 。映为系统中的每一个文件和目录都用一个 VFS inode 代表
,所以一些 inode 会被重复访问。这些 inode 保存在 inode cache ,这让对它
们的访问更快。如果一个 inode 不在 inode cache 中,那么必须调用一个和文件
系统相关的例程来读取适当的 inode 。读取这个 inode 的动作让它被放到了
inode cache ,以后对这个 inode 的访问会让它保留在 cache 中。较少使用的
VFS inode 会从这个高速缓存中删除。

参见 fs/inode.c

    所有的 Linux 文件系统使用一个共同的 buffer cache 来缓存底层的设备的
数据缓冲区,这样可以加速对于存放文件系统的物理设备的访问,从而加快对文件
系统的访问。这个 buffer cache 独立于文件系统,集成在 Linux 核心分配、读
和写数据缓冲区的机制中。让 Linux 文件系统独立于底层的介质和支撑的设备驱
动程序有特殊的好处。所有的块结构的设备向 Linux 核心登记,并表现为一个统
一的,以块为基础的,通常是异步的接口。甚至相对复杂的块设备比如 SCSI 设备
也是这样。当真实的文件系统从底层的物理磁盘读取数据的,引起块设备驱动程序
从它们控制的设备上读取物理块。在这个块设备接口中集成了 buffer cache 。当
文件系统读取了块的时候,它们被存放到了所有的文件系统和 Linux 核心共享的
全局的 buffer cache 中。其中的 buffer (缓冲区)用它们的块编号和被读取设

备的一个唯一的标识符来标记。所以,如果相同的数据经常需要,它会从
buffer cache 中读取,而不是从磁盘读取(会花费更多时间)。一些设备支持超
前读( read ahead ),数据块会预先读取,以备以后可能的读取。

参见 fs/buffer.c

    VFS 也保存了一个目录查找的缓存,所以一个常用的目录的 inode 可以快速
找到。作为一个试验,试着对于你最近没有列表的目录列表。第一次你列表的时候
,你会注意到短暂的停顿,当时第二次你列表的时候,结果会立即出来。目录缓存
本身不存储目录里的 inode ,这是 inode cache 负责的,目录缓存只是存储目录
项目全称和它们的 inode 编号。

参见 fs/dcache.c

9.2.1 The VFS Superblock ( VFS 超级块)

每一个安装的文件系统都用 VFS 超级块表示。除了其它信息, VFS 超级块包括:


参见 include/linux/fs.h

Device 这是包含文件系统的块设备的设备标识符。例如, /dev/hda1 ,系统中的

第一个 IDE 磁盘,设备标识符是 0x301

Inode pointers 其中的 mounted inode 指针指向该文件系统的第一个 inode 。
Covered inode 指针指向文件系统安装到的目录的 inode 。对于 root 文件系统
,它的 VFS 超级块中没有 covered 指针。

Blocksize 文件系统块的字节大小,例如 1024 字节。

Superblock operations 指向一组本文件系统超级块例程的指针。除了其他类型之
外, VFS 使用这些例程读写 inode 和超级块

File System type 指向这个安装的文件系统的 file_system_type 数据结构的一
个指针

File System Specific 指向这个文件系统需要的信息的一个指针

9.2.2 The VFS Inode

象 EXT2 文件系统, VFS 中每一个文件、目录等等都用一个且只用一个 VFS
inode 代表。每一个 VFS inode 中的信息使用文件系统相关的例程从底层的文件
系统中获取。 VFS inode 只在核心的内存中存在,只要对系统有用,就一直保存
在 VFS inode cache 中。除了其它信息, VFS inode 包括一些域:


参见 include/linux/fs.h

device 存放这个文件(或这个 VFS inode 代表的其它实体)的设备的设备标识符


Inode nunber 这个 inode 的编号,在这个文件系统中唯一。 Device 和 inode
number 的组合在整个虚拟文件系统中是唯一的。

Mode 象 EXT2 一样,这个域描述这个 VFS inode 代表的东西和对它的访问权限。


User ids 属主标识符

Times 创建、修改和写的时间

Block size 这个文件的块的字节大小,例如 1024 字节

Inode operations 指向一组例程地址的指针。这些例程和文件系统相关,执行对
于这个 inode 的操作,例如 truncate 这个 inode 代表的文件

Count 系统组件当前使用这个 VFS inode 的数目。 Count 0 意味着这个 inode

是空闲,可以废弃或者重用。

Lock 这个域用于锁定这个 VFS inode 。例如当从文件系统读取它的时

TOP

发新话题