ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

Ext系列文件系统详解:从磁盘寻址到 inode、目录与软硬链接

2026/8/11 3:13:22 拓冰建站 浏览量
Ext系列文件系统详解:从磁盘寻址到 inode、目录与软硬链接

当我们执行touchopenrm时,操作系统究竟修改了磁盘上的哪些内容?要回答这个问题,需要把磁盘寻址、分区、数据块、inode、目录、挂载与链接串成一条完整链路。本文以 Ext2 的经典布局为主线,建立 Ext 系列文件系统的整体认知。

一、从机械磁盘说起

机械硬盘由盘片、主轴、磁头和传动臂等部件组成。盘片表面被划分为磁道,磁道又被划分为扇区;不同盘面上半径相同的磁道可以抽象为柱面。

传统磁盘几何结构常用三个量描述:

  • Cylinder:柱面编号;
  • Head:磁头或盘面编号;
  • Sector:磁道中的扇区编号。

这就是 CHS(Cylinder-Head-Sector)寻址。若已知每个柱面的磁头数为HPC,每条磁道的扇区数为SPT,一种经典换算关系是:

LBA = C × HPC × SPT + H × SPT + (S - 1) C = LBA ÷ (HPC × SPT) H = (LBA mod (HPC × SPT)) ÷ SPT S = (LBA mod SPT) + 1

CHS 中的扇区编号通常从 1 开始,而 LBA 从 0 开始,因此公式中需要减 1 或加 1。

为什么后来使用 LBA

CHS 依赖具体磁盘几何参数,表达范围也受到字段位数限制。LBA(Logical Block Address,逻辑块地址)将磁盘看成线性的扇区数组:

物理视角:盘片 → 磁道 → 扇区 逻辑视角:[0][1][2][3] ... [N-1] ↑ LBA 线性地址

操作系统只需要提供一个 LBA,磁盘控制器和固件负责完成内部定位。现代存储设备不应再按真实 CHS 几何结构理解,CHS 更适合作为认识磁盘寻址演进的历史模型。

课件以 512 字节作为扇区大小进行推导,这对经典磁盘模型很常见。但现代磁盘可能使用 512 字节逻辑扇区、4KiB 物理扇区或 4KiB 原生扇区,实际参数应通过系统工具查询。

二、扇区、块和分区有什么区别

扇区是设备寻址层面的单位,但文件系统通常不会每次只管理一个扇区。为了提高效率,文件系统会把连续扇区组合为块(block)。常见块大小是 4KiB,但具体大小在格式化时确定。

概念所在层次作用
扇区块设备设备寻址和读写单位
文件系统分配文件内容的基本单位
分区磁盘布局将一块磁盘划分为多个逻辑区域
文件系统分区或块设备之上组织文件内容与元数据

如果逻辑扇区为 512 字节、文件系统块为 4KiB,那么一个块包含 8 个逻辑扇区。已知 LBA 时可粗略计算块号:

块号 = LBA ÷ 8 块起始 LBA = 块号 × 8

分区只是划定一段可使用的块设备地址范围。要在其中按文件和目录组织数据,还需要格式化,即写入某种文件系统的管理结构。

三、inode:文件名之外的文件身份

Linux 文件可以抽象为:

文件 = 内容 + 元数据

内容保存在数据块中,元数据则由 inode(index node,索引节点)保存。inode 通常记录:

  • 文件类型和权限;
  • 所有者 UID 与组 GID;
  • 文件大小;
  • 数据块数量;
  • 硬链接计数;
  • 访问、修改和状态变更时间;
  • 指向文件数据块的索引信息。

可以使用以下命令观察 inode:

# 查看文件的 inode 编号ls-liexample.txt# 查看详细元数据statexample.txt

需要特别注意两点:

  1. 文件名通常不保存在文件自身的 inode 中;
  2. Linux 中常见的ctime是 inode 状态变更时间,不是通常意义上的文件创建时间。

inode 编号只需在所属文件系统内唯一。不同分区或不同文件系统中可能出现相同的 inode 编号,因此不能脱离设备或文件系统单独使用 inode 号标识全局文件。

四、Ext2 如何组织一个分区

Ext2 会把分区划分为多个块组(Block Group)。各块组采用相似布局,使文件系统不必依靠一张无限增长的全局管理表。

Ext2 分区 │ ├─ Block Group 0 │ ├─ Superblock │ ├─ Group Descriptor Table │ ├─ Block Bitmap │ ├─ Inode Bitmap │ ├─ Inode Table │ └─ Data Blocks │ ├─ Block Group 1 │ └─ 相似结构 │ └─ Block Group N └─ 相似结构

1. Superblock:描述整个文件系统

