第八十七章:ext4 之基
斩灵期涉及内核源码:
一
林小源在森林深处发现了一棵巨树。
这棵树比周围的古树都要粗壮——树干的直径足有十丈,树皮上的纹路深如沟壑。树干上刻着"ext4"三个字,字迹苍劲有力,像是被闪电劈出来的。
"你就是 ext4?"林小源仰头问道。
巨树发出一声低沉的笑,声音像远处的雷鸣:"我是这片森林的主人。Linux 世界里,七成以上的文件系统都是我的子民。"
林小源走近树干,伸手触摸。树皮粗糙,每一道纹路都是一条信息。他闭上眼睛,神识渗入树干内部——眼前的景象让他屏住了呼吸。
树干内部不是实心的,而是一个精密的分区结构。最外面是一层薄薄的"Boot Block",只有 1KB。里面是一个个整齐排列的方阵,每个方阵就是一个"Block Group"。
"每个 Block Group 里有什么?"他问。
"Superblock 的备份,"巨树说,"Group Descriptors、Block Bitmap、Inode Bitmap、Inode Table、Data Blocks。一个方阵就是一个小王国——有自己的 superblock 副本,自己的 bitmap,自己的数据区。"
林小源看到 Block Bitmap 上无数个亮点,每个亮点代表一个数据块。有的亮着——已分配;有的暗着——空闲。
"这就是磁盘布局,"他低声说。
"对,"巨树说,"ext4 的磁盘被分成无数个 Block Group。每个 Group 独立管理自己的空间。坏了一个 Group,其他的不受影响。"
二
林小源在巨树的树干上发现了一道裂缝。
裂缝里面露出一个奇特的结构——不是传统的间接块指针,而是一个紧凑的树形结构,树的每个节点都记录着"起始块号""长度""偏移"。
"这是 extent,"巨树说,声音带着一丝骄傲,"ext4 的杀手锏。"
"和传统的间接块有什么区别?"
"间接块是这样的,"巨树说,"一个 inode 里有 12 个直接块指针,一个间接块指针,一个二级间接块指针,一个三级间接块指针。要找第 1000 个块,你得先读间接块,再读二级间接块——两层跳转。"
"extent 呢?"
"extent 说:'从第 100 块开始,连续 500 块都是我的。'一个 extent 结构就能描述连续的数据块。大文件只需要几个 extent,不需要几百个间接块指针。"
林小源恍然大悟:"减少了元数据开销。"
"不仅如此,"巨树说,"extent 还能描述空洞。如果文件中间有一段没有写过,extent 可以跳过这段——不分配磁盘块。sparse file 就是这么实现的。"
林小源蹲下来,在地上画了两个对比图:左边是一串间接块指针,层层嵌套;右边是三个 extent,每个 extent 用起始块号和长度就能描述一大片连续区域。
"连续比分散高效,"他说。
"当然,"巨树说,"磁盘喜欢顺序读写。extent 减少了随机访问,提高了性能。"
三
林小源在巨树的根部发现了一个发光的池子。
池子里的液体是透明的,但表面有一层薄薄的油膜,油膜上不断有微小的气泡升起又破裂。
"这是延迟分配池,"巨树说,"ext4 的另一个妙招。"
"延迟分配?"
"写入的时候,数据先进这个池子,"巨树说,"不立即分配磁盘块。等到回写的时候,内核已经知道了所有数据的大小和位置——可以一次性分配一大片连续的块。"
林小源蹲下来,观察池子里的气泡。每个气泡都是一个待写入的数据块,它们在池子里漂浮,等待被分配到磁盘上的位置。
"如果不延迟呢?"
"写一个字节就分配一个块,"巨树说,"碎片化严重。大文件被切成无数小块,散布在磁盘各处。读取的时候,磁头要到处跳——性能暴跌。"
林小源站起身,拍了拍手上的泥土。他看着巨树——ext4 比他想象的要复杂得多。extent、延迟分配、日志、多块分配——每一个特性都是多年打磨的结果。
"你很老了?"他问。
"ext3 的儿子,"巨树说,"ext2 的孙子。三代人的积累,都在我身上。"
他蹲下来数了数巨树的年轮——哪里是什么年轮,分明是一个个 block group,各自管着自己的一亩三分地。
/*
* ext4 的磁盘布局:
*
* Boot Block | Block Group 0 | Block Group 1 | ...
*
* 每个 Block Group 包含:
* - Superblock(备份)
* - Group Descriptors
* - Block Bitmap
* - Inode Bitmap
* - Inode Table
* - Data Blocks
*
* ext4 的特性:
* - 最大文件: 16TB
* - 最大文件系统: 1EB
* - 日志(JBD2)
* - extent(替代间接块)
* - 延迟分配
* - 多块分配
*/
printf("=== ext4 — 最常用的文件系统 ===\n\n");
printf("ext4 磁盘布局:\n");
printf(" ┌─────────────────────────────────────┐\n");
printf(" │ Boot Block (1KB) │\n");
printf(" ├─────────────────────────────────────┤\n");
printf(" │ Block Group 0 │\n");
printf(" │ ├── Superblock │\n");
printf(" │ ├── Group Descriptors │\n");
printf(" │ ├── Block Bitmap │\n");
printf(" │ ├── Inode Bitmap │\n");
printf(" │ ├── Inode Table │\n");
printf(" │ └── Data Blocks │\n");
printf(" ├─────────────────────────────────────┤\n");
printf(" │ Block Group 1 │\n");
printf(" │ └── ... │\n");
printf(" └─────────────────────────────────────┘\n\n");
printf("--- ext4 的特性 ---\n");
printf("1. extent\n");
printf(" 替代传统的间接块\n");
printf(" 一个 extent 描述连续的数据块\n");
printf(" 减少元数据开销\n\n");
printf("2. 延迟分配\n");
printf(" 写入时不立即分配磁盘块\n");
printf(" 等到写回时再分配\n");
printf(" 减少碎片\n\n");
printf("3. 日志(JBD2)\n");
printf(" 记录元数据操作\n");
printf(" 崩溃后快速恢复\n\n");
printf("4. 多块分配\n");
printf(" 一次分配多个连续块\n");
printf(" 提高大文件性能\n\n");
printf("--- inode 结构 ---\n");
printf("传统 ext2/3 inode:\n");
printf(" 12 个直接块指针\n");
printf(" 1 个间接块指针\n");
printf(" 1 个二级间接块指针\n");
printf(" 1 个三级间接块指针\n\n");
printf("ext4 inode:\n");
printf(" 使用 extent tree\n");
printf(" 每个 extent 描述连续块\n");
printf(" 更高效\n\n");
printf("--- ext4 vs ext3 ---\n");
printf("ext3: 最大 2TB 文件, 16TB 文件系统\n");
printf("ext4: 最大 16TB 文件, 1EB 文件系统\n");
printf("ext4: extent, 延迟分配, 日志校验\n");#include <stdio.h>
/*
* ext4 的磁盘布局:
*
* Boot Block | Block Group 0 | Block Group 1 | ...
*
* 每个 Block Group 包含:
* - Superblock(备份)
* - Group Descriptors
* - Block Bitmap
* - Inode Bitmap
* - Inode Table
* - Data Blocks
*
* ext4 的特性:
* - 最大文件: 16TB
* - 最大文件系统: 1EB
* - 日志(JBD2)
* - extent(替代间接块)
* - 延迟分配
* - 多块分配
*/
int main() {
printf("=== ext4 — 最常用的文件系统 ===\n\n");
printf("ext4 磁盘布局:\n");
printf(" ┌─────────────────────────────────────┐\n");
printf(" │ Boot Block (1KB) │\n");
printf(" ├─────────────────────────────────────┤\n");
printf(" │ Block Group 0 │\n");
printf(" │ ├── Superblock │\n");
printf(" │ ├── Group Descriptors │\n");
printf(" │ ├── Block Bitmap │\n");
printf(" │ ├── Inode Bitmap │\n");
printf(" │ ├── Inode Table │\n");
printf(" │ └── Data Blocks │\n");
printf(" ├─────────────────────────────────────┤\n");
printf(" │ Block Group 1 │\n");
printf(" │ └── ... │\n");
printf(" └─────────────────────────────────────┘\n\n");
printf("--- ext4 的特性 ---\n");
printf("1. extent\n");
printf(" 替代传统的间接块\n");
printf(" 一个 extent 描述连续的数据块\n");
printf(" 减少元数据开销\n\n");
printf("2. 延迟分配\n");
printf(" 写入时不立即分配磁盘块\n");
printf(" 等到写回时再分配\n");
printf(" 减少碎片\n\n");
printf("3. 日志(JBD2)\n");
printf(" 记录元数据操作\n");
printf(" 崩溃后快速恢复\n\n");
printf("4. 多块分配\n");
printf(" 一次分配多个连续块\n");
printf(" 提高大文件性能\n\n");
printf("--- inode 结构 ---\n");
printf("传统 ext2/3 inode:\n");
printf(" 12 个直接块指针\n");
printf(" 1 个间接块指针\n");
printf(" 1 个二级间接块指针\n");
printf(" 1 个三级间接块指针\n\n");
printf("ext4 inode:\n");
printf(" 使用 extent tree\n");
printf(" 每个 extent 描述连续块\n");
printf(" 更高效\n\n");
printf("--- ext4 vs ext3 ---\n");
printf("ext3: 最大 2TB 文件, 16TB 文件系统\n");
printf("ext4: 最大 16TB 文件, 1EB 文件系统\n");
printf("ext4: extent, 延迟分配, 日志校验\n");
return 0;
}道藏笔记
内核启示
ext4 是 Linux 世界用得最多的文件系统,七成以上的 Linux 机器都在用它。
它的磁盘被分成一个个 Block Group,每个 Group 里都有 superblock 的备份、Group Descriptor、Block Bitmap、Inode Bitmap、Inode Table 和数据区。坏了一个 Group,其他的不受影响——这叫故障隔离。
ext4 相比 ext3 最大的进步是 extent。传统的间接块指针要层层跳转才能找到数据块,extent 直接说"从第 100 块开始,连续 500 块都是我的",一个结构就描述了一大片连续区域。再加上延迟分配——写入时不急着分配磁盘块,等到回写时再统一分配,减少碎片。这两个特性加在一起,大文件的性能比 ext3 好得多。
日志(JBD2)保证了崩溃后能快速恢复,多块分配让大文件的写入更高效。ext3 的儿子,ext2 的孙子,三代人的积累都在它身上。
ext4 之试
ext4 写入时不急着分配磁盘块、等回写再统一安排的特性叫什么?