3. ext4 文件系统原理(上):磁盘布局深度解析

ext4 是 Linux 最广泛使用的日志文件系统,其核心设计围绕 块组(Block Group) 展开。理解 ext4 的磁盘布局,是定制文件系统、优化性能、甚至修复损坏文件系统的基础。本节将深入剖析 ext4 的物理结构,从超级块到数据块位图,逐一拆解每个关键组件。

学习提示: ext4 的磁盘布局是“元数据驱动”的典范。所有数据块的管理都依赖于预先规划好的元数据区域。请重点关注块组内各元数据块的排列顺序及其作用。

3.1 整体磁盘布局:块组(Block Group)

ext4 将整个分区划分为若干个大小相同的 块组(Block Group)。每个块组管理连续的物理块,并包含该组内所有文件的元数据。这种设计将元数据分散存储,避免了单点故障,并支持并行访问。

每个块组的大小通常为 128MB(当块大小为 4KB 时),但可以通过 mke2fs 的 -g 参数调整。块组内部的结构是固定的,但某些元数据块(如超级块和块组描述符表)会在多个块组中冗余备份。

ext4 磁盘布局:块组划分 块组 0 (引导块 + 超级块) 块组 1 (超级块备份) 块组 2 (无备份) 块组 3 (块组描述符备份) ... 块组 N (最后一个) 说明:块组 0 和 1 包含超级块备份;块组 0 和块组编号为 3、5、7 的幂次方的块组包含块组描述符表备份。

3.2 块组内部结构:六大核心组件

每个块组内部,元数据块与数据块按固定顺序排列。以下是一个标准块组(非稀疏模式)的内部布局:

组件名称 占用块数 功能描述 冗余策略
超级块(Superblock) 1 块 存储文件系统全局信息(块大小、inode 数量、状态等) 块组 0、1 及部分组有备份
块组描述符表(GDT) N 块(取决于块组数) 存储每个块组的描述符(位图块号、inode 表起始块号等) 块组 0 及 3、5、7 的幂次方组有备份
数据块位图(Block Bitmap) 1 块 记录本组内数据块的分配状态(0=空闲,1=已用) 仅本组一份
inode 位图(Inode Bitmap) 1 块 记录本组内 inode 的分配状态 仅本组一份
inode 表(Inode Table) M 块(取决于 inode 数) 存储所有 inode 数据结构(文件元数据) 仅本组一份
数据块(Data Blocks) 剩余所有块 存储文件的实际内容 无冗余
关键注意: 超级块和块组描述符表的损坏会导致整个文件系统不可挂载。因此,ext4 在多个块组中维护了这些关键元数据的冗余备份。使用 fsck 修复时,会优先使用备份恢复主超级块。

3.3 超级块(Superblock)深度解析

超级块是 ext4 文件系统的“身份证”和“配置手册”。它位于每个块组的起始位置(块组 0 的偏移 1024 字节处),大小为 1024 字节。超级块中包含了文件系统的所有全局参数。

关键字段包括:

  • s_inodes_count:文件系统中 inode 的总数。
  • s_blocks_count_lo:文件系统中块的总数(低 32 位)。
  • s_r_blocks_count_lo:为 root 用户保留的块数。
  • s_log_block_size:块大小的对数(1024 << s_log_block_size)。
  • s_log_cluster_size:簇大小的对数(用于 bigalloc 特性)。
  • s_blocks_per_group:每个块组包含的块数。
  • s_inodes_per_group:每个块组包含的 inode 数。
  • s_mtime:文件系统最后挂载时间。
  • s_wtime:文件系统最后写入时间。
  • s_magic:魔数,固定为 0xEF53。
  • s_state:文件系统状态(有效/错误)。
  • s_feature_compat / s_feature_incompat / s_feature_ro_compat:特性标志位,决定文件系统支持哪些扩展功能。
超级块关键字段结构 超级块 (1024 字节) 魔数 (0xEF53) 块大小 (4KB) inode 总数 特性标志 状态/时间戳 说明:超级块位于块组 0 的偏移 1024 字节处,占用 1 个块。 特性标志(feature_flags)决定了文件系统是否支持 flex_bg、extents、journal 等高级特性。

3.4 块组描述符表(Group Descriptor Table, GDT)

块组描述符表紧跟在超级块之后。每个块组对应一个 块组描述符(Group Descriptor),大小为 32 字节(ext4 扩展后为 64 字节)。GDT 是定位所有元数据块的“索引”。

每个块组描述符包含以下关键字段:

  • bg_block_bitmap_lo:数据块位图所在的块号。
  • bg_inode_bitmap_lo:inode 位图所在的块号。
  • bg_inode_table_lo:inode 表起始块号。
  • bg_free_blocks_count_lo:本组空闲块数。
  • bg_free_inodes_count_lo:本组空闲 inode 数。
  • bg_used_dirs_count_lo:本组目录数。
  • bg_flags:块组标志(如是否包含 inode 表、是否校验和等)。
