文件系统崩溃一致性
概述
文件的创建
找到创建文件所在目录的inode
从inode分配表里找出一个空闲的inode, 标记为占用, 并初始化该inode
将文件名和新分配的inode号作为新的目录项写入目录中.
文件系统崩溃一致性约束
大概就是信息要与实际的一样.
比如超级块保存的文件系统大小, 一定要与文件系统管理的空间大小相同.
目录inode保存的链接数, 一定要与指向其的目录项个数相同.
文件系统崩溃可能会导致这些一致性被打破. 假如在刚才的创建文件中, 有一步突然崩溃了, 那就可能导致某块inode被占用了, 但实际上没有.
一致性写着的跟真的相同.
崩溃与恢复
内存缓存下的崩溃情况
文件的创建中大概涉及这几个修改: 标记inode为占用, 初始化inode, 将目录项写入目录. 当考虑内存缓存时, 这些操作在内存可能是一起或者近似一起执行的, 但是写回时可能不是, 甚至顺序也不能保证.
任一一个修改失效了都会失去一致性. 比如写入目录之后文件系统崩溃了, 这样目录项指向了未初始化的inode; 或者标记inode为占用前崩溃了, 这样就指向了未分配的inode.
这里还没有考虑修改时间戳, 写入目录项分配新的数据块, 修改超级块的统计信息等情况.
期望的恢复
最好就是恢复后大部分文件都还在, 只有最近的操作失效了; 所有磁盘块都正常, 没有被标记空闲但实际被使用的块. 没有顺序异常等.
原子更新技术
文件系统操作要求三个属性
持久化/Durable 哪些操作可见
原子性/Atomic 要不所有操作都可见, 要不都不可见
有序性/Ordered 按照前缀序的方式可见, 如果b可见, 那么a也可见
日志
在进行修改前, 先将修改记录到日志中.
所有要进行的修改都记录完毕后, 提交日志.
此后再进行修改.
修改后, 删除日志.
内存在执行标记inode为占用, 初始化inode等的同时, 在磁盘上记录日志.
如果在日志记录完成前文件系统崩溃了, 恢复后日志不完整, 忽略日志. 新文件不会被创建.
如果日志记录完成后文件系统崩溃了, 恢复后日志完整且存在, 那么执行日志上的操作. 新文件创建成功.
问题
每个操作都写磁盘, 那内存缓存就跟没用一样了.
每个修改都要拷贝数据到日志里.
相同块的多个修改被记录多次
改进
- 利用内存中的页缓存
在内存里记录日志, 异步写入磁盘中. 只要在磁盘修改前提交日志就可以了.
- 批量处理日志以减少磁盘写
多个文件操作的日志合并在一起, 每个修改过的块记录一次就够了.
磁盘的探头一次读取一块数据, 日志批量处理会比较快.
日志提交的触发条件
- 定期触发
每一段时间触发一次
或者日志达到一定量时触发一次
- 用户触发
比如调用fsync
日志系统JBD2
Journal Block Device 2
这是一个通用的日志记录模块, 日志可以以文件形式保存, 也可以直接写入存储设备块.
Journal 日志, 由文件或设备中某区域组成
Handle 原子操作, 由需要原子完成的多个修改组成
Transaction 事务, 多个批量在一起的原子操作
使用过程
这个日志系统中维护了一些块, 超级日志块, 和一些事务块.
事务块由描述块, 日志块和提交块组成.
- 记录日志并提交
从内存写入存储设备
- 将修改写回文件系统中原位置
按照日志记录的命令, 忠实完成
- 删除日志
将事务设置为失效即可
如果事务没写完就崩溃了, 那就把不完整的事务丢掉.
如果事务写完了崩溃, 可以根据事务恢复对文件系统的修改.
Ext4用JBD2实现的三种日志模式
- 写回(write back)模式
日志只记录元数据.
最快, 但一致性最差. 保结构, 但不保数据
- 日志(Journal)模式
元数据和数据都使用日志记录, 一致性最好, 但数据写两次
- 顺序(Ordered)模式
日志只记录元数据, 数据块在元数据日志前写入磁盘
这是默认模式, 新数据可能会被覆盖, 但结构不会乱.
写时复制
在修改多个数据时, 不直接修改数据, 而是将数据复制一份, 在复制上进行修改, 并通过递归的方法将修改变成原子操作. 常用于树状结构.
大概就是比如我要同时修改节点A和节点B, 那么就会复制一份数据, 得到
这样的话, 就复制了一棵小树过来, 之后再把指向原父节点的指针, 指向拷贝过来的父节点就可以了.
其实复制节点并修改后, 找最小公共祖先, 然后把路径相关的节点都复制过来, 最后只需要修改一个指针就可以了. 这个指针的修改是具有原子性的.
很明显存在一个问题, 要写太多次了. 我只想写两个节点, 但是可能需要复制很多个节点. 如果使用日志的话, 每个数据块得写两遍.
文件中的写时复制
将要修改的数据块进行复制, 然后在新的数据块上修改数据.
然后向上递归复制和修改, 比如找自己的上级索引块, 上上级索引块, 反正还是找最小公共祖先.
最后进行原子修改, 然后回收资源.
写时复制也可以用在整个文件系统中. Btrfs.
Soft Updates
日志文件系统
为了追求最好的崩溃一致性, 所有数据都要写两次. 还是太吃性能了, 有没有更好的办法呢? 有的, 只写日志, 不要原来的文件系统了.