Linux 基础体系 · 第 15/85 篇。示例面向现代主流 Linux 发行版;发行版差异、权限和生产风险会明确说明。
Linux 文件系统:ext4、XFS、日志、缓存、一致性和损坏恢复
文件系统不是“把文件名映射到磁盘扇区”的简单索引。它同时负责:
- 将目录、文件、符号链接和元数据组织成持久化结构;
- 将进程看到的字节偏移转换为块设备上的数据;
- 管理空闲空间、权限、时间戳、配额和文件大小;
- 在内核缓存与持久化介质之间协调读写;
- 在断电、内核崩溃或设备错误后恢复文件系统自身的一致性;
- 把底层 I/O 错误传递给应用或将文件系统切换为只读。
本文以现代主流 Linux 发行版中的 ext4 和 XFS 为重点,解释日志、页缓存、写回、一致性、fsync、崩溃恢复和损坏修复之间的关系。命令需要 root 权限,并且不同发行版的工具包、默认挂载参数和内核版本可能不同。
一、先区分四种“已经写入”
讨论文件系统时,“写入成功”至少可能表示四种不同状态:
- 应用把数据交给了内核;
- 数据进入了文件系统和块层的缓存;
- 块设备驱动接受了请求;
- 数据已经稳定存储在断电后仍可读取的介质上。
这四个状态不是同一个事件。
例如:
write(fd, buf, len);
返回 len,通常只说明内核接受了这次写入,并不等于数据已经落盘。即使后续调用:
fsync(fd);
也只是在文件系统和块设备语义允许的范围内,请求将该文件相关的数据和必要元数据持久化;它不能修复已经损坏的 SSD、失效的 RAID 控制器或错误报告“写入完成”的设备固件。
因此,分析一个存储问题时应分别问:
- 可见性:其他进程何时能看到新内容?
- 原子性:是否可能看到半个更新?
- 一致性:文件系统内部结构是否满足约束?
- 持久性:断电后更新是否仍存在?
- 完整性:读出来的内容是否就是写入的内容?
日志主要解决文件系统结构的一致性问题,不自动解决应用数据的语义一致性,也不等于端到端数据完整性校验。
二、文件系统在 Linux I/O 路径中的位置
典型写路径可以简化为:
应用程序
│ write / pwrite / mmap
▼
VFS(虚拟文件系统)
│ inode、dentry、file、权限检查
▼
ext4 或 XFS
│ 分配块、更新 inode/目录、建立日志事务
▼
页缓存 / writeback
│ 脏页回写
▼
块层
│ bio、队列、调度
▼
设备驱动
│
▼
SSD、HDD、虚拟磁盘或 RAID
1. VFS:统一接口,不是具体文件系统
Linux 的 VFS 为 open、read、write、rename、unlink 等系统调用提供统一入口。它维护几个重要对象:
- inode:文件的元数据和数据块映射;
- dentry:目录项,连接“目录中的名字”和 inode;
- file:一次
open得到的打开文件对象,包含当前文件偏移和打开标志; - 文件描述符:进程文件描述符表中指向
file的整数句柄。
目录名不是文件本身。一个硬链接是多个目录项指向同一个 inode;符号链接则是一个独立 inode,其内容通常是目标路径字符串。unlink 删除的是目录项,只有当链接计数归零且没有进程打开该文件时,文件的数据空间才通常可以回收。
这解释了一个常见现象:
rm large.log
df -h
文件名消失了,但磁盘空间没有立即恢复。若某个进程仍持有该文件的打开描述符,inode 仍被引用。可以用:
lsof +L1
查找“已删除但仍被打开”的文件。该命令需要 lsof,并且输出受权限和 /proc 可见性影响。
2. 文件系统负责什么
ext4 或 XFS 会将逻辑操作分解为多个底层修改。例如创建文件可能需要:
- 在目录中加入文件名;
- 分配一个 inode;
- 更新 inode 的类型、权限、时间和链接计数;
- 分配数据块或记录文件为空;
- 更新块位图、inode 位图或其他空间索引;
- 更新目录的大小和校验信息。
这些修改必须满足内部约束。例如:
- 目录项不能指向不存在的 inode;
- 已分配的数据块不能同时被两个不允许共享的文件占用;
- 空闲空间索引不能把正在使用的块当成空闲;
- inode 的链接计数应与实际目录引用一致,除非处于特殊恢复状态。
文件系统日志的主要目标,就是让这些元数据更新在崩溃后回到一个可接受状态。
三、磁盘布局:分区、块设备、文件系统和挂载
一个文件系统通常建立在块设备之上,但中间可能还有多层:
物理磁盘 / 虚拟磁盘
└─ 分区
└─ RAID 设备
└─ LVM 逻辑卷
└─ ext4 或 XFS
└─ 挂载到目录树
因此,不能仅凭目录名判断文件系统边界。应先查看设备、类型和挂载关系:
lsblk -o NAME,TYPE,FSTYPE,SIZE,MOUNTPOINTS,UUID
findmnt -T /var/lib
df -T /var/lib
可能看到类似结果:
NAME TYPE FSTYPE SIZE MOUNTPOINTS
nvme0n1 disk 1.8T
└─nvme0n1p2 part ext4 200G /
└─nvme0n1p3 part LVM 1.6T
└─vg-data-lv data xfs 1.6T /var/lib
这里 / 和 /var/lib 可能属于两个不同文件系统。对 /var/lib 执行修复时,目标设备是对应的逻辑卷,而不是整块 NVMe 盘。
df 反映文件系统可用空间,du 统计目录树中可见文件所占的空间。两者差异可能来自:
- 已删除但仍打开的文件;
- 预留块;
- 文件系统元数据;
- 稀疏文件的“逻辑大小”与“实际占用”不同;
- 快照、共享块或隐藏的内部结构。
四、ext4 和 XFS 的共同基础
ext4 与 XFS 都是 Linux 上成熟的日志文件系统,但内部空间管理和数据结构不同。它们共同提供:
- inode 和目录;
- 文件权限、ACL、扩展属性;
- 稀疏文件;
- 日志或日志化元数据;
- 页缓存路径;
- 挂载、卸载和崩溃恢复;
- 工具链检查和修复。
“日志文件系统”不是指它们把每次普通文件写入都复制到一个文本日志里。日志通常是结构化的二进制区域,用于记录元数据事务,或者在特定模式下记录数据。日志的用途是让恢复程序知道某个更新是:
- 尚未开始;
- 已经完成;
- 只完成了一部分;
- 需要重放;
- 需要撤销或丢弃。
它解决的是崩溃时结构修改处于中间状态的问题,而不是保证所有用户数据永不丢失。
五、ext4:块组、extent 和日志模式
5.1 ext4 的基本组织
ext4 将文件系统划分为块组,每个块组管理一部分数据块和 inode。现代 ext4 通常使用 extent 表示连续或大范围的数据块映射。
逻辑文件偏移到物理块的过程大致是:
文件偏移
▼
extent 树
▼
物理块范围
▼
块设备地址
一个 extent 可表示“从某个逻辑块开始,连续对应多少个物理块”。相比早期逐块间接索引,extent 更适合大文件和连续分配。
文件大小和块数仍不是一回事:
- 普通文件可能存在内部空洞;
- 稀疏文件的逻辑大小很大,但没有为未写区域分配物理块;
stat的Size与Blocks因此可能差异很大。
示例:
truncate -s 1G sparse.img
du -h sparse.img
du --apparent-size -h sparse.img
truncate 创建一个逻辑大小为 1 GiB 的文件,但不必立即分配 1 GiB 的磁盘块。du 通常显示实际块占用,而 --apparent-size 显示逻辑大小。对稀疏文件执行普通复制、压缩或写入时,可能改变其实际占用。
5.2 ext4 的日志模式
ext4 常见的数据模式包括:
data=ordered:默认常见模式。文件数据通常在相关元数据提交前先写出,但这不是每个应用写入的持久化承诺;data=writeback:元数据日志化,但数据写出顺序约束更弱,崩溃后旧数据或未初始化内容出现在新文件区域的风险更高;data=journal:数据和元数据都写入日志,通常写放大更明显。
查看实际挂载参数:
findmnt -no SOURCE,FSTYPE,OPTIONS /
data=ordered 中的“ordered”不能理解成“调用 write 后数据立即持久化”。它主要约束文件数据回写与元数据提交之间的顺序,最终仍受回写线程、设备缓存和 fsync 等因素影响。
挂载选项是内核和文件系统实现的行为接口,不同内核版本可能有细节差异。生产环境不能只根据 /etc/fstab 推断当前状态,应查看实际挂载结果。
六、XFS:分配组、B+ 树和元数据日志
6.1 XFS 的基本组织
XFS 将文件系统划分为多个 allocation group,简称 AG。每个 AG 管理一部分空间和 inode,并使用 B+ 树等结构维护:
- 空闲空间;
- inode;
- extent 映射;
- 目录;
- 配额和其他元数据。
多 AG 的设计有利于大型文件系统上的并发分配。多个 CPU 或多个线程可以在不同 AG 上进行操作,减少单一全局锁和索引热点。
XFS 的文件数据也通常通过 extent 映射。目录可能使用短格式、块格式或 B+ 树格式,具体取决于目录内容规模。
6.2 XFS 的日志
XFS 使用日志记录元数据事务。日志可以位于文件系统内部,也可以配置为外部日志设备。现代 XFS 还支持延迟记录、延迟分配等机制,以便合并操作和改善分配效率。
“延迟分配”意味着应用写入后,文件系统可能先在内存中记录脏页,而不是立即决定最终物理块。这样有机会把多个小写入合并成更连续的分配,但崩溃或空间不足时,写入可能在回写阶段才暴露错误。
XFS 默认并不等同于“所有文件数据都进入日志”。其日志重点是元数据。若应用需要特定数据持久化语义,仍应使用合适的 fsync、fdatasync 或更高层事务协议。
6.3 XFS 的重要特性边界
某些 XFS 文件系统创建时可启用特定功能,例如:
reflink:多个文件可以共享物理数据块,写时复制;rmapbt:记录反向映射;bigtime:支持更大范围的时间戳;finobt:辅助 inode 空闲索引。
不能假设所有 XFS 都启用了这些功能。查看文件系统特征应使用:
xfs_info /mount/point
xfs_info 需要目标文件系统已挂载。卸载后的设备可用其他只读查询工具查看,但具体选项应以本机 man 页面为准。
启用 reflink 后,物理块占用、文件逻辑大小和“两个文件是否真正复制了数据”不能简单按普通文件理解。删除一个文件也不一定立即释放底层块,因为另一个文件可能仍引用共享块。
七、日志究竟保证什么
7.1 元数据事务的形式化描述
设文件系统状态为 ,一次元数据操作为 ,例如“创建文件并把目录项加入目录”。理想情况是:
崩溃可能发生在 的任意中间步骤。没有日志时,磁盘上可能出现:
它既不是旧状态 ,也不是完整新状态 。
日志系统会先记录足够的信息,使恢复程序在重启后执行:
这里的 是日志记录。通常要求结果至少满足文件系统不变量,而不要求所有应用层操作都回到完整新状态。
例如创建文件时断电:
- 目录项已经写入;
- inode 尚未完全初始化;
- inode 位图已更新;
- 日志只记录了部分事务。
恢复程序可能重放完整事务,也可能丢弃未提交事务。最终目标是“不出现指向随机 inode 的目录项”,而不是保证用户一定能看到这个新文件。
7.2 日志提交不等于应用事务提交
数据库事务可能包含:
- 写入日志;
- 更新多个数据页;
- 修改索引;
- 提交事务;
- 对外返回成功。
文件系统日志通常只知道“块级元数据修改”,不知道应用的业务约束。例如:
扣减库存
写入订单文件
更新索引文件
即使三个文件的文件系统元数据都一致,断电后也可能只持久化了其中两个业务动作。文件系统不会自动保证这三个动作具有业务原子性。
7.3 日志通常不能保证用户数据内容
如果应用写入:
旧文件内容:ABCDEFGHIJ
新文件内容:1234567890
崩溃后可能出现:
- 完整旧内容;
- 完整新内容;
- 新旧内容混合;
- 文件大小或元数据已更新,但部分数据尚未持久化。
具体结果取决于写入方式、块大小、页缓存、文件系统、存储设备和同步调用。日志模式不能被当作通用的数据版本控制系统。
八、页缓存、脏页和写回
8.1 读路径
普通 read 的简化路径是:
read(fd, buf, n)
│
├─ 页缓存中已有数据:直接复制到用户缓冲区
│
└─ 未命中:
从文件系统映射到块设备
发起 I/O
数据进入页缓存
再复制到用户缓冲区
页缓存按页管理,而不是按“一个文件一个缓存”。不同文件的页都可能竞争内存。
8.2 写路径
普通 write 通常先修改页缓存:
用户缓冲区
▼
页缓存页变脏
▼
文件系统记录元数据变化或延迟分配
▼
writeback 回写
▼
块设备
脏页不会无限停留。内核通过后台回写和内存压力控制回写;应用也可以显式调用:
sync
或在程序中使用:
fsync(fd);
fdatasync(fd);
sync 会请求系统范围内的脏数据和相关元数据回写,代价和影响范围都比单文件同步更大。它不是替代应用事务设计的工具。
8.3 fsync、fdatasync 和目录同步
fsync(fd) 的语义是请求文件数据及恢复文件所需的元数据写入稳定存储。fdatasync(fd) 更偏向只保证数据和必要元数据,若不影响后续数据访问,某些元数据更新可以不必同步。
创建或替换文件时,仅同步文件本身可能不够。典型安全更新流程是:
int fd = open("config.new", O_WRONLY | O_CREAT | O_TRUNC, 0644);
write_all(fd, data, len);
fsync(fd);
close(fd);
rename("config.new", "config");
int dfd = open(".", O_RDONLY | O_DIRECTORY);
fsync(dfd);
close(dfd);
逻辑如下:
- 写临时文件;
fsync临时文件,确保内容和必要元数据已持久化;rename在同一文件系统内原子替换目录项;fsync父目录,使目录项替换本身持久化。
如果进程在第 2 步前崩溃,旧文件通常仍在;如果在 rename 后、父目录 fsync 前断电,重启后目录替换是否持久存在不能按应用预期假设。不同文件系统、内核和设备实现会影响细节,但“文件同步”和“目录项同步”是两个不同对象。
rename 的原子性也有限制:
- 源和目标必须位于同一文件系统;
- 它保证目录项切换不会被观察为“半个名字”,但不保证文件内容已经持久化;
- 它不等于多个文件同时替换;
- 跨文件系统时通常失败并返回
EXDEV。
8.4 内存映射写入
mmap 修改文件时,写入首先修改映射页。应用应使用:
msync(addr, length, MS_SYNC);
请求映射区域同步,但要注意:
msync的语义与fsync不是完全相同的接口;- 文件扩展、元数据更新和目录项仍需单独考虑;
- 映射访问可能在缺页或写回时异步暴露错误;
- 收到
SIGBUS可能表示文件被截断、底层空间或设备访问失败。
8.5 O_DIRECT 也不是“自动持久化”
O_DIRECT 试图绕过页缓存,但通常有对齐、长度和地址限制,具体要求取决于文件系统和设备。它不自动保证断电后数据已经稳定存储,应用仍可能需要 fsync 或设备级同步语义。
九、并发、可见性和持久性不是一回事
假设两个线程同时写同一文件:
线程 A:pwrite(fd, "AAAA", 4, 0)
线程 B:pwrite(fd, "BBBB", 4, 0)
最终内容取决于两次写入的排序。即使每次系统调用都成功,也不能从文件系统日志推导出业务上的“谁应该获胜”。
常见操作的语义应区分:
1. O_APPEND
使用 O_APPEND 时,每次写入前文件偏移会定位到文件末尾;在支持的本地文件系统上,定位和写入具有一定的单次操作原子性。但它不保证:
- 多个写入在业务上不可交错;
- 写入已持久化;
- NFS 等远程文件系统有完全相同的语义;
- 一个大写入一定作为业务记录完整保留。
2. 锁
flock、fcntl 锁可以协调合作进程,但锁通常是协作式的:
flock -x /run/myjob.lock -c '/usr/local/bin/update-data'
不遵守锁的进程仍可能修改文件。锁也不替代 fsync:它解决并发互斥,不解决断电持久性。
3. 打开文件与删除
进程 A:open("a", O_RDONLY) 得到 fd
进程 B:unlink("a")
目录项被删除后,进程 A 仍可通过 fd 读取文件。新进程无法通过名字打开它,但数据要等打开引用消失后才可能回收。这是 inode 引用和目录项引用分离的结果,不是文件系统泄漏。
十、文件系统一致性、数据一致性和数据完整性
这三个词常被混用,但对象不同。
10.1 文件系统一致性
文件系统一致性指内部结构满足不变量,例如块分配、inode、目录和日志之间相互匹配。e2fsck 或 xfs_repair 主要检查这一层。
10.2 应用数据一致性
应用数据一致性指业务规则成立,例如:
账户 A 余额减少 100
账户 B 余额增加 100
如果只完成其中一半,文件系统仍可能完全一致,但业务数据不一致。需要数据库事务、写前日志、版本文件、校验和或幂等恢复协议解决。
10.3 数据完整性
数据完整性关注“读回的字节是否被静默改变”。文件系统日志不一定能发现介质上的静默位翻转。可以采用:
- 应用层校验和;
- 数据库页校验;
- RAID 校验;
- 具备校验能力的存储栈;
- 定期读取和备份验证。
应注意,RAID 的冗余主要提高可用性和部分错误恢复能力,不等于备份,也不一定能识别所有错误。
十一、ext4 与 XFS 的选择边界
不能用“某个文件系统永远更快”概括选择。应根据工作负载、运维能力、所需特性和恢复工具作决定。
ext4 常见特点
- 发行版支持广泛,工具成熟;
- 适合系统盘、通用服务器和中小规模文件系统;
- 可在线扩展,通常也可离线缩小;
e2fsck可执行检查和修复;- 具备较丰富的兼容特性与挂载选项。
XFS 常见特点
- 适合大文件、大容量文件系统和高并发 I/O;
- 在线扩容能力成熟;
- 使用
xfs_repair进行检查修复; - 通常不能缩小已创建的 XFS 文件系统;
- 许多高级特性在创建时决定,不能简单通过挂载参数后补齐。
在线扩容示例:
# 先确认逻辑卷或底层设备已经扩容
lsblk
lvs
# XFS:对挂载点执行扩容
sudo xfs_growfs /data
# ext4:可以对设备或挂载点扩容,具体形式以本机 man 页面为准
sudo resize2fs /dev/mapper/vg_data-lv_data
xfs_growfs 的对象通常是挂载点;resize2fs 面向 ext 文件系统设备。扩容前必须先完成分区、LVM 或 RAID 层的扩容,否则文件系统看不到新增空间。任何扩容都应先确认备份和设备路径,避免把操作施加到错误的逻辑卷。
十二、空间不足不只是一种故障
检查空间时至少看四类指标:
df -h /data
df -i /data
sudo du -xhd1 /data | sort -h
sudo lsof +L1
1. 数据块耗尽
df -h 显示可用容量接近零。大文件、日志、快照或共享块都可能是原因。
2. inode 耗尽
df -i 显示 inode 使用率接近 100%。大量小文件会先耗尽 inode,即使还有很多字节空间。
3. 删除文件仍被打开
lsof +L1 找到进程后,应通过应用自身的日志轮转或重启流程释放描述符。直接杀进程可能造成业务中断。
4. 预留空间和保留区
ext4 可能保留一部分空间供特权进程和文件系统维护使用。普通用户报告“满了”,而 root 仍能写入,不一定是异常。具体比例可用:
sudo tune2fs -l /dev/sdXn | grep -E 'Reserved block count|Block count'
不要在不了解业务的情况下随意降低预留空间。根文件系统完全耗尽会使日志、临时文件和修复流程都变得困难。
十三、崩溃恢复的实际路径
系统崩溃或断电后,文件系统通常经历以下过程:
运行中
│
├─ 应用修改页缓存和元数据
├─ 日志事务部分提交
└─ 发生断电或内核崩溃
▼
下次挂载
│
├─ 读取日志
├─ 重放已提交但尚未写入主结构的事务
├─ 丢弃未完成事务
└─ 将文件系统置于可挂载状态
ext4 常可在挂载时自动进行日志恢复。XFS 也会在挂载时进行日志重放。日志恢复通常比全盘扫描快,因为它只处理日志中的近期事务。
但以下情况可能仍需要离线检查:
- 日志本身损坏;
- 底层设备返回 I/O 错误;
- 文件系统元数据已经超过日志可恢复范围;
- 文件系统被错误地多次强制断电;
- 内核或驱动存在缺陷;
- RAID、虚拟磁盘或存储控制器重排了写入;
- 文件系统已被标记为需要检查。
十四、发现疑似损坏时的诊断顺序
先保存证据,再尝试修复。不要看到错误就立即运行修复工具。
14.1 查看内核和设备错误
dmesg -T | grep -Ei 'ext4|xfs|I/O error|blk_update|buffer|nvme|scsi|ata|readonly'
journalctl -k -b
重点关注:
I/O error;uncorrectable;medium error;journal aborted;remounting filesystem read-only;- NVMe、SCSI、SATA 链路或控制器错误。
如果设备层已经报错,单独修复文件系统可能掩盖硬件问题。
14.2 查看挂载状态
findmnt -T /data
mount | grep ' /data '
若文件系统被重新挂载为只读,先检查硬件、线缆、虚拟化存储和 RAID 状态。只读通常是保护行为:继续写入可能扩大损坏范围。
14.3 查看 SMART 或设备健康状态
SATA/SAS 设备常使用:
sudo smartctl -a /dev/sdX
NVMe 可使用:
sudo nvme smart-log /dev/nvme0
工具未安装、虚拟机不暴露健康信息、RAID 控制器隐藏物理盘时,命令可能无法提供有效数据。SMART 正常也不能证明文件系统或数据绝对无误。
十五、ext4 检查和修复
15.1 必须先卸载
普通 e2fsck 不应对正在读写的 ext4 文件系统执行。先确认设备:
findmnt -T /data
sudo umount /data
如果是根文件系统,应进入救援系统、安装介质或使用发行版提供的维护环境。不能为了“方便”直接对正在使用的根分区做写修复。
15.2 只读检查
sudo e2fsck -f -n /dev/mapper/vg_data-lv_data
含义:
-f:即使文件系统看起来干净,也强制检查;-n:假设回答否,不写入修复结果。
输出可能包含 inode、目录、链接计数、位图等阶段。只读检查适合初步了解问题,但它不能保证随后仍保持相同状态;如果设备继续坏,状态可能变化。
15.3 实际修复
sudo e2fsck -f /dev/mapper/vg_data-lv_data
工具可能询问:
- 是否修复孤儿 inode;
- 是否重建目录;
- 是否修正链接计数;
- 是否修复位图;
- 是否清除损坏的日志。
每次修复都可能丢失无法重建的目录项或文件片段。应先做块级镜像或确认备份,尤其是出现大量 I/O 错误时。对故障盘直接反复运行修复,会把原始证据和可恢复结构进一步改变。
15.4 典型结果解释
- clean:检查通过,不等于硬件健康;
- 修复 orphan inode:文件曾经没有可见目录项,但仍有引用,工具将其放回恢复目录或释放;
- 修复 bitmap:分配状态与实际引用不一致;
- 目录项错误:可能导致文件名丢失、移动到
lost+found或无法恢复; - I/O error:优先处理设备问题,而不是继续增加修复次数。
十六、XFS 检查和修复
16.1 XFS 的边界:fsck.xfs 通常不做常规修复
对 XFS,常见的 fsck.xfs 程序通常只提示应使用 xfs_repair,不会像 ext4 的 e2fsck 那样进行常规检查修复。
先卸载:
sudo umount /data
只读检查可以使用:
sudo xfs_repair -n /dev/mapper/vg_data-lv_data
-n 表示不写入修复。它仍可能需要读取大量元数据,设备故障时应谨慎。
实际修复:
sudo xfs_repair /dev/mapper/vg_data-lv_data
必须针对未挂载的 XFS 设备。修复过程可能重建索引、丢弃无法验证的元数据关系,并把孤立对象放入可访问的恢复结构或造成文件名、目录结构丢失。
16.2 日志损坏与 -L
有时 XFS 日志无法正常重放,工具可能提示需要清除日志。xfs_repair -L 会强制清除日志,可能丢失最近尚未完成的元数据事务:
sudo xfs_repair -L /dev/mapper/vg_data-lv_data
这不是普通的“再试一次”选项。使用前应:
- 确认文件系统确实已卸载;
- 采集内核日志;
- 检查底层设备和 RAID;
- 尽可能做镜像或快照;
- 评估最近写入的数据能否从备份恢复。
如果日志只是因为设备暂时不可用,先修复设备层可能比直接清除日志更安全。
16.3 XFS 文件系统扩容和缩容
XFS 通常支持在线增长:
sudo xfs_growfs /data
但常规 XFS 不支持缩小。需要减少底层卷大小时,通常流程是:
- 新建一个更小的文件系统;
- 停止业务并复制数据;
- 验证复制结果;
- 切换挂载点;
- 再处理旧卷。
直接缩小 LVM 或分区而不先缩小文件系统会截断仍在使用的块,通常导致严重损坏。
十七、先镜像,再修复:面对坏盘的正确优先级
如果存在持续的读错误,首要目标不是让文件系统“看起来干净”,而是最大化保留可恢复数据。
常见原则是:
原始故障设备
▼
尽量少写入
▼
块级镜像
▼
对镜像做检查和修复
▼
从镜像或备份恢复文件
GNU ddrescue 常用于有坏块的设备:
sudo ddrescue -f -n /dev/sdX /recovery/disk.img /recovery/disk.map
sudo ddrescue -d -r3 /dev/sdX /recovery/disk.img /recovery/disk.map
说明:
- 第一步优先复制容易读取的区域;
- 第二步尝试处理失败区域;
.map文件记录进度,便于中断后继续;/dev/sdX必须确认无误,写反目标会摧毁数据;- 目标镜像需要足够空间;
- 对有硬件故障的盘反复通电和重试可能加剧损坏。
更安全的操作对象是镜像文件或其只读映射。若镜像来自加密、RAID、LVM 等多层结构,还必须按正确顺序组装这些层,不能把任意中间层直接当作普通文件系统设备。
十八、挂载与恢复时的安全策略
18.1 只读挂载不是绝对零写入
sudo mount -o ro,noload /dev/mapper/vg_data-lv_data /mnt/recovery
对某些文件系统,ro 仍可能触发日志重放或其他元数据动作;noload 用于禁止加载日志,但具体支持与风险应查看本机文档。禁止日志重放后,看到的可能是崩溃前的中间状态,适合取证或复制前评估,不代表文件系统已正常恢复。
对于 ext4,ro,noload 常用于尽量避免日志回放;对于 XFS,具体行为和工具版本应以 mount、xfs 文档及内核支持为准。
18.2 恢复数据时避免覆盖源
推荐将恢复文件写到另一块健康存储:
sudo mount -o ro /dev/mapper/vg_data-lv_data /mnt/source
sudo mount /dev/sdb1 /mnt/target
sudo rsync -aHAX --numeric-ids /mnt/source/ /mnt/target/
选项含义:
-a:保留常见属性并递归复制;-H:保留硬链接关系;-A:保留 ACL;-X:保留扩展属性;--numeric-ids:不依赖目标系统用户名映射。
rsync 的返回码、错误输出和目标空间都必须检查。只看到命令结束并不代表所有文件都成功复制。
十九、日志、缓存和设备写缓存之间的隐藏边界
即使文件系统发出了写入顺序,底层设备也可能有内部缓存。设备需要正确处理:
- flush;
- barrier;
- force unit access;
- 电源故障保护;
- 写入重排。
Linux 会通过块层和文件系统向设备发出同步请求,但最终可靠性取决于设备是否正确实现协议。没有断电保护的消费级 SSD、虚拟化存储后端或错误的 RAID 控制器,可能在报告 flush 完成后仍丢失数据。
这会产生一个重要反例:
应用 write
文件系统提交日志
fsync 返回成功
突然断电
重启后仍缺少最近数据
若设备错误地确认了尚未稳定的数据,文件系统无法从空中恢复这些内容。fsync 是必要的语义请求,不是对不可靠硬件的物理保证。
二十、故障表现与原因对照
1. 启动时长时间检查
可能原因:
- 上次未正常卸载;
- 日志需要重放;
- 文件系统确实存在结构问题;
- 底层设备读操作很慢;
- 大型文件系统正在做全量检查。
应结合启动日志和设备错误判断,不能仅根据“检查很久”断定文件系统损坏。
2. 文件系统自动变只读
常见原因:
- 元数据写入返回 I/O 错误;
- 日志提交失败;
- 块设备或路径暂时不可用;
- 文件系统检测到无法安全继续的状态。
此时反复执行 mount -o remount,rw 可能再次触发写入并扩大损坏。先收集日志、确认设备稳定、安排维护窗口。
3. 文件存在但内容为空或变旧
可能原因:
- 应用只调用了
write,未同步就崩溃; - 数据写入页缓存后尚未回写;
- 应用使用临时文件替换,但未同步父目录;
- 存储设备丢失了已确认的写入;
- 读取的是另一个挂载层或容器卷。
日志能保证文件系统结构可恢复,不保证应用最近一次内容更新一定出现。
4. No space left on device
除了容量和 inode 耗尽,还可能来自:
- ext4 预留空间;
- 项目配额、用户配额或组配额;
- XFS 配额;
- 文件大小限制;
- 容器或虚拟文件系统的独立限制;
- 目录所在文件系统与预期不同。
应同时检查:
df -h
df -i
ulimit -a
findmnt
二十一、检查工具不能替代备份
文件系统检查工具的目标是重建元数据结构,不是恢复所有历史版本。修复时可能发生:
- 文件名与目录层级丢失;
- 部分文件被放入
lost+found或无法按原路径关联; - 文件内容只剩部分块;
- 最近事务被丢弃;
- 共享块、快照或 reflink 关系发生变化;
- 原本可读的结构因设备继续失败而变得更差。
因此,恢复策略应分层:
- 先确认是否有可验证的备份;
- 若设备有错误,先做镜像;
- 在镜像或副本上检查和修复;
- 恢复到新的文件系统;
- 通过校验和、应用测试和目录抽样验证;
- 保留原始介质和修复日志,直到确认数据完整。
备份“存在”不等于备份“可恢复”。至少需要实际抽样恢复文件,验证权限、ACL、扩展属性、硬链接、符号链接和应用可读性。
二十二、一个可观察的安全更新示例
下面用 shell 演示文件替换流程的关键步骤:
set -eu
dir=/data/app
tmp="$dir/config.new"
dst="$dir/config"
printf '%s\n' 'version=2' 'mode=safe' > "$tmp"
# 让临时文件内容进入文件系统和块设备的同步路径
sync -d "$tmp"
# 同一文件系统内,目录项替换是原子的
mv -f "$tmp" "$dst"
# 同步父目录,使名称替换本身持久化
dirfd=$(exec 9<"$dir" && echo 9)
sync -d "$dir"
exec 9<&-
这个 shell 例子用于说明概念,但生产程序更应直接使用 open、write、fsync、rename 和目录 fsync,并逐一检查返回值。sync -d 的可用性和行为依赖 coreutils 版本;不要把它当成跨平台、精确的应用事务接口。
更完整的 C 逻辑应处理:
write的短写;EINTR;ENOSPC;EIO;fsync失败;rename失败;- 目录
fsync失败; - 临时文件清理;
- 崩溃后旧文件和新文件的选择策略。
如果配置内容超过单个文件,或者需要同时更新多个相关文件,就应使用数据库事务、版本目录加指针切换,或设计可重放的日志协议,而不是假设多次 rename 具有整体原子性。
二十三、哪些说法是错误的
错误一:有日志就不会丢数据
日志主要保证文件系统元数据恢复到一致状态。未同步的用户数据、设备缓存中的数据和业务事务仍可能丢失。
错误二:write 返回成功就是落盘
它通常只表示内核接受了写入。需要持久化语义时,应设计 fsync 或 fdatasync,并确认设备链路可靠。
错误三:rename 能保证配置不会损坏
rename 能提供同一文件系统内目录项切换的原子观察语义,但若没有先同步新文件或随后同步父目录,断电后仍可能得到旧版本、缺少新版本,或出现与预期不同的持久化结果。
错误四:XFS 不能修复
XFS 有 xfs_repair,但它要求文件系统通常处于卸载状态,且强制清除日志可能造成数据丢失。不能用 ext4 的思路直接运行 e2fsck。
错误五:RAID 等于备份
RAID 主要处理设备故障和可用性问题,不能防止误删、恶意加密、应用错误写入或逻辑损坏。损坏会被同步到所有冗余副本。
错误六:只读挂载一定不会写任何东西
日志重放、挂载行为、设备层和文件系统特性存在差异。恢复现场应使用经过确认的只读策略,并尽量操作镜像。
错误七:删除大文件后空间必然立即恢复
若文件仍被进程打开,目录项虽已删除,数据块仍被打开文件引用。应检查 /proc 或 lsof +L1,通过安全方式释放进程描述符。
二十四、建立正确的判断模型
面对一个文件系统问题,可以按以下因果链分析:
应用操作
│
├─ 是否完成了业务级原子性?
├─ 是否调用了合适的同步接口?
▼
VFS 与具体文件系统
│
├─ inode、目录、extent、空闲空间是否一致?
├─ 日志事务是否提交?
▼
页缓存与写回
│
├─ 是否仍有脏页?
└─ 写回是否返回错误?
▼
块层与设备
│
├─ 是否正确执行 flush?
├─ 是否发生介质或链路错误?
└─ 是否存在 RAID/LVM/虚拟化层问题?
▼
断电或崩溃后的状态
这个模型能避免把不同层的问题混为一谈:
- 文件系统修复工具处理的是结构;
fsync处理的是同步请求;- 锁处理的是并发;
- 数据库事务处理的是业务原子性;
- 备份处理的是历史恢复;
- RAID 处理的是部分设备故障;
- 应用校验和处理的是内容完整性。
ext4 和 XFS 的核心差异在数据结构、分配策略、扩容缩容边界、特性开关和工具链;日志和缓存则是理解两者运行行为的共同基础。真正可靠的存储系统,需要把这些层的语义逐层验证,而不能用“写成功”“有日志”或“做了 RAID”替代对持久性、一致性和恢复路径的具体分析。
系列导航与关联阅读
- 系列入口:Linux 完整学习路线:从内核与文件系统到网络、性能和生产运维
- 上一篇:Linux 块设备与存储:分区、LVM、RAID、挂载和在线扩容
- 下一篇:Linux 虚拟内存:页表、缺页、Cache、Swap、OOM 与内存诊断
- 延伸:Linux 文件、Inode 与链接:描述符、硬链接、符号链接和删除语义
官方资料
本文依据 Linux 内核、systemd 与主流发行版官方文档重新梳理;正文与实验由 WR BLOG 编写。

评论
0 条讨论