让我们大概地从 EXT4 的历史、特性以及最佳实践这几个方面来学习它和之前的几代 EXT 文件系统有何不同。
在之前关于 Linux 文件系统的文章里,我写过一篇 Linux 文件系统介绍 和一些更高级的概念例如 一切都是文件。现在我想要更深入地了解 EXT 文件系统的特性的详细内容,但是首先让我们来回答一个问题,“什么样才算是一个文件系统 ?” 一个文件系统应该涵盖以下所有特点:
- 数据存储: 对于任何一个文件系统来说,一个最主要的功能就是能够被当作一个结构化的容器来存储和获取数据。
- 命名空间: 命名空间是一个提供了用于命名与组织数据的命名规则和数据结构的方法学。
- 安全模型: 一个用于定义访问权限的策略。
- API: 操作这个系统的对象的系统功能调用,这些对象诸如目录和文件。
- 实现: 能够实现以上几点的软件。
本文内容的讨论主要集中于上述几点中的第一项,并探索为一个 EXT 文件系统的数据存储提供逻辑框架的元数据结构。
EXT 文件系统历史
虽然 EXT 文件系统是为 Linux 编写的,但其真正起源于 Minix 操作系统和 Minix 文件系统,而 Minix 最早发布于 1987,早于 Linux 5 年。如果我们从 EXT 文件系统大家族的 Minix 起源来观察其历史与技术发展那么理解 EXT4 文件系统就会简单得多。
Minix
当 Linux Torvalds 在写最初的 Linux 内核的时候,他需要一个文件系统但是他又不想自己写一个。于是他简单地把 Minix 文件系统 加了进去,这个 Minix 文件系统是由 Andrew S. Tanenbaum 写的,同时它也是 Tanenbaum 的 Minix 操作系统的一部分。Minix 是一个类 Unix 风格的操作系统,最初编写它的原因是用于教育用途。Minix 的代码是自由可用的并有适当的许可协议,所以 Torvalds 可以把它用 Linux 的最初版本里。
Minix 有以下这些结构,其中的大部分位于生成文件系统的分区中:
- 引导扇区 是硬盘安装后的第一个扇区。这个引导块包含了一个非常小的引导记录和一个分区表。
- 每一个分区的第一个块都是一个包含了元数据的超级块(superblock) ,这些元数据定义了其他的文件系统结构并将其定位于物理硬盘的具体分区上。
- 一个 inode 位图块 决定了哪些 inode 是在使用中的,哪一些是未使用的。
- inode 在硬盘上有它们自己的空间。每一个 inode 都包含了一个文件的信息,包括其所处的数据块的位置,也就是该文件所处的区域。
- 一个 区位图 用于保持追踪数据区域的使用和未使用情况。
- 一个 数据区, 这里是数据存储的地方。
对上述了两种位图类型来说,一个位(bit)表示一个指定的数据区或者一个指定的 inode。 如果这个位是 0 则表示这个数据区或者这个 inode 是未使用的,如果是 1 则表示正在使用中。
那么,inode 又是什么呢 ? 就是 index-node(索引节点)的简写。 inode 是位于磁盘上的一个 256 字节的块,用于存储和该 inode 对应的文件的相关数据。这些数据包含了文件的大小、文件的所有者和所属组的用户 ID、文件模式(即访问权限)以及三个时间戳用于指定:该文件最后的访问时间、该文件的最后修改时间和该 inode 中的数据的最后修改时间。
同时,这个 inode 还包含了位置数据,指向了其所对应的文件数据在硬盘中的位置。在 Minix 和 EXT 1-3 文件系统中,这是一个数据区和块的列表。Minix 文件系统的 inode 支持 9 个数据块,包括 7 个直接数据块和 2 个间接数据块。如果你想要更深入的了解,这里有一个优秀的 PDF 详细地描述了 Minix 文件系统结构 。同时你也可以在维基百科上对 inode 指针结构 做一个快速了解。
EXT
原生的 EXT 文件系统 (意即扩展的(extended)) 是由 Rémy Card 编写并于 1992 年与 Linux 一同发行。主要是为了克服 Minix 文件系统中的一些文件大小限制的问题。其中,最主要的结构变化就是文件系统中的元数据。它基于 Unix 文件系统 (UFS),其也被称为伯克利快速文件系统(FFS)。我发现只有很少一部分关于 EXT 文件系统的发行信息是可以被确证的,显然这是因为其存在着严重的问题,并且它很快地被 EXT2 文件系统取代了。
EXT2
EXT2 文件系统 就相当地成功,它在 Linux 发行版中存活了多年。它是我在 1997 年开始使用 Red Hat Linux 5.0 时接触的第一个文件系统。实际上,EXT2 文件系统有着和 EXT 文件系统基本相同的元数据结构。然而 EXT2 更高瞻远瞩,因为其元数据结构之间留有很多供将来使用的磁盘空间。
和 Minix 类似,EXT2 也有一个引导扇区 ,它是硬盘安装后的第一个扇区。它包含了非常小的引导记录和一个分区表。接着引导扇区之后是一些保留的空间,它填充了引导记录和硬盘驱动器上的第一个分区(通常位于下一个柱面)之间的空间。GRUB2 - 也可能是 GRUB1 - 将此空间用于其部分引导代码。
每个 EXT2 分区中的空间被分为柱面组(cylinder group),它允许更精细地管理数据空间。 根据我的经验,每一组大小通常约为 8MB。 下面的图 1 显示了一个柱面组的基本结构。 柱面中的数据分配单元是块,通常大小为 4K。
图 1: EXT 文件系统中的柱面组的结构
柱面组中的第一个块是一个超级块(superblock),它包含了元数据,定义了其它文件系统的结构并将其定位于物理硬盘的具体分区上。分区中有一些柱面组还会有备用超级块,但并不是所有的柱面组都有。我们可以使用例如 dd 等磁盘工具来拷贝备用超级块的内容到主超级块上,以达到修复损坏的超级块的目的。虽然这种情况不会经常发生,但是在几年前我的一个超级块损坏了,我就是用这种方法来修复的。幸好,我很有先见之明地使用了 dumpe2fs 命令来备份了我的系统上的分区描述符信息。
以下是 dumpe2fs 命令的一部分输出。这部分输出主要是超级块上包含的一些元数据,同时也是文件系统上的前两个柱面组的数据。
- # dumpe2fs /dev/sda1
- Filesystem volume name: boot
- Last mounted on: /boot
- Filesystem UUID: 79fc5ed8-5bbc-4dfe-8359-b7b36be6eed3
- Filesystem magic number: 0xEF53
- Filesystem revision #: 1 (dynamic)
- Filesystem features: has_journal ext_attr resize_inode dir_index filetype needs_recovery extent 64bit flex_bg sparse_super large_file huge_file dir nlink extra_isize
- Filesystem flags: signed_directory_hash
- Default mount options: user_xattr acl
- Filesystem state: clean
- Errors behavior: Continue
- Filesystem OS type: Linux
- Inode count: 122160
- Block count: 488192
- Reserved block count: 24409
- Free blocks: 376512
- Free inodes: 121690
- First block: 0
- Block size: 4096
- Fragment size: 4096
- Group descriptor size: 64
- Reserved GDT blocks: 238
- Blocks per group: 32768
- Fragments per group: 32768
- Inodes per group: 8144
- Inode blocks per group: 509
- Flex block group size: 16
- Filesystem created: Tue Feb 7 09:33:34 2017
- Last mount time: Sat Apr 29 21:42:01 2017
- Last write time: Sat Apr 29 21:42:01 2017
- Mount count: 25
- Maximum mount count: -1
- Last checked: Tue Feb 7 09:33:34 2017
- Check interval: 0 (<none>)
- Lifetime writes: 594 MB
- Reserved blocks uid: 0 (user root)
- Reserved blocks gid: 0 (group root)
- First inode: 11
- Inode size: 256
- Required extra isize: 32
- Desired extra isize: 32
- Journal inode: 8
- Default directory hash: half_md4
- Directory Hash Seed: c780bac9-d4bf-4f35-b695-0fe35e8d2d60
- Journal backup: inode blocks
- Journal features: journal_64bit
- Journal size: 32M
- Journal length: 8192
- Journal sequence: 0x00000213
- Journal start: 0
- Group 0: (Blocks 0-32767)
- Primary superblock at 0, Group descriptors at 1-1
- Reserved GDT blocks at 2-239
- Block bitmap at 240 (+240)
- Inode bitmap at 255 (+255)
- Inode table at 270-778 (+270)
- 24839 free blocks, 7676 free inodes, 16 directories
- Free blocks: 7929-32767
- Free inodes: 440, 470-8144
- Group 1: (Blocks 32768-65535)
- Backup superblock at 32768, Group descriptors at 32769-32769
- Reserved GDT blocks at 32770-33007
- Block bitmap at 241 (bg #0 + 241)
- Inode bitmap at 256 (bg #0 + 256)
- Inode table at 779-1287 (bg #0 + 779)
- 8668 free blocks, 8142 free inodes, 2 directories
- Free blocks: 33008-33283, 33332-33791, 33974-33975, 34023-34092, 34094-34104, 34526-34687, 34706-34723, 34817-35374, 35421-35844, 35935-36355, 36357-36863, 38912-39935, 39940-40570, 42620-42623, 42655, 42674-42687, 42721-42751, 42798-42815, 42847, 42875-42879, 42918-42943, 42975, 43000-43007, 43519, 43559-44031, 44042-44543, 44545-45055, 45116-45567, 45601-45631, 45658-45663, 45689-45695, 45736-45759, 45802-45823, 45857-45887, 45919, 45950-45951, 45972-45983, 46014-46015, 46057-46079, 46112-46591, 46921-47103, 49152-49395, 50027-50355, 52237-52255, 52285-52287, 52323-52351, 52383, 52450-52479, 52518-52543, 52584-52607, 52652-52671, 52734-52735, 52743-53247
- Free inodes: 8147-16288
- Group 2: (Blocks 65536-98303)
- Block bitmap at 242 (bg #0 + 242)