文件系统面临的一个主要挑战在于,如何在出现断电(power loss)或系统崩溃(system crash)的情况下,更新持久数据结构。
由于崩溃而导致磁盘文件系统映像可能出现的许多问题:在文件系统数据结构中可能存在不一致性。可能有空间泄露,可能将垃圾数据返回给用户,等等。
崩溃一致性问题(crash-consistency problem)也称一致性更新问题,consistent-update problem)
理想的做法是将文件系统从一个一致状态(在文件被追加之前),原子地(atomically)移动到另一个状态(在inode、位图和新数据块被写入磁盘之后)。
但做到这一点不容易,因为磁盘一次只提交一次写入,而这些更新之间可能会发生崩溃或断电。
例子:
lseek()
文件系统包括一个inode位图(inode bitmap,只有8位,每个inode一个),一个数据位图(data bitmap,也是8位,每个数据块一个),inode(总共8个,编号为0到7,分布在4个块上),以及数据块(总共8个,编号为0~7)。以下是该文件系统的示意图:
一个应用以某种方式更新磁盘结构:将单个数据块附加到原有文件
在文件操作过程中可能会发生崩溃,从而干扰磁盘的这些更新。特别是,如果这些写入中的一个或两个完成后发生崩溃,而不是全部 3个,则文件系统可能处于有趣(不一致)的状态。
崩溃场景
解决方案1:文件系统检查程序 fsck
解决方案2:基于预写日志(write ahead log)的文件系统
早期的文件系统采用了一种简单的方法来处理崩溃一致性。基本上,它们决定让不一致的事情发生,然后再修复它们(重启时)。
唯一目标,是确保文件系统元数据内部一致。
注:inode有很多错误可能性,比如其inode内的元数据不一致:inode有文件的长度记录,但其实际指向的数据块大小小于其文件长度。
解决方案2:日志(或预写日志)
对于一致更新问题,最流行的解决方案可能是从数据库管理系统的世界中借鉴的一个想法。这种名为预写日志(write-ahead logging)的想法,是为了解决这类问题而发明的。在文件系统中,出于历史原因,我们通常将预写日志称为日志(journaling)。第一个实现它的文件系统是Cedar [H87],但许多现代文件系统都使用这个想法,包括Linux ext3和ext4、reiserfs、IBM的JFS、SGI的XFS和Windows NTFS。
基本思路
解决方案2:日志(或预写日志) 基本思路
解决方案2:日志(或预写日志) 数据日志(data journaling)
上述 transaction 写到磁盘上后, 更新磁盘,覆盖相关结构 (checkpoint)
解决方案2:日志(或预写日志) 写入日志期间发生崩溃 磁盘内部可以(1)写入TxB、I[v2]、B[v2]和TxE,然后才写入Db。遗憾的是,如果磁盘在(1)和(2)之间断电,那么磁盘上会变成:
解决方案2:日志(或预写日志) 为避免该问题,文件系统分两步发出事务写入。首先,它将除TxE块之外的所有块写入日志,同时发出这些写入。当这些写入完成时,日志将看起来像这样(假设又是文件追加的工作负载):
解决方案2:日志(或预写日志) 当这些写入完成时,文件系统会发出TxE块的写入,从而使日志处于最终的安全状态:
解决方案2:日志(或预写日志) 此过程的一个重要方面是磁盘提供的原子性保证。因此,我们当前更新文件系统的协议如下,3个阶段中的每一个都标上了名称。
解决方案2:日志(或预写日志) 恢复 在此更新序列期间的任何时间都可能发生崩溃。
太多写,慢!
解决方案2:日志(或预写日志) -- 恢复 -- 提高速度
解决方案2:日志(或预写日志) -- 恢复 -- 提高速度 新的更新过程
解决方案2:Metadata Journaling: 进一步提高速度
新的更新过程
通过强制首先写入数据,文件系统可保证指针永远不会指向垃圾数据。
Data Journaling时间线 v.s. Metadata Journaling时间线
![w:900](figs/crash-ex.png)