Ext系列文件系统详解:从磁盘寻址到 inode、目录与软硬链接
当我们执行touch、open或rm时操作系统究竟修改了磁盘上的哪些内容要回答这个问题需要把磁盘寻址、分区、数据块、inode、目录、挂载与链接串成一条完整链路。本文以 Ext2 的经典布局为主线建立 Ext 系列文件系统的整体认知。一、从机械磁盘说起机械硬盘由盘片、主轴、磁头和传动臂等部件组成。盘片表面被划分为磁道磁道又被划分为扇区不同盘面上半径相同的磁道可以抽象为柱面。传统磁盘几何结构常用三个量描述Cylinder柱面编号Head磁头或盘面编号Sector磁道中的扇区编号。这就是 CHSCylinder-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) 1CHS 中的扇区编号通常从 1 开始而 LBA 从 0 开始因此公式中需要减 1 或加 1。为什么后来使用 LBACHS 依赖具体磁盘几何参数表达范围也受到字段位数限制。LBALogical 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 文件可以抽象为文件 内容 元数据内容保存在数据块中元数据则由 inodeindex node索引节点保存。inode 通常记录文件类型和权限所有者 UID 与组 GID文件大小数据块数量硬链接计数访问、修改和状态变更时间指向文件数据块的索引信息。可以使用以下命令观察 inode# 查看文件的 inode 编号ls-liexample.txt# 查看详细元数据statexample.txt需要特别注意两点文件名通常不保存在文件自身的 inode 中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块组说明书GDTGroup Descriptor Table块组描述符表记录各块组的布局和统计信息例如Block Bitmap 位于哪里Inode Bitmap 位于哪里Inode Table 从哪里开始还有多少空闲数据块与 inode。超级块描述整个文件系统块组描述符则帮助系统定位每个块组内部的管理区域。3. Block Bitmap数据块使用情况Block Bitmap 使用位图记录数据块是否已分配。一个 bit 对应一个数据块0空闲 1已使用位图可以快速寻找空闲块也能在释放文件内容时把对应位重新清零。4. Inode Bitmap 与 Inode TableInode Bitmap 记录哪些 inode 空闲Inode Table 则连续保存当前块组中的 inode 实体。创建文件时内核先从位图中寻找空闲 inode再在 Inode Table 的对应位置写入元数据。5. Data Blocks保存实际内容Data Blocks 保存文件内容。不同类型的文件对数据块的解释不同普通文件的数据块保存字节内容目录的数据块保存目录项符号链接较短时目标路径还可能直接保存在 inode 可利用的空间中具体行为与文件系统实现有关。五、inode 如何找到文件内容经典 Ext2 inode 中包含一组块指针通常包括直接块指针一级间接块指针二级间接块指针三级间接块指针。inode ├─ 直接指针 ─────────→ 数据块 ├─ 一级间接指针 ─────→ 指针块 → 数据块 ├─ 二级间接指针 ─────→ 指针块 → 指针块 → 数据块 └─ 三级间接指针 ─────→ 指针块 → 指针块 → 指针块 → 数据块小文件可以直接通过 inode 中的直接指针定位访问层级少大文件则通过多级间接索引扩展可寻址的数据块数量。这种设计兼顾了小文件效率和大文件容量。Ext4 对这一机制进行了重要改进常使用 extent 描述连续的数据块范围减少大文件需要维护的离散块指针数量。因此学习直接与间接块适合理解 Ext2 的经典模型但不能把它当成所有现代 Ext4 文件的唯一组织方式。六、创建一个文件时发生了什么假设执行touchabc从文件系统角度可以将过程概括为在 Inode Bitmap 中寻找空闲 inode在 Inode Table 中初始化 inode写入类型、权限、时间等元数据如果文件产生内容则从 Block Bitmap 中申请数据块把数据写入 Data Blocks并在 inode 中记录映射在父目录的数据块中新增目录项建立名称abc到 inode 号的映射更新相关位图、统计信息和时间戳。这也解释了为什么文件名和文件内容不是直接绑定的文件名属于目录项inode 才保存文件自身的元数据和数据索引。七、目录为什么也是文件从文件系统角度看目录同样拥有 inode 和数据块。区别在于目录的数据块保存的是目录项其核心信息可以抽象为文件名 → inode 编号下面的程序读取目录项并输出名称与 inode 编号#includedirent.h#includestdio.h#includestdlib.hintmain(intargc,char*argv[]){if(argc!2){fprintf(stderr,usage: %s DIRECTORY\n,argv[0]);returnEXIT_FAILURE;}DIR*diropendir(argv[1]);if(dirNULL){perror(opendir);returnEXIT_FAILURE;}structdirent*entry;while((entryreaddir(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/zeroofdisk.imgbs1Mcount64statusprogress# 写入 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. 硬链接硬链接是在目录中增加另一个名称让它指向同一个 inodeechohelloorigin.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文件内容最后修改时间ctimeinode 状态最后变更时间例如权限、所有者或链接数变化。部分文件系统还支持文件创建时间birth time但它与ctime不是同一个概念是否可见取决于文件系统、内核和工具支持。十三、总结本文的核心链路可以归纳为磁盘扇区与 LBA ↓ 分区与文件系统块 ↓ Ext 块组管理结构 ↓ inode 保存元数据和数据索引 ↓ 目录项建立文件名与 inode 的映射 ↓ 路径解析与 dentry 缓存 ↓ 挂载把多个文件系统接入统一目录树理解这条链路后就能解释许多 Linux 文件操作创建文件是在分配 inode、数据块并添加目录项删除文件是在移除目录项并更新链接计数硬链接是多个名称指向同一 inode软链接则是保存目标路径的独立文件。