f2fs系列文章gc
這篇文章將講述f2fs的gc,其主要的步驟應該是分為兩步,首先select一個合適的section,然後將section中的資料全部遷移。
f2fs_gc:這個函式主要有兩個函式呼叫gc執行緒和f2fs_balance_fs。首先檢查super_block是否設定MS_ACTIVE,也就是super_block處於活動狀態(目前不知道什麼個狀態),如果設定了就不做gc了。然後再檢查是否設定了CP_ERROR_FLAG(這個表示檔案系統的沒有穩定的cp pack),如果設定了,也是不做gc了。如果此時是BG_GC並且已經沒有足夠的section了,那麼將gc_type設定成FG_GC。如果這種情況下,沒有廢棄的prefree segment並且呼叫get_victim函式也沒有獲得section,但是還有足夠的section,那麼此時不需做write_checkpoint,其他情況下都要進行write_checkpoint並且選擇的segno被設定成NULL_SEGNO。接著檢查呼叫get_victim_by_default來選擇victim section來進行垃圾回收。然後就呼叫do_garbage_collect對選擇的section進行資料的遷移。如果是BG_GC進入的f2fs_gc,這個時候還需要檢查有沒有足夠的section,如果沒有的話,繼續回去進行新一輪的回收,另外如果此時的gc_type是FG_GC,那麼進行write_checkpoint操作。
int f2fs_gc(struct f2fs_sb_info *sbi, bool sync) { unsigned int segno; int gc_type = sync ? FG_GC : BG_GC; int sec_freed = 0; int ret = -EINVAL; struct cp_control cpc; struct gc_inode_list gc_list = { .ilist = LIST_HEAD_INIT(gc_list.ilist), .iroot = RADIX_TREE_INIT(GFP_NOFS), }; cpc.reason = __get_cp_reason(sbi); gc_more: segno = NULL_SEGNO; if (unlikely(!(sbi->sb->s_flags & MS_ACTIVE))) goto stop; if (unlikely(f2fs_cp_error(sbi))) { ret = -EIO; goto stop; } if (gc_type == BG_GC && has_not_enough_free_secs(sbi, sec_freed, 0)) { gc_type = FG_GC; if (__get_victim(sbi, &segno, gc_type) || prefree_segments(sbi)) { ret = write_checkpoint(sbi, &cpc); if (ret) goto stop; segno = NULL_SEGNO; } else if (has_not_enough_free_secs(sbi, 0, 0)) { ret = write_checkpoint(sbi, &cpc); if (ret) goto stop; } } if (segno == NULL_SEGNO && !__get_victim(sbi, &segno, gc_type)) goto stop; ret = 0; if (do_garbage_collect(sbi, segno, &gc_list, gc_type) && gc_type == FG_GC) sec_freed++; if (gc_type == FG_GC) sbi->cur_victim_sec = NULL_SEGNO; if (!sync) { if (has_not_enough_free_secs(sbi, sec_freed, 0)) goto gc_more; if (gc_type == FG_GC) ret = write_checkpoint(sbi, &cpc); } stop: mutex_unlock(&sbi->gc_mutex); put_gc_inode(&gc_list); if (sync) ret = sec_freed ? 0 : -EAGAIN; return ret; }
get_victim_by_default:首先初始化選擇過程中使用到的資料結構victim_sel_policy,先說一下其欄位的含義:alloc_mode,可以取值LFS和SSR,選擇過程中這兩種的處理模式是不同的。gc_mode這個是計算cost的演算法,取值GC_CB和GC_GREEDY。dirty_segmap是記錄dirty的segment的點陣圖,選擇過程中需要在dirty的segment的section中選擇。max_search表示查詢過程中的最多的segment的數量,實際就是上述點陣圖的dirty的segment的數量。offset表示在便利過程中的當前的查詢偏移。ofs_unit表示在查詢過程中每次查詢跨越的單元,SSR是以1個segment為單元,LFS是以1個section為單元。min_cost記錄查詢過程中的最小cost。min_segno記錄的是查詢過程中最小cost所對應的segno。
struct victim_sel_policy {
int alloc_mode;
int gc_mode;
unsigned long *dirty_segmap;
unsigned int max_search;
unsigned int offset;
unsigned int ofs_unit;
unsigned int min_cost;
unsigned int min_segno;
};
所以其初始化時通過函式select_policy完成的。然後檢查是不是以LSF並且是FG_GC的方式進行select,如果是就直接使用之前BG_GC選擇過的GC(這樣選擇的section其有效塊數比較少,具體原因不清楚)。如果對應的segno不是NULL_SEGNO,那就找到了。否則需要跟其他的一樣對所有的有髒的segment的section進行計算cost,然後選擇出最小的cost作為最後的結果。可能是為了均勻的原因,這個遍歷不是從頭開始的,而是從上次的選擇開始的,然後遍歷整個迴圈。首先通過函式find_next_bit在dirty_segmap中找到dirty的segment,接下來的判斷是為了完成迴圈的掉頭。然後呼叫函式count_bits計算這個單元中的dirty的segment的數量。對找到的dirty的segment所在的單元檢查,呼叫sec_usage_check檢查這個segment是否為current segment,如果是就跳過這個單元。另外為了平均一下被選中的情況victim_secmap記錄了BG_GC情況下被選擇過的section,如果是備選過,那也跳過這個選擇。沒有問題了就呼叫get_gc_cost計算整個單元中的cost,如果這個cost比之前的所有的cost都小,那麼修改victim_sel_policy來記錄當前的單元是cost最小的單元。然後比較檢查過的dirty的segment和dirtysegmap中的dirty的segment的個數,如果超過了,那就直接可以停止查找了。查詢結束之後,檢查segno==NULL_SEGNO,如果是,那就是在查詢過程中沒有找到相應的segno,返回NULL_SEGNO。如果不是,對於FG_GC那就將cur_victim_sec設定為選擇的segment(根據上面的查詢可以看出,這裡雖然是選擇的segment,實際上計算的cost是以section計算的,所以這個segment指的是代表了它所在的section的segment)的對應的section。對於BG_GC,將victim_secmap對應的section置位。接下來就將結果置為segment所在的section的起始segment。
static int get_victim_by_default(struct f2fs_sb_info *sbi,
unsigned int *result, int gc_type, int type, char alloc_mode)
{
struct dirty_seglist_info *dirty_i = DIRTY_I(sbi);
struct victim_sel_policy p;
unsigned int secno, last_victim;
unsigned int last_segment = MAIN_SEGS(sbi);
unsigned int nsearched = 0;
mutex_lock(&dirty_i->seglist_lock);
p.alloc_mode = alloc_mode;
select_policy(sbi, gc_type, type, &p);
p.min_segno = NULL_SEGNO;
p.min_cost = get_max_cost(sbi, &p);
if (p.max_search == 0)
goto out;
last_victim = sbi->last_victim[p.gc_mode];
if (p.alloc_mode == LFS && gc_type == FG_GC) {
p.min_segno = check_bg_victims(sbi);
if (p.min_segno != NULL_SEGNO)
goto got_it;
}
while (1) {
unsigned long cost;
unsigned int segno;
segno = find_next_bit(p.dirty_segmap, last_segment, p.offset);
if (segno >= last_segment) {
if (sbi->last_victim[p.gc_mode]) {
last_segment = sbi->last_victim[p.gc_mode];
sbi->last_victim[p.gc_mode] = 0;
p.offset = 0;
continue;
}
break;
}
p.offset = segno + p.ofs_unit;
if (p.ofs_unit > 1) {
p.offset -= segno % p.ofs_unit;
nsearched += count_bits(p.dirty_segmap, p.offset - p.ofs_unit, p.ofs_unit);
} else {
nsearched++;
}
secno = GET_SECNO(sbi, segno);
if (sec_usage_check(sbi, secno))
goto next;
if (gc_type == BG_GC && test_bit(secno, dirty_i->victim_secmap))
goto next;
cost = get_gc_cost(sbi, segno, &p);
if (p.min_cost > cost) {
p.min_segno = segno;
p.min_cost = cost;
}
next:
if (nsearched >= p.max_search) {
if (!sbi->last_victim[p.gc_mode] && segno <= last_victim)
sbi->last_victim[p.gc_mode] = last_victim + 1;
else
sbi->last_victim[p.gc_mode] = segno + 1;
break;
}
}
if (p.min_segno != NULL_SEGNO) {
got_it:
if (p.alloc_mode == LFS) {
secno = GET_SECNO(sbi, p.min_segno);
if (gc_type == FG_GC)
sbi->cur_victim_sec = secno;
else
set_bit(secno, dirty_i->victim_secmap);
}
*result = (p.min_segno / p.ofs_unit) * p.ofs_unit;
trace_f2fs_get_victim(sbi->sb, type, gc_type, &p,
sbi->cur_victim_sec, prefree_segments(sbi), free_segments(sbi));
}
out:
mutex_unlock(&dirty_i->seglist_lock);
return (p.min_segno == NULL_SEGNO) ? 0 : 1;
}
do_garbage_collect:首先預讀需要gc的section中的所有的segment的f2fs_summary,然後對section中的每個segment進行遍歷。首先檢查segment中的有效塊數是否為零,如果是零就直接跳到下一個segment,另外如果segment對應的summary不是最新的,或者f2fs_checkpoint設定了CP_ERROR_FLAG,也是跳過下一個segment。如果不滿足上述的條件,那麼根據需要遷移的segment的型別來進行node或者data的遷移,這個是通過兩個不同的函式gc_node_segment和gc_data_segment實現的。回收完成之後,如果是FG_GC,那就馬上提交Io,另外如果FG_GC下經過回收後的section中的有效塊數變為了0,那麼就返回1,作為釋放的section的個數,可能用於接下來繼續回收。
static int do_garbage_collect(struct f2fs_sb_info *sbi, unsigned int start_segno,
struct gc_inode_list *gc_list, int gc_type)
{
struct page *sum_page;
struct f2fs_summary_block *sum;
struct blk_plug plug;
unsigned int segno = start_segno;
unsigned int end_segno = start_segno + sbi->segs_per_sec;
int sec_freed = 0;
unsigned char type = IS_DATASEG(get_seg_entry(sbi, segno)->type) ? SUM_TYPE_DATA : SUM_TYPE_NODE;
if (sbi->segs_per_sec > 1)
ra_meta_pages(sbi, GET_SUM_BLOCK(sbi, segno), sbi->segs_per_sec, META_SSA, true);
while (segno < end_segno) {
sum_page = get_sum_page(sbi, segno++);
unlock_page(sum_page);
}
blk_start_plug(&plug);
for (segno = start_segno; segno < end_segno; segno++) {
sum_page = find_get_page(META_MAPPING(sbi), GET_SUM_BLOCK(sbi, segno));
f2fs_put_page(sum_page, 0);
if (get_valid_blocks(sbi, segno, 1) == 0 || !PageUptodate(sum_page) ||
unlikely(f2fs_cp_error(sbi)))
goto next;
sum = page_address(sum_page);
f2fs_bug_on(sbi, type != GET_SUM_TYPE((&sum->footer)));
if (type == SUM_TYPE_NODE)
gc_node_segment(sbi, sum->entries, segno, gc_type);
else
gc_data_segment(sbi, sum->entries, gc_list, segno, gc_type);
stat_inc_seg_count(sbi, type, gc_type);
next:
f2fs_put_page(sum_page, 0);
}
if (gc_type == FG_GC)
f2fs_submit_merged_bio(sbi, (type == SUM_TYPE_NODE) ? NODE : DATA, WRITE);
blk_finish_plug(&plug);
if (gc_type == FG_GC && get_valid_blocks(sbi, start_segno, sbi->segs_per_sec) == 0)
sec_freed = 1;
stat_inc_call_count(sbi->stat_info);
return sec_freed;
}
gc_node_segment:首先計算segment的起始塊地址,然後再對segment中的每個block遍歷。如果是BG_GC並且沒有足夠的section了,那麼就直接返回停止BG_GC。然後檢查當前block的sit是否為有效,如果無效,那麼這塊是不用遷移的,直接跨過。然後讀取當前node的所在的f2fs_nat_block,然後預讀該block所放置的node,接著獲取該f2fs_node,再次檢查當前block的sit是否有效,如果無效也是直接跨過。接著獲取該node的node_info,然後檢查node_info中的最新的塊地址是否跟當前塊地址相同,如果不同說明當前block盛放的不是該node的最新資料,所以快過當前block。否則呼叫move_node_page進行真正的遷移動作。
static void gc_node_segment(struct f2fs_sb_info *sbi,
struct f2fs_summary *sum, unsigned int segno, int gc_type)
{
struct f2fs_summary *entry;
block_t start_addr;
int off;
int phase = 0;
start_addr = START_BLOCK(sbi, segno);
next_step:
entry = sum;
for (off = 0; off < sbi->blocks_per_seg; off++, entry++) {
nid_t nid = le32_to_cpu(entry->nid);
struct page *node_page;
struct node_info ni;
if (gc_type == BG_GC && has_not_enough_free_secs(sbi, 0, 0))
return;
if (check_valid_map(sbi, segno, off) == 0)
continue;
if (phase == 0) {
ra_meta_pages(sbi, NAT_BLOCK_OFFSET(nid), 1, META_NAT, true);
continue;
}
if (phase == 1) {
ra_node_page(sbi, nid);
continue;
}
node_page = get_node_page(sbi, nid);
if (IS_ERR(node_page))
continue;
if (check_valid_map(sbi, segno, off) == 0) {
f2fs_put_page(node_page, 1);
continue;
}
get_node_info(sbi, nid, &ni);
if (ni.blk_addr != start_addr + off) {
f2fs_put_page(node_page, 1);
continue;
}
move_node_page(node_page, gc_type);
stat_inc_node_blk_count(sbi, 1, gc_type);
}
if (++phase < 3)
goto next_step;
}
move_node_page:對於FG_GC ,首先將f2fs_node置成dirty,然後呼叫node的寫函式f2fs_write_node_page來進行真正的操作。對於BG_GC,由於不著急,那麼僅僅將當前的f2fs_node置為dirty就行了。
void move_node_page(struct page *node_page, int gc_type)
{
if (gc_type == FG_GC) {
struct f2fs_sb_info *sbi = F2FS_P_SB(node_page);
struct writeback_control wbc = {
.sync_mode = WB_SYNC_ALL,
.nr_to_write = 1,
.for_reclaim = 0,
};
set_page_dirty(node_page);
f2fs_wait_on_page_writeback(node_page, NODE, true);
f2fs_bug_on(sbi, PageWriteback(node_page));
if (!clear_page_dirty_for_io(node_page))
goto out_page;
if (NODE_MAPPING(sbi)->a_ops->writepage(node_page, &wbc))
unlock_page(node_page);
goto release_page;
} else {
if (!PageWriteback(node_page))
set_page_dirty(node_page);
}
out_page:
unlock_page(node_page);
release_page:
f2fs_put_page(node_page, 0);
}
gc_data_segment:首先計算segment的起始塊地址,然後再對segment中的每個block遍歷。如果是BG_GC並且沒有足夠的section了,那麼就直接返回停止BG_GC。然後檢查當前block的sit是否為有效,如果無效,那麼這塊是不用遷移的,直接跨過。然後預讀當前data block的dnode所在的f2fs_nat_block,接著預讀dnode本身。然後呼叫is_alive函式檢查當前的資料塊是不是有效的(可能在dnode中相應的位置已經有新的資料填充了,這個新的資料在新的位置),如果無效就跨過。接著預讀對應的f2fs_inode,接著獲取inode,如果該inode加密其標誌REG,那麼呼叫add_gc_inode將inode加入到平gc管理的連結串列和radixtree中,如果不滿足這個條件的,呼叫get_read_data_page來讀取該block並且呼叫函式add_gc_inode將inode加入到平gc管理的連結串列和radixtree中。接著對每個塊的inode,都在gc管理的radix tree中尋找,然後計算block在檔案中的index,然後根據是否加密分別呼叫move_encrypted_block和move_data_page對資料進行遷移。
static void gc_data_segment(struct f2fs_sb_info *sbi, struct f2fs_summary *sum,
struct gc_inode_list *gc_list, unsigned int segno, int gc_type)
{
struct super_block *sb = sbi->sb;
struct f2fs_summary *entry;
block_t start_addr;
int off;
int phase = 0;
start_addr = START_BLOCK(sbi, segno);
next_step:
entry = sum;
for (off = 0; off < sbi->blocks_per_seg; off++, entry++) {
struct page *data_page;
struct inode *inode;
struct node_info dni;
unsigned int ofs_in_node, nofs;
block_t start_bidx;
nid_t nid = le32_to_cpu(entry->nid);
if (gc_type == BG_GC && has_not_enough_free_secs(sbi, 0, 0))
return;
if (check_valid_map(sbi, segno, off) == 0)
continue;
if (phase == 0) {
ra_meta_pages(sbi, NAT_BLOCK_OFFSET(nid), 1, META_NAT, true);
continue;
}
if (phase == 1) {
ra_node_page(sbi, nid);
continue;
}
if (!is_alive(sbi, entry, &dni, start_addr + off, &nofs))
continue;
if (phase == 2) {
ra_node_page(sbi, dni.ino);
continue;
}
ofs_in_node = le16_to_cpu(entry->ofs_in_node);
if (phase == 3) {
inode = f2fs_iget(sb, dni.ino);
if (IS_ERR(inode) || is_bad_inode(inode))
continue;
if (f2fs_encrypted_inode(inode) && S_ISREG(inode->i_mode)) {
add_gc_inode(gc_list, inode);
continue;
}
start_bidx = start_bidx_of_node(nofs, inode);
data_page = get_read_data_page(inode, start_bidx + ofs_in_node, REQ_RAHEAD, true);
if (IS_ERR(data_page)) {
iput(inode);
continue;
}
f2fs_put_page(data_page, 0);
add_gc_inode(gc_list, inode);
continue;
}
inode = find_gc_inode(gc_list, dni.ino);
if (inode) {
struct f2fs_inode_info *fi = F2FS_I(inode);
bool locked = false;
if (S_ISREG(inode->i_mode)) {
if (!down_write_trylock(&fi->dio_rwsem[READ]))
continue;
if (!down_write_trylock(&fi->dio_rwsem[WRITE])) {
up_write(&fi->dio_rwsem[READ]);
continue;
}
locked = true;
}
start_bidx = start_bidx_of_node(nofs, inode) + ofs_in_node;
if (f2fs_encrypted_inode(inode) && S_ISREG(inode->i_mode))
move_encrypted_block(inode, start_bidx);
else
move_data_page(inode, start_bidx, gc_type);
if (locked) {
up_write(&fi->dio_rwsem[WRITE]);
up_write(&fi->dio_rwsem[READ]);
}
stat_inc_data_blk_count(sbi, 1, gc_type);
}
}
if (++phase < 5)
goto next_step;
}
is_alive:這個函式主要確認一個data block是不是有效的。首先根據summary得到對應的dnode,然後對比dnode中的version和node_info中的version,然後再對比block的地址和dnode中的地址,只有兩者都是一致的才能說明這個data block是有效的。
static bool is_alive(struct f2fs_sb_info *sbi, struct f2fs_summary *sum,
struct node_info *dni, block_t blkaddr, unsigned int *nofs)
{
struct page *node_page;
nid_t nid;
unsigned int ofs_in_node;
block_t source_blkaddr;
nid = le32_to_cpu(sum->nid);
ofs_in_node = le16_to_cpu(sum->ofs_in_node);
node_page = get_node_page(sbi, nid);
if (IS_ERR(node_page))
return false;
get_node_info(sbi, nid, dni);
if (sum->version != dni->version) {
f2fs_put_page(node_page, 1);
return false;
}
*nofs = ofs_of_node(node_page);
source_blkaddr = datablock_addr(node_page, ofs_in_node);
f2fs_put_page(node_page, 1);
if (source_blkaddr != blkaddr)
return false;
return true;
}
move_data_page:完成真正的遷移。首先判斷是否為BG_GC,如果是的話就不著急回收,將對應的data block設定dirty並置為cold就行了。否則就著急回收。所以將對應的data block設定dirty並置為cold,然後呼叫寫函式do_write_data_page進行寫回操作。
static void move_data_page(struct inode *inode, block_t bidx, int gc_type)
{
struct page *page;
page = get_lock_data_page(inode, bidx, true);
if (IS_ERR(page))
return;
if (gc_type == BG_GC) {
if (PageWriteback(page))
goto out;
set_page_dirty(page);
set_cold_data(page);
} else {
struct f2fs_io_info fio = {
.sbi = F2FS_I_SB(inode),
.type = DATA,
.op = REQ_OP_WRITE,
.op_flags = WRITE_SYNC,
.page = page,
.encrypted_page = NULL,
};
bool is_dirty = PageDirty(page);
int err;
retry:
set_page_dirty(page);
f2fs_wait_on_page_writeback(page, DATA, true);
if (clear_page_dirty_for_io(page))
inode_dec_dirty_pages(inode);
set_cold_data(page);
err = do_write_data_page(&fio);
if (err == -ENOMEM && is_dirty) {
congestion_wait(BLK_RW_ASYNC, HZ/50);
goto retry;
}
clear_cold_data(page);
}
out:
f2fs_put_page(page, 1);
}