由于Log-Structured结构,F2FS文件系统的可用空间会快速被消耗,因此需要引入垃圾回收(GC)来回收空间。
本源码阅读主要为个人阅读F2FS GC源码过程中的记录,主要介绍以下内容。
- 【当前】介绍f2fs gc的主体流程
- 介绍victim segment选择策略(涉及一定的SSR机制相关的内容)
- 介绍GC的执行过程
在每部分内容中,会对涉及的关键数据结构进行阅读与记录,并分析函数的执行流程与若干细节。
F2FS GC 主体流程
1. GC线程创建以及GC的触发时机
此部分为后期补充部分,由于这部分源码变动不大,且网上已经有较为详尽的解析,便不再赘述。
相关链接:F2FS-NOTES/F2FS-GC/GC流程介绍.md at master · RiweiPan/F2FS-NOTES · GitHub
2. f2fs_gc 函数(F2FS GC的关键执行函数)
由上面的gc线程函数可知,f2fs_gc
函数是F2FS文件系统进行GC的关键执行函数。
2.1 关键数据结构
-
gc_inode_list
在
f2fs_gc
函数中,初始化变量gc_list
需要额外关注。该变量的主要作用是将受到GC影响的inode都加入到list中。在数据迁移步骤中,可能需要读取inode信息、以及对data block所属的inode进行一定的修改,因此将这些inode都放入list中,统一处理。struct gc_inode_list { struct list_head ilist; struct radix_tree_root iroot; };
-
f2fs_gc_control
(fs/f2fs/f2fs.h)f2fs_gc_control存储了F2FS GC所需要的一些控制配置参数。
struct f2fs_gc_control { unsigned int victim_segno; /* target victim segment number */ int init_gc_type; /* 初始化的GC类型, 其值为FG_GC or BG_GC */ bool no_bg_gc; /* check the space and stop bg_gc */ bool should_migrate_blocks; /* should migrate blocks */ bool err_gc_skipped; /* return EAGAIN if GC skipped */ unsigned int nr_free_secs; /* # of free sections to do GC,本次GC需要释放的section数 */ };
使用:在gc触发线程中进行初始化以及赋值,并作为函数
f2fs_gc
的参数传入,指导相应的GC逻辑。- 对于前台GC,设定了
gc_control.nr_free_secs=1
,说明本次GC必须要至少回收一个segment, - 对于后台GC,设定了
gc_control.nr_free_secs=0
,说明后台GC可以没有收获。
//函数 gc_thread_func if (foreground) sync_mode = false; gc_control.init_gc_type = sync_mode ? FG_GC : BG_GC; gc_control.no_bg_gc = foreground; //foreground状态下,不进行bggc gc_control.nr_free_secs = foreground ? 1 : 0; if (f2fs_gc(sbi, &gc_control)) { ... }else ...
- 对于前台GC,设定了
2.2 函数主要流程
由F2FS GC原理可知,F2FS GC的核心步骤主要有两个:
-
选择合适的victim segment
在作者定义中,F2FS GC选取的单位为section,但在默认F2FS下,一般一个section大小等同于一个segment大小,大小均为2MB,即能够存储512个block(4KB),且在实现中一般直接使用
segno(# of segment)
来作为选取的结果,因此一般也可视为segment为GC的基本单位。 -
对选取的victim segment中的有效块迁移到其他位置,待全部迁移完成后,释放该segment的空间
其中,完成步骤1的关键函数在于__get_victim
(间接调用的核心函数为f2fs_get_victim
函数),完成步骤2的关键函数在于do_garbage_collect
。在当前函数主流程中,先不进行分析,无需关注这些函数的具体实现,只需知道它们实现了上述的功能即可。(若想进一步了解,请查看f2fs_get_victim
与do_garbage_collect
部分的源码阅读)
f2fs_gc
主要流程如下
-
若可用空间不足,则将gc_type设定为FG_GC
-
空间不足的判断条件:
has_not_enough_free_secs(sbi, freed=0, needed=0)
,下面为该函数的核心判断部分。/**计算dirty node/dentry所需的最少section数量和最多section数量 *lower_secs = node_secs + dent_secs; *upper_secs = node_secs + dent_secs + (node_blocks ? 1 : 0) + (dent_blocks ? 1 : 0); */ __get_secs_required(sbi, &lower_secs, &upper_secs, &curseg_space); free_secs = free_sections(sbi) + freed; lower_secs += needed + reserved_sections(sbi); upper_secs += needed + reserved_sections(sbi); // reserved_sections = SM_I(sbi)->reserved_segments + SM_I(sbi)->additional_reserved_segments // additional_reserved_segment: reserved segs for IO align feature if (free_secs > upper_secs) return false; else if (free_secs <= lower_secs) return true; return !curseg_space;
-
空间不足的情况下,若当前还有PRE FREE状态的segment,则会触发一次checkpoint把这些segment变为free,从而尽量不触发FG_GC。
-
-
选择victim segment:
ret = __get_victim(sbi, &segno, gc_type)
-
若ret返回值为0,则代表成功找到了victim segment,其段号存储在
segno
中。 -
若ret返回值为
-ENODATA
,且当前为FG_GC模式,且存在pinned_section
,则unpin这些sections。FG_GC状态下,急需使用空间,因此在一般方法找不到victim segment的情况下,对于pinned segment的空间也要利用上。
-
-
对选取的victim segment进行垃圾回收(调用函数
do_garbage_collect
),并且统计相应的数据seg_freed = do_garbage_collect(sbi, segno, &gc_list, gc_type, gc_control->should_migrate_blocks); total_freed += seg_freed;
-
运行完上述步骤后,进行一些后处理
-
BG_GC下,且可用空间不紧缺(
has_enough_free_secs
),更新相应信息,退出。注意,在这种情况下,没有触发checkpoint
-
FG_GC下,若可用空间不再紧缺(
has_enough_free_secs(sbi, sec_freed, 0)
)若回收的segment数量还没达到要求,则重新进行上述gc步骤。否则更新相应信息,退出。
-
无论是BG_GC还是FG_GC,若可用空间依然紧缺,则
-
重新计算当前dirty node/dentry所需的最大segment数量
通过函数
__get_secs_required(sbi, NULL, &upper_secs, NULL);
得到upper_secs
-
若free segment数量不足,且PRE链表中仍然有PRE FREE segment,则会触发一次checkpoint把这些segment变为free,若已经不再有PRE FREE segment或CP失败了,则重新进行上述gc步骤。
// NR_GC_CHECKPOINT_SECS:3,需要3个extra sections for writer's data/node/dentry if (free_sections(sbi) <= upper_secs + NR_GC_CHECKPOINT_SECS && prefree_segments(sbi)) { ret = f2fs_write_checkpoint(sbi, &cpc); if (ret) goto stop; //退出 /* Reset due to checkpoint */ sec_freed = 0; } go_gc_more: segno = NULL_SEGNO; goto gc_more; // 重新进行GC步骤
-
对于FG_GC,还要判断
round
和skipped_round
(这部分仍有疑问)if (sbi->skipped_gc_rwsem) skipped_round++; round++; if (skipped_round > MAX_SKIP_GC_COUNT && skipped_round * 2 >= round) { ret = f2fs_write_checkpoint(sbi, &cpc); goto stop; }
-
-