名詞解釋
cleaning 是append文件系統中常用術語,和trim有聯繫,但也需要注意他們之間的區別和聯繫:
cleaning: LFS 刪去無效數據的過程
trim: SSD 內部刪把dirty塊設置成invalid,以備後續使用的過程
背景
鑑於之前評測的nvme SSD場景下 f2fs 整體性能表現出色,有必要分析其背後的根本原因。f2fs作爲一種append模式的文件系統,
cleaning流程的設計和實現起着影響性能的重要的作用。爲此有必要深入瞭解f2fs的GC的原理和實現。
f2fs GC的整體介紹
f2fs GC模塊和其他模塊的交互(接口)
- mkfs.f2fs 時的操作
在磁盤格式化成f2fs的時候,會做一遍全盤trim。
- trim API
f2fs提供了標準的POSIX trim接口,供外部直接觸發trim。
f2fs GC模塊內部的原理
觸發時機
/*
* [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.
*/
響應機制
- foreground cleaning
當free sections不夠的時候:會觸發 foreground cleaning
- background cleaning
f2fs文件系統有一個內核線程會週期地檢查並執行cleaning 過程
src的選擇
- greedy 策略
每次選擇valid blocks 數量最小的section, 用在foreground cleaning,
目的是爲了減小latency(因爲需要搬遷的valid數據量少);
- cost-best 策略
每次不僅僅考慮section的利用率,還考察其age。 f2fs 根據一個section內的segments
的平均age來決定其age。每個segment的age可以在SIT更新的時候記錄下來。
分情況進行處理:
-
如果是foreground cleaning:
從parent node 的索引裏獲取一個新的free log,然後把上面選定的section中valid blocks數據搬移到free log裏面去; - 如果是background cleaning:
並不是馬上觸發IO。而是把上面選定的section中valid blocks數據讀到page cache,並且設置爲dirty,這樣能合併小塊的寫,
然後讓page cache週期刷到新的空閒塊上。
f2fs GC的特色
上面操作做完之後,上面選定的section被標記爲pre-free, 只有等到checkpoint做完之後,上面的狀態才變爲free,可以被再次
分配出去。原因如下:如果沒有做checkpoint 就直接把那個塊釋放掉,如果它被別人使用之後突然掉電之後再次拉起來,之前的數據會丟。
- f2fs 如何判斷用戶IO是否繁忙
思路:根據一段時間內:用戶提交的請求;page cache刷回的數據的次數
不同策略下cost 的比較方法
爲了統一cost-best 和greedy 策略,f2fs 爲這兩種方法都提供了get_cost接口,分別實現如下:
優先選擇cost成本低的segment去做GC:
greedy 算法
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);
}
cost-best 算法
計算比較複雜: 基於時間局部性原理,最近更新的可能馬上還有更新,爲此降低其做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過程。
觸發trim的條件
需要前端GC配合,尋找得哪些segment需要被trim.
trim的實現
nvme SSD 內部的trim
看論文的啓發
- 是否需要支持normal logging 和threaded logging ?
- 爲了避免SSD內部GC的影響,測試的時候 只使用部分SSD(大約一半空間);
- 測試用例: 參考論文
看代碼啓發
-
GC 也不能太頻繁,因爲有的數據可能會很快被無效掉(發生了覆蓋寫或者刪除操作)
- 如果不用thread logging 的方法,在磁盤空間接近用滿的時候,如果爲了繼續保持append,是不是就需要繼續從一個比較大的聯繫空閒塊開始寫才行(怎麼纔不影響性能?)和從啓始地址開始,有何區別?
- 如何保證GC的速度能夠趕上空間被消耗掉速度?