1. 程式人生 > >f2fs系列文章gc

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);
}