在前滚恢复扫描的时候,f2fs通过两个标记来判断遇到的不同场景是否需要恢复。本文分析各种可能遇到的情况。
(x)指没有fsync标记,也没有dentry标记;(F)、(DF)指具有fsync标记和兼有fsync、dentry两个标记。
其中fsync、dentry标记只在f2fs_fsync_node_pages中设置。也就是说只与fsync和原子写有关。通过fsync落盘的node中会有fsync标记;在CP之后没有做过fsync的新文件,第一次落盘的时候其inode会有dentry标记。对于原子写的文件,在原子写commit后会调用一次fsync。但为了保证其原子性,这一次fsync中只有最后落盘的node会有fsync标记。
flowchart LR
A[f2fs_sync_file]-->B[f2fs_do_sync_file]
B-->C[f2fs_fsync_node_pages]
D[f2fs_ioc_commit_atomic_write]-->B
E[f2fs_ioc_abort_volatile_write]-->B
C-->F["set_fsync_mark(page, 1)"]
C-->G["set_dentry_mark(page,
f2fs_need_dentry_mark(sbi, ino))"]
场景解析在recovery.c的开头。
/*
* Roll forward recovery scenarios.
*
* [Term] F: fsync_mark, D: dentry_mark
*
* 1. inode(x) | CP | inode(x) | dnode(F)
* -> Update the latest inode(x).
*
* 2. inode(x) | CP | inode(F) | dnode(F)
* -> No problem.
*
* 3. inode(x) | CP | dnode(F) | inode(x)
* -> Recover to the latest dnode(F), and drop the last inode(x)
*
* 4. inode(x) | CP | dnode(F) | inode(F)
* -> No problem.
*
* 5. CP | inode(x) | dnode(F)
* -> The inode(DF) was missing. Should drop this dnode(F).
*
* 6. CP | inode(DF) | dnode(F)
* -> No problem.
*
* 7. CP | dnode(F) | inode(DF)
* -> If f2fs_iget fails, then goto next to find inode(DF).
*
* 8. CP | dnode(F) | inode(x)
* -> If f2fs_iget fails, then goto next to find inode(DF).
* But it will fail due to no inode(DF).
*/
其中1~4是该文件在上一次CP之前就存在,而5~8是指该文件在上一次CP之后才创建。
文件在上一次CP之前就存在的情况:
inode(x) | CP | inode(x) | dnode(F)
CP后的inode(x)是后台刷写下来的,而dnode(F)代表做了fdatasync。因此恢复时只需要更新CP后inode(x)中指向数据的指针(下称恢复数据)。
inode(x) | CP | inode(F) | dnode(F)
这种情况就是标准的调用fsync,也可能是一次只更新元数据的fsync和一次fdatasync。恢复的时候除了恢复数据,还要将inode(x)更新成inode(F)。
inode(x) | CP | dnode(F) | inode(x)
可能是fdatasync后又更改了文件,最后的inode(x)是后台刷写下来的。也有可能是fdatasync后做了原子写,但是没有完成。这两种情况根据语义都不需要恢复最新的inode(x),因此只对CP之前的inode(x)恢复数据。
inode(x) | CP | dnode(F) | inode(F)
这种情况和2是相同的,也要恢复数据和inode。
文件在上一次CP之后创建的情况,注意在前滚恢复之前文件系统回滚到了上一个CP点,因此文件系统中是没有这个文件的,即iget不到inode。需要在恢复的时候创建inode,并且通过inode中的pino恢复目录结构。
CP | inode(x) | dnode(F)
后台刷写下来了inode(x),可能又做了一次fsync,但是途中断电了。由于fsync没有做完,所以不恢复这个文件,也就是不管了。
CP | inode(DF) | dnode(F)
文件在CP之后创建,并做了fsync,因此需要恢复。
CP | dnode(F) | inode(DF)
和7是一样的,但是第一次扫描到dnode(F)的时候没办法将其inode加入inode_list链表,因为会iget失败。因此需要在扫描到inode(DF)的时候创建inode并加入inode_list链表,并在第二次扫描的时候使用dnode(F)恢复它的数据。
CP | dnode(F) | inode(x)
暂时没想到为什么会出现这种情况,但是这种情况是肯定不能恢复的。