主旨

在前滚恢复扫描的时候,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之前就存在的情况:

  1. inode(x) | CP | inode(x) | dnode(F)

    CP后的inode(x)是后台刷写下来的,而dnode(F)代表做了fdatasync。因此恢复时只需要更新CP后inode(x)中指向数据的指针(下称恢复数据)。

  2. inode(x) | CP | inode(F) | dnode(F)

    这种情况就是标准的调用fsync,也可能是一次只更新元数据的fsync和一次fdatasync。恢复的时候除了恢复数据,还要将inode(x)更新成inode(F)。

  3. inode(x) | CP | dnode(F) | inode(x)

    可能是fdatasync后又更改了文件,最后的inode(x)是后台刷写下来的。也有可能是fdatasync后做了原子写,但是没有完成。这两种情况根据语义都不需要恢复最新的inode(x),因此只对CP之前的inode(x)恢复数据。

  4. inode(x) | CP | dnode(F) | inode(F)

    这种情况和2是相同的,也要恢复数据和inode。

文件在上一次CP之后创建的情况,注意在前滚恢复之前文件系统回滚到了上一个CP点,因此文件系统中是没有这个文件的,即iget不到inode。需要在恢复的时候创建inode,并且通过inode中的pino恢复目录结构。

  1. CP | inode(x) | dnode(F)

    后台刷写下来了inode(x),可能又做了一次fsync,但是途中断电了。由于fsync没有做完,所以不恢复这个文件,也就是不管了。

  2. CP | inode(DF) | dnode(F)

    文件在CP之后创建,并做了fsync,因此需要恢复。

  3. CP | dnode(F) | inode(DF)

    和7是一样的,但是第一次扫描到dnode(F)的时候没办法将其inode加入inode_list链表,因为会iget失败。因此需要在扫描到inode(DF)的时候创建inode并加入inode_list链表,并在第二次扫描的时候使用dnode(F)恢复它的数据。

  4. CP | dnode(F) | inode(x)

    暂时没想到为什么会出现这种情况,但是这种情况是肯定不能恢复的。