news 2026/8/16 13:18:32

【Linux】Ext 系列文件系统深度解析:从 LBA、inode、块组到路径解析、挂载与软硬链接

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
【Linux】Ext 系列文件系统深度解析:从 LBA、inode、块组到路径解析、挂载与软硬链接

🔥本文定位:面向已经掌握 Linux 基础命令、准备深入文件系统与系统编程的同学,沿着“块设备 → Ext 布局 → inode → 目录项 → 路径解析 → 挂载 → 链接 → 日志”这条主线,建立一套能真正解释现象的文件系统模型。
💡学习目标:不仅知道 Ext2、Ext3、Ext4 的名词,还能回答文件名为什么不在 inode 中、删除文件后空间为何可能不立即释放、挂载为何会遮蔽原目录内容、硬链接为何不能跨文件系统,以及 Ext4 为什么更常使用 extent 而不是三级间接块。


文章目录

  • 前言:先把“名字、元数据、数据”分开
  • 一、从机械磁盘到 LBA
  • 二、扇区、文件系统块、内存页与分区
  • 三、inode:文件元数据的核心
  • 四、Ext 块组:把大文件系统拆成局部管理单元
  • 五、inode 如何定位文件数据
  • 六、文件创建、读取与删除的完整生命周期
  • 七、目录与路径解析
  • 八、挂载:把文件系统接入目录树
  • 九、硬链接与符号链接
  • 十、Ext3/Ext4 日志与崩溃恢复
  • 十一、Ext2、Ext3、Ext4 的演进
  • 十二、动手实验:用镜像文件安全观察 Ext4
  • 十三、常见误区与高频面试题
  • 总结

前言:先把“名字、元数据、数据”分开

用户看到的是一个路径:

/home/alice/docs/report.txt

Ext 文件系统内部看到的却不是一个“带名字的文件对象”,而是一组协作关系:

父目录中的目录项 report.txt → inode number ↓ inode 类型 / 权限 / UID / GID / 大小 时间戳 / link count / 数据映射 ↓ extent 或间接块索引 ↓ 数据块

这三个对象必须先分开:

对象主要保存什么是否保存普通文件名
目录项(directory entry)名字、inode 编号,Ext4 中还可带文件类型保存名字
inode类型、权限、属主、大小、时间戳、链接计数、数据映射等通常不保存普通文件名
数据块普通文件内容、目录项、索引块、扩展属性等取决于数据用途

一句话记忆:

目录项负责“名字指向谁”,inode 负责“这个对象是什么”,数据块负责“内容在哪里”。

这套模型能直接解释硬链接、重命名、删除后延迟回收、路径解析和挂载等问题。


一、从机械磁盘到 LBA

1.1 机械硬盘的经典结构

机械硬盘的教学模型通常包含:

  • 盘片:表面记录数据;
  • 主轴:带动盘片旋转;
  • 磁头:读写盘面;
  • 磁臂:把磁头移动到目标半径;
  • 磁道:盘面上的同心圆;
  • 扇区:磁道划分出的区域;
  • 柱面:不同盘面上相同半径磁道组成的逻辑集合。

机械盘一次访问的时间主要来自:

  1. 寻道时间:磁臂移动到目标磁道;
  2. 旋转延迟:等待目标扇区转到磁头下;
  3. 传输时间:真正读写数据。

因此,文件系统会尽量让相关的元数据和文件数据靠近,也会通过缓存、预读、合并 I/O 等方式减少随机访问。

注意:Ext 的很多经典布局设计都能在机械盘背景下理解,但文件系统并不只运行在机械硬盘上。SSD、虚拟块设备、RAID、LVM 和 loop 设备同样可以承载 Ext 文件系统。

1.2 CHS:经典教学坐标

CHS 使用三个坐标定位扇区:

  • C:Cylinder,柱面号;
  • H:Head,磁头号;
  • S:Sector,扇区号。

在“每磁道扇区数固定”的简化模型中,设磁头数为heads,每磁道扇区数为sectors_per_track

LBA = C × heads × sectors_per_track + H × sectors_per_track + (S - 1)

这里的S - 1来自经典 CHS 扇区号通常从 1 开始,而 LBA 从 0 开始。

反向换算为:

C = LBA / (heads × sectors_per_track) H = (LBA % (heads × sectors_per_track)) / sectors_per_track S = (LBA % sectors_per_track) + 1

这些公式适合帮助理解地址线性化,但不要把它们当成现代磁盘内部真实几何结构的完整描述。

1.3 LBA:系统真正关心的一维地址

现代块设备通常向操作系统暴露 LBA(Logical Block Address):

[0][1][2][3][4][5] ... [n]

