3. 文件系统层面的恢复:LFS、CoW 与 fsck 原理
文件系统层面的恢复,说白了就是跟文件系统的“记账本”打交道。你想想看,升级中断时最怕什么?不是数据本身丢了,而是文件系统不知道数据该放哪、哪些是有效的、哪些是垃圾。这一章,我带你看看三种典型的文件系统是怎么处理这个问题的。
3.1 日志结构文件系统(LFS)
LFS 的设计思路很特别——它把整个磁盘当成一个巨大的日志。所有写操作都追加到日志末尾,从不原地修改。这听起来有点浪费空间,但好处是崩溃恢复极其简单。
核心思想: 数据就是日志,日志就是数据。恢复时只需要回放最后一段日志即可。
3.1.1 LFS 的写入流程
- 数据先写入内存中的缓冲区(segment buffer)
- 缓冲区满了,一次性写入磁盘的某个空闲段
- 更新段摘要(segment summary),记录该段包含哪些文件
- 更新检查点(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 会这样做:
- 读取原始数据块
- 在空闲位置写入修改后的数据块
- 更新元数据指针,指向新数据块
- 旧数据块变成“孤儿”,等待回收
你想想看,这意味着什么?任何时候中断,旧数据都还在原地。恢复时只需要检查元数据树的完整性就行了。
注意: 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 的工作阶段
- 阶段一:检查超级块 — 验证文件系统的基本参数
- 阶段二:检查块组描述符 — 确认每个块组的信息一致
- 阶段三:检查 inode — 遍历所有 inode,检查类型、权限、链接数
- 阶段四:检查块位图 — 对比 inode 引用的块和位图记录
- 阶段五:检查目录结构 — 验证目录项的完整性
为什么会分这么多阶段?因为 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% 的麻烦。
嗯,这一章的内容就到这里。三种机制各有千秋,关键是要理解它们的恢复逻辑——这样真遇到升级中断时,你才知道该从哪里下手。