3. ext4 文件系统原理(上):磁盘布局深度解析
ext4 是 Linux 最广泛使用的日志文件系统,其核心设计围绕 块组(Block Group) 展开。理解 ext4 的磁盘布局,是定制文件系统、优化性能、甚至修复损坏文件系统的基础。本节将深入剖析 ext4 的物理结构,从超级块到数据块位图,逐一拆解每个关键组件。
3.1 整体磁盘布局:块组(Block Group)
ext4 将整个分区划分为若干个大小相同的 块组(Block Group)。每个块组管理连续的物理块,并包含该组内所有文件的元数据。这种设计将元数据分散存储,避免了单点故障,并支持并行访问。
每个块组的大小通常为 128MB(当块大小为 4KB 时),但可以通过 mke2fs 的 -g 参数调整。块组内部的结构是固定的,但某些元数据块(如超级块和块组描述符表)会在多个块组中冗余备份。
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) | 剩余所有块 | 存储文件的实际内容 | 无冗余 |
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:特性标志位,决定文件系统支持哪些扩展功能。
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 表、是否校验和等)。
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 值。
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 树替代块指针数组。
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 位)。
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
dd 和 hexdump 配合 debugfs,可以直观地看到超级块、GDT、位图、inode 表在磁盘上的物理偏移。这是理解磁盘布局最有效的方法。
3.9 本章小结
ext4 的磁盘布局是“分组管理、元数据冗余、位图驱动”的经典设计。核心要点如下:
- 块组是 ext4 的基本管理单元,每个块组包含完整的元数据副本(超级块、GDT 有冗余)。
- 超级块存储全局参数,损坏后可使用备份恢复。
- 块组描述符表是定位所有元数据块的索引。
- 位图以 O(1) 的时间复杂度管理块和 inode 的分配状态。
- inode 表存储文件元数据,ext4 使用 extent 树优化大文件寻址。
- 数据块是文件内容的最终归宿,通过 inode 中的指针或 extent 树访问。