操作系统提交一个线性编号,设备固件再负责内部映射、坏块替换、闪存转换层或其他实现细节。LBA 隐藏了真实介质的复杂性,也让 HDD、SSD、虚拟磁盘等设备可以使用相似的块接口。


二、扇区、文件系统块、内存页与分区

2.1 四个容易混淆的单位

概念所属层次作用常见值示例
逻辑扇区块设备接口操作系统看到的设备寻址单位512 B、4 KiB
物理扇区设备介质设备实际读改写的物理单元4 KiB
文件系统块Ext 文件系统分配、记录和组织空间的基本单位常见 4 KiB
内存页虚拟内存页表、缺页和页缓存管理单位常见 4 KiB

它们的数值可能恰好相同,但不能画等号,因为它们来自不同层次、承担不同职责。

例如逻辑扇区为 512 B、文件系统块为 4 KiB 时,一个文件系统块覆盖 8 个逻辑扇区:

文件系统块号 = LBA / 8 块内逻辑扇区偏移 = LBA % 8

Ext 的块大小记录在超级块中,内核文档给出的计算方式是:

block_size = 2 ^ (10 + s_log_block_size)

所以s_log_block_size = 2时,块大小为 4 KiB。

Ext4 启用bigalloc后,还会在文件系统块之上引入更大的 cluster 分配单位;此时块位图实际按 cluster 跟踪空间。本文主体先以未启用bigalloc的常见配置讲解。

2.2 分区、格式化和挂载不是一回事

三者经常被一句“把磁盘分区格式化后挂载”连在一起,实际是三个独立动作:

整块磁盘 ↓ 分区:划分 LBA 地址范围 分区 / 逻辑卷 / 其他块设备 ↓ 格式化:写入 Ext 超级块、位图、inode 表等 一个可识别的文件系统 ↓ 挂载:接入 Linux 目录树 用户可通过路径访问
  • 分区:只是在分区表中描述一段地址范围;
  • 格式化:在块设备上创建某种文件系统;
  • 挂载:让一个已有文件系统出现在指定目录路径下。

Ext 可以建立在分区上,也可以建立在整块设备、LVM 逻辑卷、RAID 设备或普通镜像文件对应的 loop 设备上。

常用只读观察命令:

lsblk-ffindmnt blkiddf-hTdf-i

⚠️mkfs.ext4fdiskparted等命令会改变磁盘结构或文件系统。不要把教程中的目标路径替换成保存重要数据的/dev/sdX/dev/nvme...或其他真实设备。


三、inode:文件元数据的核心

3.1 inode 保存什么

inode 是 Ext 文件系统描述文件对象的核心结构。典型字段包括:

  • 文件类型和权限位;
  • UID、GID;
  • 文件大小;
  • atime、mtime、ctime,Ext4 还可保存 crtime;
  • 硬链接计数;
  • 数据块映射或 extent tree;
  • inode 标志、扩展属性、校验信息等。

查看文件的 inode 编号与元数据:

ls-lidemo.txtstatdemo.txtstat-c'inode=%i links=%h size=%s blocks=%b'demo.txt

inode 编号的作用域是单个文件系统。两个不同文件系统都可以存在 inode 3017,它们并不是同一个对象。这正是硬链接不能跨文件系统的根本原因。

3.2 inode 不保存普通文件名

普通文件名存在于父目录的数据中:

目录项:"demo.txt" → inode 3017

inode 3017 本身并不知道哪个目录项叫demo.txt。同一个 inode 可以被多个目录项引用,于是形成硬链接。

这也解释了为什么同一文件系统内的重命名通常很快:核心动作主要是修改目录项,而不是搬运整份文件数据。

3.3 四个时间戳不要混淆

时间含义典型变化场景
atime最近访问时间读取文件;实际更新受relatimenoatime等挂载策略影响
mtime文件数据最近修改时间写入、截断文件内容
ctimeinode 状态最近变化时间权限、属主、链接数、mtime 等状态变化
btime / crtime创建时间文件系统和接口支持时可读取

最常见的误区是把ctime解释成 creation time。它实际是change time,即 inode 状态变化时间。Ext4 的创建时间字段在磁盘格式中称crtime,用户态可能通过statx()stat的 Birth 字段看到它,但是否可用仍取决于内核、文件系统和工具支持。

3.4 inode 大小不是永远固定

经典 Ext2/Ext3 inode 记录是 128 B;Ext4 允许在格式化时选择更大的 inode 记录,常见默认值为 256 B。实际大小保存在超级块的s_inode_size中。

更大的 inode 可以容纳纳秒级扩展时间戳、创建时间、项目 ID、校验值和部分扩展属性等信息。因此,不能把“inode 永远是 128 字节”当成通用结论。

3.5 inode 号如何定位到 inode 表

