3. 文件系统层面的恢复:LFS、CoW 与 fsck 原理

文件系统层面的恢复,说白了就是跟文件系统的“记账本”打交道。你想想看,升级中断时最怕什么?不是数据本身丢了,而是文件系统不知道数据该放哪、哪些是有效的、哪些是垃圾。这一章,我带你看看三种典型的文件系统是怎么处理这个问题的。

3.1 日志结构文件系统(LFS)

LFS 的设计思路很特别——它把整个磁盘当成一个巨大的日志。所有写操作都追加到日志末尾,从不原地修改。这听起来有点浪费空间,但好处是崩溃恢复极其简单。

核心思想: 数据就是日志,日志就是数据。恢复时只需要回放最后一段日志即可。

3.1.1 LFS 的写入流程

  1. 数据先写入内存中的缓冲区(segment buffer)
  2. 缓冲区满了,一次性写入磁盘的某个空闲段
  3. 更新段摘要(segment summary),记录该段包含哪些文件
  4. 更新检查点(checkpoint),记录当前有效段的起始位置

我在项目中遇到过一台老旧的嵌入式设备,用的就是类 LFS 文件系统。升级到一半断电了,重启后系统自动回放日志,数据完好无损。说实话,当时我挺惊讶的——要是换成传统文件系统,估计已经炸了。

3.1.2 崩溃恢复策略

LFS 的恢复分两步:

  • 检查点恢复: 从最后一个有效检查点开始,读取检查点记录的段列表
  • 日志回放: 从检查点位置向后扫描,把未完成的写入重新执行一遍

避坑指南: 我曾经遇到过检查点本身损坏的情况。LFS 通常会在磁盘上保留两份检查点,交替使用。如果两份都坏了……嗯,那就只能靠段摘要里的信息做部分恢复了。

3.1.3 LFS 的垃圾回收

因为所有写入都是追加,磁盘上会积累大量过期数据。LFS 需要定期做垃圾回收:

  • 扫描段,找出有效数据
  • 把有效数据搬移到新段
  • 释放旧段

这个过程中如果中断,最多丢失正在搬移的数据,不会影响文件系统整体结构。

3.2 写时复制(CoW)文件系统

CoW 的思路跟 LFS 有点像,但更激进——每次修改都创建新副本,而不是覆盖旧数据。ZFS、Btrfs 都是典型的 CoW 文件系统。

3.2.1 CoW 的工作原理

当你修改一个文件时,CoW 会这样做:

  1. 读取原始数据块
  2. 在空闲位置写入修改后的数据块
  3. 更新元数据指针,指向新数据块
  4. 旧数据块变成“孤儿”,等待回收

你想想看,这意味着什么?任何时候中断,旧数据都还在原地。恢复时只需要检查元数据树的完整性就行了。

注意: CoW 的写放大问题很严重。一个小修改可能触发整个元数据树的更新。我在生产环境见过 ZFS 因为频繁的小写入导致性能骤降,后来调整了 recordsize 才解决。

3.2.2 CoW 的快照与回滚

CoW 天生支持快照。创建快照时,只需要复制一份元数据根节点,数据块本身不需要复制。升级前打个快照,升级中断后直接回滚——这是我个人习惯的做法。

特性 LFS CoW(ZFS/Btrfs)
写入方式 追加到日志末尾 写入新位置,更新指针
恢复速度 快(回放日志即可) 快(检查元数据树)
空间利用率 需要垃圾回收 需要定期清理孤儿块
快照支持 有限 原生支持
写放大 中等 较高

3.2.3 CoW 的恢复流程

升级中断后,CoW 文件系统的恢复步骤:

  • 读取超级块,找到元数据树的根节点
  • 深度遍历元数据树,检查每个节点的校验和
  • 发现损坏节点,尝试从冗余副本恢复(如果有)
  • 重建元数据树,丢弃未完成的写入

我建议你在升级前至少保留两个快照:一个在升级前一刻,一个在升级开始前。这样即使升级过程中快照本身被修改,你还有退路。

3.3 fsck 工具原理

fsck(file system check)是传统文件系统的救火队员。它不像 LFS 或 CoW 那样有优雅的恢复机制,而是靠暴力扫描来修复问题。

3.3.1 fsck 的工作阶段

  1. 阶段一:检查超级块 — 验证文件系统的基本参数
  2. 阶段二:检查块组描述符 — 确认每个块组的信息一致
  3. 阶段三:检查 inode — 遍历所有 inode,检查类型、权限、链接数
  4. 阶段四:检查块位图 — 对比 inode 引用的块和位图记录
  5. 阶段五:检查目录结构 — 验证目录项的完整性

为什么会分这么多阶段?因为 fsck 必须假设文件系统已经处于不一致状态,每一步都可能发现新问题。

关键点: fsck 不会恢复数据,它只恢复文件系统的元数据一致性。数据本身可能已经丢失,但至少文件系统能挂载了。

3.3.2 fsck 的常见修复场景

问题类型 fsck 的处理方式 数据风险
孤儿 inode 放入 lost+found 目录 低(数据还在)
块位图不一致 重新标记块状态 低
目录项损坏 删除或重建目录项 中(文件名可能丢失)
超级块损坏 使用备份超级块 低(有备份的话)
inode 交叉链接 复制数据块 高(可能产生重复数据)

3.3.3 实战中的 fsck 使用建议

我在实际恢复中总结了几条经验:

  • 先做镜像: 用 dd 把整个分区复制出来,在镜像上跑 fsck
  • 用 -n 参数预览: 先看看 fsck 打算做什么,别直接让它动手
  • 不要中断 fsck: 跑了一半中断,文件系统可能比之前更糟
  • 大分区用 e2fsck -c: 检查坏道,避免 fsck 在坏道上反复失败

警告: 我曾经在 ext4 分区上直接跑 fsck -y,结果它把一些看起来“可疑”但实际正常的文件删了。从那以后,我坚持先用 -n 看报告,手动判断后再决定怎么修。

3.4 三种机制的对比与选择

说了这么多,到底该用哪种?我个人的经验是:

  • 嵌入式设备: LFS 优先,简单可靠,恢复快
  • 服务器/存储: CoW 优先,快照功能太香了
  • 传统 Linux 桌面: ext4 + fsck,够用且兼容性好

你想想看,升级中断这种事,预防永远比恢复重要。选对文件系统,能省掉 80% 的麻烦。

文件系统恢复机制对比 LFS 日志结构 CoW 写时复制 fsck 工具 追加写入 检查点恢复 日志回放 垃圾回收 新块写入 指针更新 快照回滚 孤儿清理 超级块检查 inode 遍历 位图修复 目录重建 恢复速度对比 LFS ≈ CoW(秒级) > fsck(分钟到小时级) 数据安全性:CoW ≥ LFS > 传统 fsck

嗯,这一章的内容就到这里。三种机制各有千秋,关键是要理解它们的恢复逻辑——这样真遇到升级中断时,你才知道该从哪里下手。