超级块保存文件系统的全局信息,例如:

  • block 与 inode 总数;
  • 空闲 block 与 inode 数量;
  • block 和 inode 大小;
  • 每个块组的 block 与 inode 数量;
  • 文件系统状态、特性标志和 UUID;
  • 挂载次数和检查时间等信息。

超级块一旦损坏,文件系统可能无法正常识别。因此 Ext 文件系统会按相应规则在部分块组中保存备份,而不是简单理解为每个块组都必然保存完整副本。

2. GDT:块组说明书

GDT(Group Descriptor Table,块组描述符表)记录各块组的布局和统计信息,例如:

  • Block Bitmap 位于哪里;
  • Inode Bitmap 位于哪里;
  • Inode Table 从哪里开始;
  • 还有多少空闲数据块与 inode。

超级块描述整个文件系统,块组描述符则帮助系统定位每个块组内部的管理区域。

3. Block Bitmap:数据块使用情况

Block Bitmap 使用位图记录数据块是否已分配。一个 bit 对应一个数据块:

0:空闲 1:已使用

位图可以快速寻找空闲块,也能在释放文件内容时把对应位重新清零。

4. Inode Bitmap 与 Inode Table

Inode Bitmap 记录哪些 inode 空闲,Inode Table 则连续保存当前块组中的 inode 实体。创建文件时,内核先从位图中寻找空闲 inode,再在 Inode Table 的对应位置写入元数据。

5. Data Blocks:保存实际内容

Data Blocks 保存文件内容。不同类型的文件对数据块的解释不同:

  • 普通文件的数据块保存字节内容;
  • 目录的数据块保存目录项;
  • 符号链接较短时,目标路径还可能直接保存在 inode 可利用的空间中,具体行为与文件系统实现有关。

五、inode 如何找到文件内容

经典 Ext2 inode 中包含一组块指针,通常包括:

  • 直接块指针;
  • 一级间接块指针;
  • 二级间接块指针;
  • 三级间接块指针。
inode ├─ 直接指针 ─────────→ 数据块 ├─ 一级间接指针 ─────→ 指针块 → 数据块 ├─ 二级间接指针 ─────→ 指针块 → 指针块 → 数据块 └─ 三级间接指针 ─────→ 指针块 → 指针块 → 指针块 → 数据块

小文件可以直接通过 inode 中的直接指针定位,访问层级少;大文件则通过多级间接索引扩展可寻址的数据块数量。这种设计兼顾了小文件效率和大文件容量。

Ext4 对这一机制进行了重要改进,常使用 extent 描述连续的数据块范围,减少大文件需要维护的离散块指针数量。因此,学习直接与间接块适合理解 Ext2 的经典模型,但不能把它当成所有现代 Ext4 文件的唯一组织方式。

六、创建一个文件时发生了什么

假设执行:

touchabc

从文件系统角度,可以将过程概括为:

  1. 在 Inode Bitmap 中寻找空闲 inode;
  2. 在 Inode Table 中初始化 inode,写入类型、权限、时间等元数据;
  3. 如果文件产生内容,则从 Block Bitmap 中申请数据块;
  4. 把数据写入 Data Blocks,并在 inode 中记录映射;
  5. 在父目录的数据块中新增目录项,建立名称abc到 inode 号的映射;
  6. 更新相关位图、统计信息和时间戳。

这也解释了为什么文件名和文件内容不是直接绑定的:文件名属于目录项,inode 才保存文件自身的元数据和数据索引。

七、目录为什么也是文件

从文件系统角度看,目录同样拥有 inode 和数据块。区别在于,目录的数据块保存的是目录项,其核心信息可以抽象为:

文件名 → inode 编号

下面的程序读取目录项并输出名称与 inode 编号:

#include<dirent.h>#include<stdio.h>#include<stdlib.h>intmain(intargc,char*argv[]){if(argc!=2){fprintf(stderr,"usage: %s DIRECTORY\n",argv[0]);returnEXIT_FAILURE;}DIR*dir=opendir(argv[1]);if(dir==NULL){perror("opendir");returnEXIT_FAILURE;}structdirent*entry;while((entry=readdir(dir))!=NULL){printf("inode=%llu name=%s\n",(unsignedlonglong)entry->d_ino,entry->d_name);}if(closedir(dir)!=0){perror("closedir");returnEXIT_FAILURE;}returnEXIT_SUCCESS;}

opendir()readdir()是 libc 提供的目录流接口,并非都应简单称为直接系统调用。它们会在底层借助操作系统提供的目录读取能力。

八、路径是如何解析的

当程序访问:

/home/alice/project/test.c

内核不能把整串路径直接当成一个磁盘文件名,而要从根目录开始逐级查找:

根目录 / ↓ 查找 home 对应的目录项 /home ↓ 查找 alice /home/alice ↓ 查找 project /home/alice/project ↓ 查找 test.c 目标 inode

相对路径则以进程的当前工作目录为起点。进程调用open()时提供的路径,加上进程维护的工作目录信息,使内核能够确定解析起点。

如果每次访问都从磁盘逐级读取目录,性能会很差。Linux VFS 会利用 dentry(目录项缓存)等内存结构缓存名称到 inode 的解析结果,并配合哈希、LRU 等机制加速查找和回收。

这里的struct dentry字段属于具体内核版本实现,可能随版本变化。更稳定的理解是:dentry 表示路径中的一个名称组件,帮助 VFS 缓存“父目录 + 名称”到目标对象的解析关系。

九、为什么分区必须挂载

每个文件系统内部都有自己的 inode 编号空间,而 Linux 向用户呈现的是一棵统一的目录树。挂载(mount)负责把一个文件系统的根连接到现有目录树中的某个挂载点。

Linux 根目录树 / ├─ home ├─ etc └─ mnt └─ mydisk ← 挂载另一个文件系统

可以用镜像文件进行教学实验:

# 创建 64 MiB 镜像文件ddif=/dev/zeroof=disk.imgbs=1Mcount=64status=progress# 写入 Ext4 文件系统mkfs.ext4 disk.img# 创建挂载点sudomkdir-p/mnt/mydisk# 通过 loop 设备挂载镜像sudomount-oloop disk.img /mnt/mydisk# 查看挂载结果findmnt /mnt/mydisk# 使用完成后卸载sudoumount/mnt/mydisk

上述命令需要管理员权限。卸载前要确保没有进程正在使用挂载点中的文件,且当前终端不位于该目录内。

路径解析到挂载点时,VFS 会切换到被挂载文件系统的根,再继续解析剩余路径。因此用户可以通过统一路径访问不同设备、分区和文件系统。

十、Ext2、Ext3 与 Ext4 的关系

文件系统典型特点
Ext2经典块组、inode 和位图设计,不提供日志机制
Ext3在 Ext2 基础上加入日志,提高异常断电后的恢复能力
Ext4引入 extent、延迟分配、更大容量等增强,并继续使用块组与 inode 等核心思想

本文使用 Ext2 讲解,是因为它的经典布局更容易理解。Ext3 和 Ext4 并非只是把版本号递增,它们在可靠性、分配策略、索引方式和容量等方面都有明显增强。

十一、硬链接与软链接

1. 硬链接

硬链接是在目录中增加另一个名称,让它指向同一个 inode:

echo"hello">origin.txtlnorigin.txt backup.txtls-liorigin.txt backup.txt

两个名称通常会显示相同的 inode 编号。删除其中一个名称,只是移除一个目录项并减少链接计数;当链接计数归零且没有进程继续打开该文件时,文件系统才具备回收相关 inode 与数据块的条件。

硬链接通常不能跨文件系统,因为 inode 编号只在所属文件系统内有意义。普通用户也不能随意为目录创建硬链接,以避免目录树出现难以管理的环路。

2. 软链接

软链接又称符号链接,本身是一个独立文件,保存目标路径:

ln-sorigin.txt shortcut.txtls-liorigin.txt shortcut.txt readlink shortcut.txt

软链接拥有自己的 inode,可以跨文件系统,也可以指向目录;但如果目标被移动或删除,软链接可能变成悬空链接。

3. 对比

对比项硬链接软链接
inode与目标相同拥有独立 inode
保存内容目录项直接指向目标 inode保存目标路径
跨文件系统通常不可以可以
链接目录通常受限制可以
目标删除后其他硬链接仍可访问数据可能成为悬空链接

十二、文件的三个常见时间

stat常显示以下时间:

  • atime:文件内容最后访问时间;
  • mtime:文件内容最后修改时间;
  • ctime:inode 状态最后变更时间,例如权限、所有者或链接数变化。

部分文件系统还支持文件创建时间(birth time),但它与ctime不是同一个概念,是否可见取决于文件系统、内核和工具支持。

十三、总结

本文的核心链路可以归纳为:

磁盘扇区与 LBA ↓ 分区与文件系统块 ↓ Ext 块组管理结构 ↓ inode 保存元数据和数据索引 ↓ 目录项建立文件名与 inode 的映射 ↓ 路径解析与 dentry 缓存 ↓ 挂载把多个文件系统接入统一目录树

理解这条链路后,就能解释许多 Linux 文件操作:创建文件是在分配 inode、数据块并添加目录项;删除文件是在移除目录项并更新链接计数;硬链接是多个名称指向同一 inode;软链接则是保存目标路径的独立文件。