设每个块组包含s_inodes_per_group个 inode,目标 inode 编号为ino

block_group = (ino - 1) / s_inodes_per_group index = (ino - 1) % s_inodes_per_group byte_offset = index × s_inode_size

inode 0 被定义为不存在,所以计算时需要先减 1。找到块组后,再通过块组描述符定位该组的 inode table。


四、Ext 块组:把大文件系统拆成局部管理单元

4.1 为什么需要 Block Group

如果整个大文件系统只使用一份巨大的 inode 表和位图,不仅管理范围过大,目录、inode 与文件数据也可能相距很远。Ext 把文件系统划分为多个 Block Group:

Ext 文件系统 ├── Block Group 0 ├── Block Group 1 ├── Block Group 2 └── ...

每个组管理一部分 inode 和块。分配器会尽量让相关对象保持邻近,例如让一个目录下的文件分布在合适的局部区域,以改善访问局部性。

4.2 块组的典型组成

教学模型中,一个块组常画成:

[Superblock/GDT 备份(可选)] [Block Bitmap] [Inode Bitmap] [Inode Table] [Data / Index Blocks]

每一部分的职责如下:

结构作用
Superblock保存整个文件系统的全局参数、特性标志、总量和状态等
Group Descriptor Table记录各块组位图、inode 表位置和空闲统计等
Block Bitmap标记块是否分配;bigalloc下按 cluster 管理
Inode Bitmap标记 inode 槽位是否使用
Inode Table保存该组的 inode 记录
Data / Index Blocks保存文件数据、目录项、extent/间接索引、扩展属性等

4.3 超级块偏移的准确理解

Ext 文件系统的主超级块从文件系统起始位置偏移 1024 B 处开始。对块组 0 来说,最前面的 1024 B 被保留,历史上可用于 x86 引导扇区等用途:

  • 块大小为 1 KiB 时,超级块位于块 1;
  • 块大小大于 1 KiB 时,超级块位于块 0 内偏移 1024 B 的位置。

这不等于“每个分区前面都有一个文件系统永远不能修改的 1 KiB boot block”,也不应与 GPT/MBR 分区表混为一谈。这里讨论的是Ext 文件系统内部相对于文件系统起点的布局

4.4 备份不是每个块组都有,布局也并非绝对固定

常见示意图会把每个块组都画成相同顺序,但真实 Ext4 文件系统可能启用:

  • sparse_super/sparse_super2:只在部分块组保存超级块和 GDT 备份;
  • flex_bg:把多个块组组合成 flexible block group,让位图与 inode table 集中放置;
  • meta_bg:改变块组描述符的分布方式,以支持更大的文件系统;
  • uninit_bg:延迟初始化部分位图或 inode table,缩短格式化时间。

因此:

块组图是理解职责的概念图,不是可以对所有 Ext4 镜像硬套的固定偏移表。真实位置应读取超级块特性和块组描述符。

4.5 “空间还有很多却不能创建文件”

创建普通文件至少需要一个可用 inode;写入数据还需要可用块。两类资源要分开观察:

df-h# 数据空间df-i# inode 使用率

大量极小文件可能先耗尽 inode,而不是先耗尽字节容量。此时df -h看起来仍有空间,却会出现No space left on device


五、inode 如何定位文件数据

5.1 Ext2 的直接块与三级间接块

经典 Ext2 inode 的i_block区域包含 15 个块号槽位:

i_block[0..11] → 12 个直接块指针 i_block[12] → 一级间接块 i_block[13] → 二级间接块 i_block[14] → 三级间接块

若文件系统块大小为B,每个块号占 4 B,则一个索引块可容纳:

N = B / 4

理论寻址容量为:

direct = 12 × B single = N × B double = N² × B triple = N³ × B

以 4 KiB 块为例,N = 1024

层级理论覆盖范围
12 个直接块48 KiB
一级间接4 MiB
二级间接4 GiB
三级间接4 TiB

这里是索引结构的理论地址跨度,不等于具体内核与磁盘格式真正支持的最大文件大小。实际限制还会受到块计数字段、实现和文件系统配置影响。

5.2 Ext4 为什么改用 extent

大文件往往包含很长的连续块范围。如果仍逐块保存块号,索引元数据会非常庞大。Extent 用三个核心量描述一段连续空间:

逻辑块起点 → 物理块起点 + 连续长度

例如:

LBN 0–127 → PBN 8000–8127 (128 blocks) LBN 128–319 → PBN 9400–9591 (192 blocks)

一个 extent 就能代表一串连续块。Ext4 的inode.i_block前 60 B 可以保存 extent tree 的头部与根节点;树变大后再引用外部 extent 节点块。

5.3 Ext4 不只是“Ext2 加了日志”

