#深入理解 Linux Ext 系列文件系统:从磁盘物理结构到 inode 映射
摘要:本文基于 Linux 内核经典的 Ext2/3/4 文件系统文档,深入浅出地讲解了磁盘的物理与逻辑结构、CHS 与 LBA 寻址方式的演变、分区与块的概念,并重点剖析了 inode、超级块、块组描述符等核心数据结构。通过本文,你将彻底明白“文件”在硬盘上究竟是如何存储和管理的。
一、硬件基石:理解磁盘的物理与逻辑世界
在操作系统能够管理文件之前,我们必须先理解数据到底存在哪里。机械硬盘(HDD)作为计算机中唯一的机械设备,其工作原理决定了文件系统的底层设计。
1.1 磁盘的物理结构:三维的存储空间
机械磁盘由多个盘片(Platter)组成,每个盘片上下两面各有一个磁头(Head)。数据存储在盘片的同心圆轨道上,称为磁道(Track)。所有盘片上半径相同的磁道组合在一起,构成了柱面(Cylinder)。而每个磁道又被划分为若干个扇区(Sector),这是磁盘读写的最小物理单位,通常为512 字节。
定位一个扇区需要三个坐标:
- C (Cylinder):柱面号
- H (Head):磁头号
- S (Sector):扇区号
这就是著名的CHS 寻址方式。早期的 BIOS 使用 8bit 存磁头、10bit 存柱面、6bit 存扇区,导致 CHS 模式最大仅支持约 8GB 的硬盘容量,这在今天显然不够用。
1.2 逻辑结构的进化:LBA 线性地址
为了突破容量限制并简化操作系统的开发,现代磁盘引入了LBA (Logical Block Address)逻辑块地址。
- 概念转化:想象把磁盘所有的扇区像磁带一样“拉直”,形成一个巨大的一维数组。每个扇区都有一个唯一的下标,从 0 开始编号,这就是 LBA。
- 转换机制:操作系统只需关心 LBA(例如:“我要读第 1000 号扇区”),具体的 CHS 转换由磁盘内部的固件(伺服系统)自动完成。
- 计算公式:
- $LBA = C \times (H_{total} \times S_{track}) + H \times S_{track} + (S - 1)$
*其中 $S$ 通常从 1 开始,而 LBA 从 0 开始。
- $LBA = C \times (H_{total} \times S_{track}) + H \times S_{track} + (S - 1)$
💡 博客小贴士:对于开发者而言,从此以后可以将磁盘视为一个巨大的
char[]数组,LBA 就是数组下标。这种抽象极大地降低了文件系统开发的复杂度。
二、文件系统的引入:块、分区与 inode
有了磁盘,操作系统如何高效地存取数据?直接按扇区读写效率太低,因此引入了更高级的抽象概念。
2.1 块(Block):文件系统的最小单位
操作系统不会一次只读512 字节,而是一次性读取连续的多个扇区,这个集合称为块(Block)。
- 大小:通常在格式化时确定,最常见为4KB(即 8 个扇区)。
- 意义:块是文件存取的最小单位。即使一个文件只有 1 字节,它也会占用整整一个块(4KB),剩余空间被浪费(内部碎片)。
2.2 分区(Partition):逻辑隔离
一块物理磁盘可以被划分为多个逻辑区域,即分区(如 Windows 的 C 盘、D 盘,Linux 的/dev/sda1)。
- 本质:分区本质上是规定了起始柱面和结束柱面的范围。
- 独立性:每个分区可以格式化为不同的文件系统,拥有独立的超级块和管理结构。
2.3 核心革命:inode(索引节点)
在 Linux 中,文件 = 内容 + 属性。
- 内容:存储在数据块(Data Block)中。
- 属性:包括权限、所有者、大小、时间戳等元数据。这些属性存储在哪里?inode。
关键知识点:
- 分离存储:文件名并不存储在 inode 中!文件名存储在目录的数据块里,目录项将“文件名”映射到"inode 号”。
- 唯一标识:在一个分区内,每个文件对应唯一的 inode 号。
- 固定大小:无论文件多大,其 inode 结构体大小是固定的(Ext2 中通常为 128 字节)。
// 简化的 inode 结构示意 struct ext2_inode { __le16 i_mode; // 文件类型和权限 __le32 i_size; // 文件大小 __le32 i_block[15];// 指向数据块的指针数组(核心!) // ... 其他时间、用户 ID 等信息 };三、Ext2 文件系统架构详解
Ext2(及其后继者 Ext3/Ext4)是 Linux 最经典的文件系统。它将分区划分为若干个结构相同的块组(Block Group),这种设计既提高了性能,又增强了容错性。
###3.1 宏观布局
一个典型的 Ext2 分区布局如下:
- Boot Block:启动块(1KB),存放引导信息,文件系统不可修改。
- Block Groups:随后是若干个块组,每个块组结构完全一致。
3.2 块组内部构成(五脏俱全)
每个块组都包含以下关键部分,它们协同工作来管理数据:
| 组件 | 名称 | 作用 |
|---|---|---|
| Super Block | 超级块 | 文件系统的总指挥。记录整个分区的总块数、总 inode 数、空闲数量、块大小等全局信息。为了防止损坏,它在多个块组中都有备份。 |
| GDT | 块组描述符表 | 块组的身份证。描述当前块组的局部信息:本组的 inode 表从哪里开始?数据块从哪里开始?还有多少空闲块? |
| Block Bitmap | 块位图 | 数据块的地图。每一位(bit)代表一个数据块,0 表示空闲,1 表示已占用。 |
| Inode Bitmap | inode 位图 | inode 的地图。每一位代表一个 inode,标记其是否可用。 |
| Inode Table | inode 表 | 属性的仓库。连续存放该组内所有文件的 inode 结构体。 |
| Data Blocks | 数据区 | 内容的家园。实际存储文件内容和目录列表的地方。 |
3.3 文件数据的查找过程(路径解析)
当你在终端输入cat /home/user/test.txt时,内核发生了什么?
- 路径解析:从根目录
/的 inode 开始,读取其数据块,找到home目录对应的 inode 号。 - 逐级跳转:进入
home目录的 inode,读取数据块,找到user目录的 inode 号;再进入user,找到test.txt的inode 号(假设是 263715)。 - 定位 inode:根据 inode 号计算出它位于哪个块组的 inode 表中,将其读入内存。
- 获取数据块指针:查看 inode 结构中的
i_block[15]数组。- 前 12 个指针:直接指向数据块(适合小文件)。
- 第 13 个指针:一级间接索引(指向一个存有块号的块)。
- 第 14 个指针:二级间接索引。
- 第 15 个指针:三级间接索引(支持超大文件)。
- 读取数据:根据指针找到对应的 Data Block,读出文件内容。
四、深度思考与总结
为什么 Ext 系列要设计成“块组”结构?
- 局部性原理:将 inode 表、位图和数据块放在同一个块组内,可以减少磁头移动距离(寻道时间),提高读写效率。
- 容错性:超级块和块组描述符在每个块组都有备份。如果磁盘某处出现物理坏道破坏了主超级块,系统可以利用备份恢复文件系统结构。
Ext3/Ext4 做了什么改进?
虽然本文以 Ext2 为例,但 Ext3 和 Ext4 的核心结构(inode, block group, bitmap)并未改变。
- Ext3:主要增加了**日志(Journaling)**功能,防止断电导致文件系统元数据不一致。
- Ext4:引入了Extent(连续块映射,替代部分间接索引)、延迟分配等技术,进一步提升大文件性能和减少碎片。