主旨

崩溃恢复包括正常状态下的fsync和上电后的前滚恢复。因3.8版本中的f2fs是初版,较为简单。因此本文分析Linux 3.8版本中的崩溃恢复流程,借以了解崩溃恢复。

fsync分析

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状态标志位