Ext4 的常见空间管理特性还包括:

  • extent tree;
  • delayed allocation,延迟决定物理块位置;
  • multiblock allocator,一次分配连续范围;
  • 64-bit 块号与更大文件系统支持;
  • flexible block groups;
  • 元数据校验和;
  • inline data,在条件满足时把小数据放入 inode 区域。

小符号链接也常采用“fast symlink”方式,把路径直接放进 inode 的i_block空间,从而不必额外分配数据块。

5.4 稀疏文件与st_blocks

文件的逻辑大小不一定等于实际占用空间:

truncate-s1G sparse.binstat-c'size=%s bytes, allocated=%b × 512B'sparse.bindu-hsparse.bindu-h--apparent-size sparse.bin

st_size描述逻辑字节长度,st_blocks在常见 Linux 接口中以 512 B 单位报告已分配块数;它不是 Ext 文件系统块的数量。稀疏文件未实际写入的洞不会占用对应数据块,读取时由文件系统返回零。


六、文件创建、读取与删除的完整生命周期

6.1 创建文件

以在某目录创建hello.txt为例,逻辑过程可以概括为:

  1. 检查父目录权限和目标名字是否已存在;
  2. 在合适块组的 inode bitmap 中寻找空闲 inode;
  3. 初始化 inode 的类型、权限、UID/GID、时间戳和链接计数;
  4. 在父目录数据中增加hello.txt → inode number目录项;
  5. 写入内容时再分配数据块或建立 extent;
  6. 更新位图、块组统计、inode 大小和时间戳;
  7. Ext3/Ext4 根据日志和写回策略提交相应元数据。

Ext4 的 delayed allocation 意味着“用户已经 write”与“最终物理块位置已经确定”不一定发生在同一时刻。内核可以等积累更多数据后再做更连续的分配。

6.2 读取文件

一次基于路径的读取大致经历:

路径字符串 ↓ 逐级查询目录项 / dentry cache ↓ 得到目标 inode ↓ 检查类型、权限与打开标志 ↓ 把文件逻辑偏移转换为逻辑块 ↓ 通过 extent tree 或间接块定位物理块 ↓ 先查 page cache,未命中时提交块 I/O ↓ 数据复制到用户缓冲区

这里还会经过 VFS、具体 Ext4 实现、页缓存和块层,真实调用链比示意图更长,但核心对象关系不变。

6.3unlink()删除的是名字

unlink("hello.txt")首先移除的是父目录里的目录项,并让 inode 的硬链接计数减 1。只有同时满足:

硬链接计数 == 0 并且 没有打开引用仍持有该文件

inode 和数据块才可以真正回收。

因此,文件名已经消失时,仍持有 fd 的进程可以继续读写该文件。常见现象包括:

  • 服务日志已被rm,但进程仍占用大量磁盘空间;
  • 临时文件创建后立刻 unlink,进程退出前仍可安全使用;
  • 通过/proc/<pid>/fd/能看到带(deleted)的打开文件。

查找“已删除但仍被进程打开”的文件:

sudolsof+L1

不要通过盲目删除/proc中的对象处理问题。通常应让持有者正确关闭 fd,或按服务的日志轮转方式重新打开日志。


七、目录与路径解析

7.1 目录也是文件

在 Ext 中,目录的数据保存一组目录项。Ext4 常见目录项结构包含:

inode # 目标 inode 编号 rec_len # 当前目录项记录长度 name_len # 文件名长度 file_type # 文件类型提示 name # 文件名字节串

目录项至少回答“这个名字指向哪个 inode”。文件的权限、大小和时间戳仍应到 inode 中读取。

7.2 目录项和 dentry 不是同一个东西

这是一个很容易被略过、但非常关键的区别:

名称在哪里作用
Ext directory entry磁盘上的目录文件数据持久化name → inode number映射
VFS dentry内核内存表示和缓存一次路径分量解析结果
inode cache内核内存缓存内存中的 inode 对象
page cache内核内存缓存文件数据页

dentry cache 不只是“所有已打开文件组成的一棵树”。它可以保存:

  • 正 dentry:名字成功对应某个 inode;
  • 负 dentry:曾查询过,但这个名字不存在。

负 dentry 能避免程序反复查询不存在的路径时,每次都访问磁盘目录数据。

7.3 绝对路径与相对路径从哪里开始

  • 绝对路径以/开头,从进程看到的根目录开始;
  • 普通相对路径从当前工作目录cwd开始;
  • openat()等接口还能让相对路径从指定dirfd开始。

这里的“进程根目录”处于挂载命名空间和进程根视图中,不一定等于宿主机全局意义上的根。例如容器中的/可以是隔离后的文件系统视图。

7.4 逐级解析时发生什么

解析/home/alice/docs/a.txt时,可抽象为:

