崩溃恢复包括正常状态下的fsync和上电后的前滚恢复。因3.8版本中的f2fs是初版,较为简单。因此本文分析Linux 3.8版本中的崩溃恢复流程,借以了解崩溃恢复。
flowchart LR
A[f2fs_sync_file]-->B[filemap_write_and_wait_range]
A-->C[f2fs_balance_fs]
A--need_cp-->D[f2fs_sync_fs]
A--!need_cp-->E[sync_node_pages]
A--!need_cp-->F[f2fs_write_inode]
A--!need_cp-->G[filemap_fdatawait_range]
fsync函数的原型为int f2fs_sync_file(struct file *file, loff_t start, loff_t end, int datasync),其作用是将file指向的文件中,偏移量从start到end的部分刷写下盘。
文件系统通过wbc结构体来控制写回过程的行为。这里设置wbc为
struct writeback_control wbc = {
.sync_mode = WB_SYNC_ALL, // 同步写回,等待所有页面的写回完成
.nr_to_write = LONG_MAX, // 暂时不设置在本次操作中最多写回的页面数
.for_reclaim = 0, // 表示当前的写回操作不是为了回收页面触发的,而是由于fsync
// 在内存不足的情况下,内核通过回收页面来释放内存,
// 在回收页面之前要先将页面写回存储设备
};
如果文件系统在挂载时设置为只读的,即sb中设置了MS_RDONLY标志位,则直接返回0,因为无法在只读的文件系统中写回。
接下来通过filemap_write_and_wait_range函数写回start到end的数据。
详见:什么是filemap_write_and_wait_range
在写回数据之后,调用f2fs_balance_fs检测sbi以确定是否还有足够的空闲section,如果没有就做一次主动GC。实际上这个函数经常在和写入相关的操作中被调用,每次写入后都要检查一下。
fsync和fdatasync都通过f2fs_sync_file函数执行。如果当前执行的时fdatasync,则到这里就已经结束返回了。下面是fsync中针对元数据的操作。
if (F2FS_I(inode)->data_version != cur_version &&
!(inode->i_state & I_DIRTY))
goto out;
F2FS_I(inode)->data_version--;
检查该文件的data_version是否等于当前CP的cur_version。如果不等于,说明该文件是上一次CP之前就存在的,盘上有它的inode结构和目录结构。如果该文件的I_DIRTY标志位没有置位,则该文件没有发生任何变化。因此不需要做任何操作。
详见:inode状态标志位