Lec 16: File system performance and fast crash recovery
- Ref: https://github.com/huihongxiao/MIT6.S081/tree/master/lec16-file-system-performance-and-fast-crash-recovery-robert
- Preparation: Journaling the Linux ext2fs Filesystem (1998)
logging 重要规则
- write ahead rule(预写规则): 一系列写操作具有原子性, 系统需要先将写操作记录到日志中, 再将这些写操作应用到文件系统的实际位置(在做任何实际修改之前将所有更新提交到日志).
- freeing rule(释放规则): 直到日志中所有写操作更新到文件系统之前, 都不能释放或者重用日志. 即在日志中删除一个事务之前, 必须将日志中的所有 block 都写到文件系统中.
XV6 日志的性能瓶颈
- XV6 中的文件相关系统调用, 都需要整个事务完成后才能返回, 即 XV6 写磁盘操作是同步的(synchronized), 需要完成
end_op()
中包含的内容:- 将所有更新的 block 写入日志
- 更新日志头(header block)
- 将日志中的所有 block 写回到文件系统中
- 清除 header block
- 文件相关的系统调用是串行的, 一个文件系统调用的提交(commit)过程中, 其他系统调用不能对文件系统有任何更新.
ext3 文件系统数据结构
- block cache: 缓存了磁盘中的一些 block, 是一种 write-back cache(回写缓存, 缓存稍后才会同步到真正的后端)
- 事务(transaction)信息:
- 事务的序列号(记录在 descriptor block 和 commit block 中)
- 该事务修改的一组 block 的编号, 指向内存的 block cache
- 一组 handle, 有关仍在并发执行的系统调用的信息
磁盘中包括:
- 文件系统树, 包含 inode, 目录, 文件等
- bitmap block: 记录每个数据块是否分配
- 日志:
- super block: 在日志的最开始, 包含第一个有效的事务的偏移量和序列号.
- 每个日志中的事务包含:
- 开头是 1 个 descriptor block: 包含日志数据对应的实际 block 编号, 类似 XV6 的 header block
- 之后是对应 block 编号的实际更新内容的 block
- 对于完成并提交的事务在最后有一个 commit block
- descriptor block 和 commit block 都有一个魔数(magic number)用于与数据块区分.
- 日志的结构类似于一个循环队列, 一个时刻只有一个正在进行的事务还未从内存更新到磁盘, 而其他事务已经完成提交.
ext3 提升性能的方式
- 异步(asynchronous)系统调用: 系统调用在写入磁盘前就会返回, 只更新缓存在内存中的 block, 而不等待写磁盘操作, 但可能会等待读磁盘操作
- 批量(batching)执行: 将多个系统调用打包成一个事务
- 并发(concurrency)
异步系统调用
- 优点:
- 使得系统调用能够快速返回
- 使得大量的批量执行变得容易
- 缺点:
- 系统调用返回不能表示其应该完成的工作已经完成
- 通过
fsync
系统调用等待所有数据写入磁盘后返回
批量执行
- ext3 只有一个正在执行的事务(open transaction)
- 一个事务可以包含多个不同的系统调用. 每隔一定时间会创建一个新事务, 之后一段时间内的系统调用都会被打包到该事务中. ext3会在所有正在进行的系统调用都完成对 block 的写操作后(即到达
stop()
函数时)才能进行提交. - 优点:
- 分摊了执行事务的固有损耗. 包括写 descriptor block 和 commit block, 查找日志位置并等待机械磁盘
- 更容易触发 write absorption(写入吸收): 即由于数据的空间局部性, 多次操作可能更新相同的 block, 因此可以减少写磁盘操作
- disk scheduing(磁盘调度): 向磁盘提交大批量的写操作, 磁盘可以调度这些请求, 进行优化执行.
并发
- ext3 允许多个系统调用同时执行. 在关闭并提交当前事务之前, 系统调用不必等待其它系统调用完成. 在 XV6 中若当前的事务不足够执行新的系统调用, 则新的系统调用不能继续执行.
- ext3 可以有多个不同阶段的事务同时存在
- 有一个打开的事务(open transaction): 用于接收当前系统调用.
- 若干正在提交到日志的事务
- 若干从 block cache 向文件系统中 block 写数据的事务
- 若干正在被释放的事务
ext3 事务提交过程
- 阻止新的系统调用.
- 等待包含在事务中已经开始的系统调用结束(等待其完成更新 block cache 中的数据)
- 开启一个新的事务, 并将 1. 中等待的系统调用继续进行
- 更新(关闭)事务的 descriptor block, 其中包含所有在事务中被修改的 block 的编号
- 将被修改的 block 从缓存写入到磁盘的日志中. 此处写入磁盘日志的是事务结束时对于相关 block cache 的拷贝(即该事务之后的 block 更新时不会写入该事务的磁盘日志中的).
- 等待 4. 5. 两步写日志完成.
- 写 commit block
- 等待写 commit block 结束. 此时达到提交点(commit point).
- 将事务中包含的 block 写入到文件系统的实际位置
- 可以重用事务对应的日志空间.
ext3 文件系统恢复过程
- 恢复软件读取日志的 super block, 找到日志第一个有效事务的位置.
- 根据事务的 descriptor block 中记录的 data block 的数量, 扫描这些 data block. 若当前事务已经完成提交, 就会包含 commit block.
- 然后再去看 commit block 的下一个 block 是否是一个有效的 descriptor block, 然后重复 2. 操作. 若当前事务没有 commit block 或其之后不为有效的 descriptor block则停止扫描.
- 当前已经确定了所有已经提交的事务, 并将这些事务的 log block 写到文件系统的实际位置.
- 恢复完成之后, 才能运行其他程序.
- 注: 除了 descriptor block 和 commit block 的其它 block 不会以 32 比特的魔数起始(有的话会在写日志时替换, 在写文件系统时恢复).
- XV6 只支持一个事务中包换多个系统调用. 但是 XV6 是同步的, 即执行系统调用(写 block)和提交事务不能同时进行, 而 ext3 可以.