/ 的目录项中查 home → home inode 的目录项中查 alice → alice inode 的目录项中查 docs → docs inode 的目录项中查 a.txt → 得到目标 inode

每个中间目录都需要搜索权限,即目录的x权限。只有对最终文件执行具体操作时,才结合目标 inode 权限、ACL、打开方式和其他安全机制继续判断。

解析过程中还可能遇到:

  • ...
  • 符号链接,需要按目标路径继续解析;
  • 挂载点,需要切换到另一个文件系统的根;
  • mount namespace、chroot、容器根等视图边界;
  • 并发重命名、删除与缓存失效。

7.5 用 C 读取目录项

下面的程序使用opendir()/readdir()读取目录。d_ino是目录项中报告的 inode 编号;d_type在某些文件系统上可能是DT_UNKNOWN,此时不能只依赖它判断类型,应继续使用fstatat()等接口。

#include<dirent.h>#include<errno.h>#include<inttypes.h>#include<stdio.h>#include<stdlib.h>intmain(intargc,char*argv[]){constchar*path=argc>1?argv[1]:".";DIR*dir=opendir(path);if(dir==NULL){perror("opendir");returnEXIT_FAILURE;}for(;;){errno=0;structdirent*entry=readdir(dir);if(entry==NULL){if(errno!=0){perror("readdir");closedir(dir);returnEXIT_FAILURE;}break;}printf("inode=%10ju d_type=%3u name=%s\n",(uintmax_t)entry->d_ino,(unsigned)entry->d_type,entry->d_name);}if(closedir(dir)==-1){perror("closedir");returnEXIT_FAILURE;}returnEXIT_SUCCESS;}

编译运行:

cc-Wall-Wextra-O2readdir_demo.c-oreaddir_demo ./readdir_demo /tmp

八、挂载:把文件系统接入目录树

8.1 挂载改变的是路径解析接入点

假设根文件系统中原本存在:

/mnt/demo/local.txt

现在把另一个 Ext4 文件系统挂载到/mnt/demo

sudomount/dev/example /mnt/demo

之后访问/mnt/demo时,路径解析会跨入被挂载文件系统的根目录。原来的local.txt并未删除,而是被挂载视图临时遮蔽;卸载后它会重新可见。

所以挂载不是:

  • 把新文件复制进目录;
  • 把两个目录的文件自动合并;
  • 把文件系统永久绑定到一个固定路径。

挂载更准确的理解是:

在当前挂载命名空间中,把一个文件系统的根接到目录树的某个挂载点。

8.2 挂载点必须是空目录吗

技术上不要求为空,但通常建议使用空目录。若目录本来有内容,挂载期间这些内容会被遮蔽,很容易造成“文件怎么突然没了”的误判。

查看挂载关系:

findmnt findmnt--target/mnt/demo mountpoint /mnt/demo

8.3 卸载失败:target is busy

常见原因包括:

  • 某进程当前工作目录位于挂载点内部;
  • 某文件仍被打开;
  • 还有子挂载;
  • 终端、服务或文件管理器仍在访问。

可以先观察:

findmnt--target/mnt/demosudofuser-vm/mnt/demosudolsof+D /mnt/demo

lsof +D会递归扫描,大目录上可能较慢。应先让相关进程退出目录或关闭文件,再正常umount;不要一上来就使用 lazy/force 卸载掩盖原因。

8.4 挂载命名空间

挂载关系不是所有进程都必须共享同一个全局视图。Linux mount namespace 可以让不同进程组看到不同的挂载树,这是容器文件系统隔离的重要基础。

同一路径字符串在两个命名空间中可能解析到不同文件系统对象。排查容器或服务中的路径问题时,必须先确认“从哪个进程的挂载视图看”。


九、硬链接与符号链接

9.1 硬链接:新的目录项指向同一 inode

printf'hello\n'>a.txtlna.txt a.hardls-lia.txt a.hard

逻辑关系是:

a.txt ───┐ ├──→ inode 3017 → 同一份数据 a.hard ───┘

两个名字地位平等,没有“原文件”和“快捷方式”的层级。删除任意一个目录项,只会让 link count 减 1;另一个名字仍然可以访问内容。

硬链接的核心限制:

  • inode 编号只在单个文件系统内有意义,因此不能跨文件系统;
  • 普通用户不能随意给目录创建硬链接,以免破坏目录树约束并制造环;
  • 一个 inode 的所有硬链接共享内容和 inode 元数据,但目录项名字各自独立。

9.2 符号链接:自己的 inode 保存目标路径

ln-sa.txt a.symls-lia.txt a.sym readlink a.sym

逻辑关系是:

