cleaning 是append文件系統中經常使用術語,和trim有聯繫,但也須要注意他們之間的區別和聯繫:前端
cleaning: LFS 刪去無效數據的過程
trim: SSD 內部刪把dirty塊設置成invalid,以備後續使用的過程node
鑑於以前評測的nvme SSD場景下 f2fs 總體性能表現出色,有必要分析其背後的根本緣由。f2fs做爲一種append模式的文件系統,
cleaning流程的設計和實現起着影響性能的重要的做用。爲此有必要深刻了解f2fs的GC的原理和實現。算法
在磁盤格式化成f2fs的時候,會作一遍全盤trim。app
觸發時機ide
/* * [GC triggering condition] * 0. GC is not conducted currently. * 1. There are enough dirty segments. * 2. IO subsystem is idle by checking the # of writeback pages. * 3. IO subsystem is idle by checking the # of requests in * bdev's request list. * * Note) We have to avoid triggering GCs frequently. * Because it is possible that some segments can be * invalidated soon after by user update or deletion. * So, I'd like to wait some time to collect dirty segments. */
後臺GC機制的觸發時機:性能
/* * Background GC is triggered with the following conditions. * 1. There are a number of invalid blocks. * 2. There is not enough free space. */
響應機制測試
當free sections不夠的時候:會觸發 foreground cleaning spa
src的選擇線程
每次選擇valid blocks 數量最小的section, 用在foreground cleaning,
目的是爲了減少latency(由於須要搬遷的valid數據量少);設計
分狀況進行處理:
若是是foreground cleaning:
從parent node 的索引裏獲取一個新的free log,而後把上面選定的section中valid blocks數據搬移到free log裏面去;
並非立刻觸發IO。而是把上面選定的section中valid blocks數據讀到page cache,而且設置爲dirty,這樣能合併小塊的寫,
而後讓page cache週期刷到新的空閒塊上。
f2fs GC的特點
上面操做作完以後,上面選定的section被標記爲pre-free, 只有等到checkpoint作完以後,上面的狀態才變爲free,能夠被再次
分配出去。緣由以下:若是沒有作checkpoint 就直接把那個塊釋放掉,若是它被別人使用以後忽然掉電以後再次拉起來,以前的數據會丟。
爲了統一cost-best 和greedy 策略,f2fs 爲這兩種方法都提供了get_cost接口,分別實現以下:
優先選擇cost成本低的segment去作GC:
cost 是segment中有效數據block的數量
static inline unsigned int get_gc_cost(struct f2fs_sb_info *sbi, unsigned int segno, struct victim_sel_policy *p) { if (p->alloc_mode == ×××) return get_seg_entry(sbi, segno)->ckpt_valid_blocks; /* alloc_mode == LFS */ if (p->gc_mode == GC_GREEDY) return get_valid_blocks(sbi, segno, true); else return get_cb_cost(sbi, segno); }
計算比較複雜: 基於時間局部性原理,最近更新的可能立刻還有更新,爲此下降其作GC的優先級,對應提升其作GC cost;反之,
對應下降其作GC cost。
static unsigned int get_cb_cost(struct f2fs_sb_info *sbi, unsigned int segno) { struct sit_info *sit_i = SIT_I(sbi); unsigned int secno = GET_SEC_FROM_SEG(sbi, segno); unsigned int start = GET_SEG_FROM_SEC(sbi, secno); unsigned long long mtime = 0; unsigned int vblocks; unsigned char age = 0; unsigned char u; unsigned int i; for (i = 0; i < sbi->segs_per_sec; i++) mtime += get_seg_entry(sbi, start + i)->mtime; vblocks = get_valid_blocks(sbi, segno, true); mtime = div_u64(mtime, sbi->segs_per_sec); vblocks = div_u64(vblocks, sbi->segs_per_sec); u = (vblocks * 100) >> sbi->log_blocks_per_seg; /* Handle if the system time has changed by the user */ if (mtime < sit_i->min_mtime) sit_i->min_mtime = mtime; if (mtime > sit_i->max_mtime) sit_i->max_mtime = mtime; if (sit_i->max_mtime != sit_i->min_mtime) age = 100 - div64_u64(100 * (mtime - sit_i->min_mtime), sit_i->max_mtime - sit_i->min_mtime); return UINT_MAX - ((100 * (100 - u) * age) / (100 + u)); } 上面gc 策略計算公式 UINT_MAX - ((100 * (100 - u) * age) / (100 + u)) 的說明以下: u: 單個block內部的使用率(有效塊的數量佔block內部全部塊的比例) age: 當前block的訪問時間距離全部block最近一次訪問的時間間隔(sit_i->max_mtime - mtime),在全部block最久遠和最近訪問時間間隔(sit_i->max_mtime - sit_i->min_mtime)中 的分爲佔比百分數 cost : UINT_MAX - ((100 * (100 - u) * age) / (100 + u)) u固定的狀況下,距離最近訪問的時間越久遠(age越大),cost 值越小,越可能被選中; age固定的狀況下,上述公式等價於 2u/(1+u),意味着 騰挪數據越陳本越低的block 越優先被選取。 參考: Greedy算法 固件須要維護一張Block屬性表,記錄每一個Block當前的Valid Page數量。假設每次GC處理8個Block,查表挑出Valid Page最少的8個Block進行GC,這樣作的好處是複製Valid Page的開銷最小。 Cost-Benefit算法 u表明valid page在該Block中的比例,age表明該Block距離最近一次修改的時間。 1-u是對這個Block進行GC之後可以得到Free Page的數量 2u是對這個Block進行GC的開銷,讀取Valid Page(1個u)而後寫入到新的Block(再1個u) (1-u)/2u能夠理解爲產出投入比 固件須要維護的Block屬性表裏,須要記錄每一個Block最後一次被寫入的時間,GC時選擇更久沒有被修改的Block(冷數據) 該策略就是選擇投入產出比更高,未修改時間更長的Block進行GC,二者相乘數字更大的優先被GC CAT算法 CAT的全稱是Cost Age Times,在Benefit-Cost算法的基礎上,增長了對數據壽命和擦除次數的考慮。 μ表明一個Block裏Valid Page的比例; μ/(1-μ)理解爲爲了釋放出(1-μ)的free page必須付出遷移μ的valid page,也就是總體的Cost; 1/age表明Hot degree跟Age成反比 NumberOfCleaning表明Hot degree跟Block的PE Cycle成正比 對每一個Block進行計算,選擇那些結果最高的Block進行GC過程。
須要前端GC配合,尋找得哪些segment須要被trim.
GC 也不能太頻繁,由於有的數據可能會很快被無效掉(發生了覆蓋寫或者刪除操做)