性能洞察: 当文件系统创建时,GDT 的大小是固定的。如果后期需要扩展文件系统(resize),且新块组数超过原 GDT 的容量,ext4 会使用 resize_inode 特性,在保留块中动态扩展 GDT。

3.5 位图(Bitmap):数据块位图与 inode 位图

位图是 ext4 实现空间管理的核心机制。每个块组包含两个位图:

  • 数据块位图(Block Bitmap):每个 bit 对应本组内的一个数据块。bit=0 表示空闲,bit=1 表示已分配。
  • inode 位图(Inode Bitmap):每个 bit 对应本组内的一个 inode。bit=0 表示空闲,bit=1 表示已分配。

位图的大小固定为 1 个块(4KB),因此可以管理 4KB × 8 = 32768 个资源。这正好对应了默认的 blocks_per_group 和 inodes_per_group 值。

位图工作机制:数据块分配示例 数据块位图 (1 块 = 4096 字节 = 32768 bit) 0 0 1 0 1 1 ...... 空闲 空闲 已用 空闲 已用 已用 说明:位图中 bit=0 表示对应数据块空闲,bit=1 表示已分配。分配时扫描位图寻找第一个 0 位。

3.6 inode 表(Inode Table)

inode 表是存储文件元数据的核心区域。每个文件或目录对应一个 inode 结构。inode 表由连续的块组成,每个块中存放多个 inode 结构。

ext4 的 inode 结构大小为 256 字节(默认),包含以下关键信息:

  • i_mode:文件类型和权限。
  • i_uid / i_gid:属主和属组 ID。
  • i_size_lo:文件大小(低 32 位)。
  • i_atime / i_ctime / i_mtime:访问、创建、修改时间。
  • i_blocks_lo:文件占用的块数(以 512 字节为单位)。
  • i_block[EXT4_N_BLOCKS]:数据块指针数组(15 个条目,支持直接、间接、双重间接、三重间接寻址)。
  • i_extent:当启用 extent 特性时,使用 extent 树替代块指针数组。
ext4 关键优化: 传统 ext2/3 使用间接块指针(最多支持到 3 重间接),而 ext4 引入了 Extent(区段) 机制。一个 extent 可以描述一段连续的物理块范围(起始块号 + 长度),极大减少了大文件的元数据开销。extent 树是一棵 B+ 树,根节点直接存储在 inode 的 i_block 区域中。

3.7 数据块(Data Blocks)与寻址方式

数据块是文件实际内容的存储区域。ext4 支持两种数据块寻址方式:

寻址方式 适用场景 最大文件大小(4KB 块) 优点
间接块映射 小文件 / 兼容模式 约 16TB 兼容 ext2/3
Extent 树 大文件 / 默认模式 约 16TB(48 位块号) 元数据少、碎片少、性能高

在默认的 extent 模式下,inode 中的 i_block 区域(60 字节)被重新解释为 extent 树的根节点。每个 extent 条目(ext4_extent)包含:

  • ee_block:文件内的逻辑块号。
  • ee_len:extent 覆盖的块数。
  • ee_start_hi / ee_start_lo:物理块起始号(48 位)。
Extent 树结构:大文件寻址示例 inode Extent 根节点 内部节点 叶子节点 数据块 100 数据块 101 数据块 102 数据块 103 ...... 说明:Extent 树是一棵 B+ 树。根节点存储在 inode 的 i_block 区域中。 每个叶子节点包含多个 extent 条目,每个条目描述一段连续的物理块范围。 例如:extent { ee_block=0, ee_len=4, ee_start=100 } 表示逻辑块 0-3 映射到物理块 100-103。

3.8 块组布局实战:使用 debugfs 查看

理论需要实践验证。使用 debugfs 工具可以直接查看 ext4 的磁盘布局:

# 查看超级块信息
sudo debugfs -R "stats" /dev/sda1

# 查看块组 0 的描述符
sudo debugfs -R "stat /" /dev/sda1

# 查看 inode 号 2(根目录)的详细信息
sudo debugfs -R "stat <2>" /dev/sda1

# 查看块组 0 的块位图
sudo debugfs -R "dump_block_bitmap 0" /dev/sda1
实践建议: 创建一个 100MB 的空 ext4 文件系统镜像,使用 dd 和 hexdump 配合 debugfs,可以直观地看到超级块、GDT、位图、inode 表在磁盘上的物理偏移。这是理解磁盘布局最有效的方法。

3.9 本章小结

ext4 的磁盘布局是“分组管理、元数据冗余、位图驱动”的经典设计。核心要点如下:

  • 块组是 ext4 的基本管理单元,每个块组包含完整的元数据副本(超级块、GDT 有冗余)。
  • 超级块存储全局参数,损坏后可使用备份恢复。
  • 块组描述符表是定位所有元数据块的索引。
  • 位图以 O(1) 的时间复杂度管理块和 inode 的分配状态。
  • inode 表存储文件元数据,ext4 使用 extent 树优化大文件寻址。
  • 数据块是文件内容的最终归宿,通过 inode 中的指针或 extent 树访问。