a.sym 的目录项 ↓ 符号链接自己的 inode ↓ 内容是 "a.txt" 再次进行路径解析 ↓ 目标 a.txt

符号链接可以:

  • 跨文件系统;
  • 指向目录;
  • 在目标暂时不存在时先创建。

目标被删除后,符号链接自身仍存在,只是访问目标时返回ENOENT,形成 dangling symlink。

9.3 相对符号链接从哪里解释

这是实践中最容易出错的细节:

符号链接中的相对目标,是相对于“符号链接所在目录”解释,而不是相对于调用者当前工作目录解释。

例如:

mkdir-pproject/releases/v1 project/currentln-s../releases/v1 project/current/latest readlink project/current/latest

../releases/v1project/current/出发,解析到project/releases/v1。即使从其他工作目录访问该链接,目标解释基准也不会改成调用者的 cwd。

9.4 对比表

对比项硬链接符号链接
本质新目录项指向同一 inode独立 inode 保存路径字符串
inode 是否相同相同不同
能否跨文件系统不能可以
能否指向目录通常受严格限制可以
目标删除后其他硬链接仍可访问变为悬空链接
是否增加目标 link count
相对路径问题相对链接所在目录解析

十、Ext3/Ext4 日志与崩溃恢复

10.1 为什么需要日志

创建文件可能同时修改:

  • 父目录的目录项;
  • inode bitmap;
  • block bitmap;
  • inode table;
  • 块组统计;
  • 文件数据。

如果写到一半突然断电,磁盘上可能只完成了一部分更新。Ext3/Ext4 使用 jbd2 日志,把相关元数据更新组织成事务,崩溃恢复时根据完整提交记录重放或忽略未完成事务,从而把文件系统恢复到可解释的一致状态。

10.2 一次日志事务的概念流程

产生元数据修改 ↓ 写入 journal descriptor 与日志中的元数据块 ↓ 写入 commit block,事务完成 ↓ checkpoint:把元数据写回最终位置 ↓ 日志空间可复用

真实 jbd2 还包含 revoke、校验和、序列号、fast commit 等细节,图中展示的是帮助理解的主线。

10.3 三种 data 模式

模式元数据文件数据主要特点
data=ordered写入日志不写入日志,但相关数据要求先于元数据 commit 落到最终位置常见默认,兼顾性能与合理一致性
data=writeback写入日志不保证先于元数据 commit更弱的数据顺序保证
data=journal写入日志也写入日志写放大更大,语义更强

ordered的“数据先行”不等于所有应用写入都已经获得持久化承诺,也不能替代正确的fsync()/fdatasync()/ 目录同步设计。

10.4 日志不等于备份

日志主要解决文件系统元数据一致性和崩溃恢复问题,它不能自动挽救:

  • 用户误删;
  • 应用把正确内容覆盖成错误内容;
  • 勒索软件修改;
  • 静默数据损坏;
  • 整块设备丢失。

这些问题仍然需要独立备份、快照、校验与恢复演练。


十一、Ext2、Ext3、Ext4 的演进

文件系统核心特点适合理解的关键词
Ext2无日志,经典块组、inode 与间接块结构基础磁盘格式、直接/间接块
Ext3在 Ext2 兼容布局上加入 JBD 日志日志事务、较平滑升级
Ext4extent、延迟分配、多块分配、校验和、更大规模等现代空间管理与可靠性增强

11.1 Ext2

Ext2 没有日志,结构相对清晰,非常适合学习 inode、位图、块组和间接块。不过异常断电后通常需要更完整的fsck扫描来恢复一致性。

11.2 Ext3

Ext3 的关键增强是加入日志能力,磁盘格式与 Ext2 有较强兼容性。它让异常恢复通常可以通过重放日志快速完成,而不是每次都扫描整个文件系统。

11.3 Ext4

Ext4 不只是容量变大,还重做了大文件和连续空间管理:

  • extent 代替逐块映射作为常见路径;
  • delayed allocation 与 multiblock allocator 改善连续性;
  • flex_bgmeta_bg改变元数据布局与扩展能力;
  • metadata checksum 增强损坏检测;
  • 64-bit 特性扩大块号和文件系统规模;
  • timestamp 扩展、inline data、目录索引等增强功能。

理解 Ext2 的经典模型是基础,但分析 Ext4 时必须继续读取文件系统特性标志,不能把 Ext2 图原封不动地当成 Ext4 实际布局。


十二、动手实验:用镜像文件安全观察 Ext4

⚠️ 以下实验只对新建的普通镜像文件执行mkfs.ext4。不要把变量改成真实磁盘或分区。挂载步骤需要sudo,完成后务必卸载。

12.1 创建独立实验目录和镜像

