“稳定性与原子性应各自证明。一次大write横跨多个块,即使最终fsync成功能保护完整结果,在fsync尚未成功的中间掉电仍可能出现部分新内容。安全文件替换先构造新对象,再原子发布名称,才为…”
形式陈述
目标是把已稳定路径/d/state从完整旧内容OLD替换为完整新内容NEW,并满足:任一崩溃点经恢复后,该路径只指向完整OLD或完整NEW;函数向调用者报告持久成功之后,必须指向NEW。
这需要两类前提。第一,文件与目录同步理路文件同步与目录持久化fsync and directory durability · fdatasync · File persistence boundary为write、设备完成、fsync文件与fsync目录各写出准确承诺,区分内容保存、名称保存和多对象原子性。兑现各自成功承诺,设备正确flush。第二,同目录名称替换理路文件对象、目录与路径名Inode directory and pathname · Hard link and symbolic link · 硬链接与符号链接把名称到文件对象的映射与对象本身分开,执行逐分量路径解析、硬链接计数、符号链接与删除后的存活。不但在运行时原子,而且文件系统恢复保证这次rename作为一个元数据事务要么不发布、要么完整发布。OS-16用重做日志理路文件系统重做日志与提交边界Filesystem redo journal · Journaling filesystem commit把文件数据、位图、inode和目录组成有界块事务,证明payload、commit、home和清日志的稳定顺序及再次崩溃恢复。实现这项恢复原子性,并保留旧对象直到新名称的提交已受保护。本文同目录替换涉及一个目录项块和至多两个inode元数据块,最多3个不同home块,落在4槽日志容量内;旧文件数据块延迟回收,回收另做受保护事务。仅有POSIX/Linux rename的运行时原子性,不足以单独证明这个崩溃前缀合同。
协议还限定:d及旧state的名称与内容已经稳定;临时文件与state在同一文件系统、同一目录;无其他写者或名称操作者;临时名独占创建、权限设置完成;处理短写、所有返回值和最终错误。不讨论网络文件系统、跨设备移动、链接攻击或静默媒体损坏。
直觉
直接覆盖原文件会让旧数据在新数据完整之前消失。替换协议先准备一份独立的新对象,把完整性建立好,再用一次名称发布决定读者看到哪一份。它把大文件写入和小的命名决策分离。
两个同步点保护不同东西。第一个确保新名字一旦指向新对象,就能恢复其完整内容;第二个确保系统已经承诺保留的名字不会在重启后退回旧对象。
例子与边界
四个阶段与恢复答案
初始state→inode45,内容OLD已稳定。独占创建/d/.state.tmp→inode46,按完整写循环写入NEW并设置所需元数据。接着:
- 对inode46的fd成功fsync,确认新内容可恢复
- rename临时名到state,原子替换旧名称
- 对目录d的fd成功fsync,确认名称变化可恢复
- 才向上层报告持久成功
| 崩溃阶段 | 恢复后的state | 临时名 |
|---|---|---|
| 写临时文件期间 | OLD | 可能缺失或不完整,不能当新版本 |
| 文件fsync成功、rename之前 | OLD | 可能存在;新对象内容完整 |
| rename已返回、目录fsync未成功 | OLD或NEW | 取决于恢复的名称事务 |
| 目录fsync成功以后 | NEW | 不再作为待发布入口 |
第三行允许结果未知,但不允许state缺失或指向半写NEW;这条更强结论依赖前述文件系统rename恢复原子性。没有该假设,就不能从一张API调用表推导出同样的允许结果集合。
已经打开旧state的fd仍引用inode45,可能继续读取OLD;rename之后新open取得inode46。协议保证路径发布,不强迫所有历史打开者瞬间改看NEW。
省略同步或拆开重命名会怎样
若跳过临时文件的fsync,目录事务先稳定,恢复可见state→46但内容为空或部分NEW,破坏完整性。若跳过最后目录fsync就报告持久成功,随后崩溃可能恢复OLD,破坏成功后的承诺。
若先unlink旧state,再rename临时文件,两个调用间有真实的名称空窗,且崩溃可能永久失去state。若临时文件在另一文件系统,rename可能失败为跨设备错误;把它改成复制加删除已经是另一协议,不能声称等价。
推论与应用
证明以名称发布为分界:发布前旧对象从未被覆盖,恢复仍可读OLD;发布后新对象的文件同步已经成功,故其内容是完整NEW。恢复原子性排除名称的中间状态,最后目录同步排除成功后回退。这是由对象完整性、名称原子性和同步顺序共同构成的证明,任何一项缺失都有上面的反例。
rename返回成功而目录fsync报错时,应用不应谎报持久成功,也不应断言替换没有发生。当前名称可能已经是NEW,重启结果却尚未获得相同承诺;盲目“回滚”为OLD还会覆盖别人可能观察或随后写出的结果。应将不确定状态交给有版本号或校验的恢复逻辑。
两个文件各执行本协议,仍可能恢复为A新、B旧。若二者有跨文件不变量,可把二者写入带版本号的新目录/对象集合,再原子发布一个清单或根入口;这需要清单引用全部内容先稳定,以及旧版本回收规则。单文件替换不是任意多资源事务。
参考资料
- Linux man-pages 6.19,rename(2)与fsync(2),分别支持运行时名称替换与文件/目录同步接口;崩溃前缀结论另依正文声明的文件系统恢复模型。
- Remzi与Andrea Arpaci-Dusseau,OSTEP: Crash Consistency—FSCK and Journaling,第42章,跨块更新为何需要恢复协议。OS-16的事务选择与完整替换证明为本文构造。