Lec 15: Crash recovery
- Ref: https://github.com/huihongxiao/MIT6.S081/tree/master/lec15-crash-recovery-frans
- Preparation: xv6 book Chapter 8 the logging sections
概述
- Crash safety: 崩溃(crash)或者电源故障可能导致的磁盘上文件系统处于不一致或者不正确状态的问题(如一个 data block 属于两个文件, 一个 inode 被分给两个不同文件)的解决方案.
- Crash 或者故障: 包含了多个步骤的文件系统操作过程中发生的故障.
- 文件系统操作过程中的电源故障
- 文件系统操作过程中的内核 panic
- 解决方案: logging(日志)
logging
特性
- 确保文件系统的系统调用是原子性的. 要么多个操作完全出现, 要么完全不出现.
- 支持快速恢复. 重启后不需要做大量工作来修复文件系统.
- 原则上可以非常高效
思想
- 将 block 分为两部分: 日志(log)和文件系统. 多个写操作先提交到日志, 在批量写入实际的块, 最后清除日志记录.
步骤
- log write: 需要更新文件系统时, 不是更新数据对应的文件系统的块, 而是将数据写入到日志(log).
- commit operation: 在文件系统操作完成时, 在日志的某个位置记录一组相同操作的个数. (该操作只会写 1 个 block, 而文件系统对单个 block/sector 的写操作可视为原子操作)
- install log: 在日志中存储了一组写操作后, 将真正执行这些操作. (该操作是幂等操作)
- clean log: 清除已完成的日志记录.
- 发生崩溃: 查看日志的 commit 记录值, 大于 0 则 log 中存储的 block 需写入文件系统, 进行 reinstall(将 block 再次写入文件系统), 再 clean log.
XV6 日志结构
在磁盘中的日志结构:
- header block: 即提交记录(commit record). 包含: 有效 log block 的数量 n; 每个 log block 实际对应的 block 编号
- log 数据: 记录每个 block 的数据.
XV6 代码特点
- 文件系统中 logging 层以上的所有块写操作
bwrite()
实际上都由log_write()
替换, 即写内存中的 block 后都会记录到日志中, 最后统一提交写入磁盘. - commit operation 的两个操作:
- 将存在于内存 log header 中的 block 编号对应的 block 从 block cache 写入到磁盘的 log 区域中
- 将内存中的 log header 写入磁盘. 该操作为提交点(commit point), 在此之前 crash, 重启后操作将视为未发生, 在此之后 crash 则会被恢复.
- install log 操作: 将文件系统中的 log block 写入到实际对应的 block 中.
- 在系统重启时会执行 install log 操作, 将日志中未更新到磁盘中的数据进行重新写入.
- 实际的写磁盘流程
log_write()
:- 先将数据写入到 log block
- 写 log header, 进行日志提交(将 log block 数据从内存落入磁盘的 log block)
- install log: 将 log block 的数据写入实际文件系统中对应的 block 中(并落盘)
- log clean, 清除 log header 中的记录.
注意点
- buffer cache 不能撤回任何仍位于 log 中的 block(因为在写磁盘时实际上会先写到对应的 block cache 中, 然后再调用
log_write()
写磁盘, 此时若撤回则在事务提交前进行了写磁盘操作), 通过增加 buffer cache 的引用计数解决. - 文件系统必须适配日志的大小, 即文件系统的操作(写的块)必须能够放到 log 的 block 空间中.
- 并发文件系统调用. 若同时并发处理两个文件操作的事务, 可能两个操作用完了 log 的 block 但都未完成, 此时也不能进行事务提交(及落盘).
因此需要保证 log 的 block 足够的情况下才允许并发执行另一个文件操作. 同时, 对于并发的文件操作, 采用组提交(group commit)即统一从 log block 进行落盘.