demo_dir=$(mktemp-d)image="$demo_dir/ext4-demo.img"mount_dir="$demo_dir/mnt"truncate-s128M"$image"mkfs.ext4-F"$image"mkdir"$mount_dir"printf'demo_dir=%s\nimage=%s\n'"$demo_dir""$image"

这里的-F只因为目标是普通文件。执行前应确认$image位于刚创建的$demo_dir中。

12.2 观察超级块与块组

dumpe2fs-h"$image"debugfs-Rstats"$image"

重点观察:

  • block size;
  • inode size;
  • blocks/inodes per group;
  • filesystem features;
  • journal inode;
  • flex_bg、metadata_csum、extent、64bit 等特性。

列出每个块组的概要:

dumpe2fs"$image"|less

12.3 通过 loop 挂载并创建文件

sudomount-oloop,nosuid,nodev,noexec"$image""$mount_dir"findmnt--target"$mount_dir"printf'hello ext4\n'|sudotee"$mount_dir/hello.txt">/dev/nullsudomkdir"$mount_dir/docs"sudoln"$mount_dir/hello.txt""$mount_dir/docs/hello.hard"sudoln-s../hello.txt"$mount_dir/docs/hello.sym"ls-li"$mount_dir/hello.txt""$mount_dir/docs/hello.hard""$mount_dir/docs/hello.sym"sudoumount"$mount_dir"

这里docs/hello.sym的内容是../hello.txt,它从符号链接所在的docs/目录出发,正确指向上一级的hello.txt

12.4 在未挂载镜像上查看 inode

先从上一步ls -li记下hello.txt的 inode 编号,例如 12:

debugfs-R'stat <12>'"$image"debugfs-R'blocks <12>'"$image"

如果 inode 编号不是 12,请替换成实际值。stat输出可观察 inode 类型、大小、链接计数、时间戳和 extent;blocks会列出相关块号。

12.5 验证“删除名字,但打开 fd 仍可读”

这个实验不需要挂载镜像:

printf'still alive through fd\n'>"$demo_dir/open.txt"exec3<"$demo_dir/open.txt"rm"$demo_dir/open.txt"ls-l"/proc/$$/fd/3"cat<&3exec3<&-

rm后目录项已经不存在,但当前 shell 的 fd 3 仍引用打开文件。关闭 fd 3 后,如果硬链接计数为 0,内核才能回收对应存储空间。

12.6 清理实验

确认已经卸载后再删除实验目录:

ifmountpoint-q"$mount_dir";thenprintf'still mounted: %s\n'"$mount_dir">&2elserm-r--"$demo_dir"fi

如果仍处于挂载状态,应先正常执行sudo umount "$mount_dir",再次确认mountpoint -q返回未挂载后再清理,不要直接删除挂载点。


十三、常见误区与高频面试题

13.1 文件名存在哪里?

普通文件名存放在父目录的目录项中,目录项把名字映射到 inode 编号。inode 保存元数据与数据映射,通常不保存普通文件名。

13.2 inode 编号是不是整台机器全局唯一?

不是。inode 编号只在单个文件系统内唯一。唯一标识一个对象通常需要同时考虑设备/文件系统标识与 inode 编号。

13.3ctime是创建时间吗?

不是。ctime是 inode status change time。创建时间通常称 btime 或 crtime,是否可见取决于文件系统、内核和用户态接口。

13.4 删除文件为什么空间没有立刻回来?

可能仍有其他硬链接,也可能有进程持有打开 fd。只有链接数为 0 且没有打开引用时,inode 和数据块才可回收。

13.5 目录项、dentry 和 inode 有什么区别?

  • 目录项:磁盘中的名字到 inode 编号映射;
  • dentry:VFS 在内存中表示/缓存路径分量;
  • inode:文件对象的元数据与数据映射。

13.6 为什么硬链接不能跨文件系统?

硬链接目录项只记录目标 inode 编号,而 inode 编号只在当前文件系统内解释。跨文件系统后,相同数字不能唯一定位原对象。

13.7 软链接目标的相对路径从哪里解析?

从符号链接所在目录解析,不是从访问者的 cwd 解析。

13.8 挂载为什么会让目录原有文件“消失”?

原文件没有删除。挂载改变了该路径的解析接入点,被挂载文件系统的根遮蔽了挂载点目录原有内容;卸载后会重新可见。

13.9 Ext4 是否仍使用直接、一级、二级、三级间接块?

经典 Ext2 模型使用这些指针。Ext4 普通文件通常启用 extent tree;磁盘格式仍保留i_block区域,但其中常保存 extent 头和树根,而不是简单的 15 个传统块指针。

13.10 日志能否防止用户误删?

不能。日志用于崩溃一致性恢复,不是历史版本备份。误删与错误覆盖仍要依靠备份、快照和恢复策略。

13.11df -h有空间,为什么仍报 No space left on device?

除了数据块,创建文件还需要 inode。先用df -i检查 inode 是否耗尽;配额、保留块和其他限制也可能造成类似现象。

13.12 为什么ls -l显示的文件大小和du不同?

ls -l主要显示逻辑大小,du主要统计实际分配空间。稀疏文件、压缩、共享块和元数据策略都会让两者不同。


总结

把 Ext 文件系统串起来,可以记住下面这条主线:

块设备暴露 LBA ↓ 分区或逻辑卷提供一段块地址空间 ↓ Ext 用块、块组、超级块、描述符和位图管理空间 ↓ 父目录用“名字 → inode 编号”定位对象 ↓ inode 保存元数据,并通过间接块或 extent 定位数据 ↓ VFS 用 dentry/inode/page cache 加速路径与数据访问 ↓ 挂载把其他文件系统接入目录树 ↓ 硬链接共享 inode,符号链接保存路径 ↓ Ext3/Ext4 用日志帮助恢复元数据一致性

真正理解 Ext,不是背一张磁盘布局图,而是能在遇到现象时判断:

  1. 当前讨论的是设备扇区、文件系统块,还是内存页?
  2. 文件名位于哪个父目录项?
  3. 路径解析从哪个 root、cwd 或 dirfd 开始?
  4. inode 如何映射到数据块?
  5. 当前视图是否跨越挂载点或 mount namespace?
  6. 删除的是目录项,还是对象已经满足回收条件?

沿着这些问题排查,很多看似零散的文件系统现象都会回到同一套模型中。


参考资料

  • Linux Kernel Documentation:Ext4 文件系统
  • Linux Kernel Documentation:Ext4 Block Groups
  • Linux Kernel Documentation:Ext4 Index Nodes
  • Linux Kernel Documentation:The Contents of inode.i_block
  • Linux Kernel Documentation:Directory Entries
  • Linux Kernel Documentation:Journal
  • Linux man-pages:path_resolution(7)
  • Linux man-pages:inode(7)
  • Linux man-pages:link(2)
  • Linux man-pages:symlink(2)
  • Linux man-pages:unlink(2)
  • Linux man-pages:mount(8)

推荐标签:Linux文件系统Ext4inode操作系统系统编程

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/8/16 13:16:09

deepseek-harness-minimal-tool-plugin-blog

给 deepseek-harness 写一个工具插件&#xff1a;从开发到真实调用 请以您的实际环境为准&#xff0c;以下仅供参考 最近在接触 deepseek-harness&#xff08;简称 dsh&#xff09;&#xff0c;顺手给它写了一个最小工具插件&#xff0c;把从开发、测试、发布到最终被模型真实调…

作者头像 李华
网站建设 2026/8/16 13:15:39

AI基础设施实战:从概念到部署,构建企业级AI能力栈

最近在技术圈里&#xff0c;甲骨文&#xff08;Oracle&#xff09;的一则新闻引发了广泛讨论&#xff1a;这家老牌数据库巨头正“举债重金押注 AI 基建”&#xff0c;同时被曝出正在制定新一轮裁员计划。这看似矛盾的操作背后&#xff0c;其实揭示了当前企业级技术市场一个深刻…

作者头像 李华
网站建设 2026/8/16 13:07:43

Linux root执行passwd报Permission denied的深度排查与解决方案

1. 问题现象与核心矛盾解析 “Permission denied”这个提示&#xff0c;对于任何一位Linux用户&#xff0c;尤其是系统管理员来说&#xff0c;都再熟悉不过了。它通常意味着当前用户没有执行某个操作或访问某个文件的权限。但当一个已经登录为 root 的用户&#xff0c;在执行…

作者头像 李华
网站建设 2026/8/16 13:03:00

MyBatis-Plus为何叫好不叫座?从技术选型到工程实践的深度解析

1. 一个现象引发的思考&#xff1a;好用与普及度的背离 最近在几个技术社区和项目群里&#xff0c;发现一个挺有意思的现象。大家讨论持久层框架时&#xff0c;MyBatis-Plus&#xff08;简称MP&#xff09;的口碑普遍不错&#xff0c;很多用过的人都会说一句“真香”&#xff0…

作者头像 李华
网站建设 2026/8/16 13:01:49

DeepSeek-V4-Flash视觉API实战:从零接入到生产级应用指南

最近在折腾一些需要视觉理解能力的自动化任务时&#xff0c;遇到了一个挺有意思的“坑”。手头有个项目&#xff0c;需要程序能看懂截图、分析界面元素&#xff0c;然后自动生成操作指令。一开始&#xff0c;我理所当然地想到了那些耳熟能详的多模态大模型&#xff0c;但要么是…

作者头像 李华