
??想了解更多關于開源的內容,請訪問:??
??51CTO 開源基礎軟件社區??
??https://ost.51cto.com??
前言
前面已經對littlefs的原理分析了5篇文章,內容包括了:
- littlefs整體的存儲結構
- commit機制
- fetch操作
- 目錄操作
- 文件讀寫操作
本文是littlefs原理分析系列最后一篇文章,主要介紹littlefs中與磨損均衡相關的策略,同時也會對其中的塊分配算法進行介紹。
littlefs有以下防止磨損相關的措施:
- 寫時壞塊的檢測和寫入恢復。
- 均勻地進行塊的分配:由塊分配算法實現。
- 定期重分配元數據所在塊。
1、寫時壞塊的檢測和寫入恢復
littlefs中當進行文件、目錄元數據等的寫入時,最后會調用函數lfs_bd_flush將數據最終寫入到磁盤。lfs_bd_flush函數寫入完后,會將內存中寫入的數據和磁盤上的數據進行比較。如果數據不一致,則可能是壞塊。
方法如下:
- 寫入時通過回讀磁盤上的數據進行驗證,來檢測壞塊。
- 檢測到壞塊后,清除壞塊,重新分配塊,然后重新寫入。
lfs_bd_flush函數檢查數據是否一致部分的分析如下:
// 當將緩存中的數據回寫到磁盤時,檢測壞塊
lfs_bd_flush(lfs_t *lfs,
| lfs_cache_t *pcache, lfs_cache_t *rcache, bool validate)
| ...
|
| // 調用lfs_bd_cmp比較磁盤上的數據是否與寫入的數據相同
| // 如果不同則可能遇到了壞塊
|-> if (validate) {
| lfs_bd_cmp(lfs,
| NULL, rcache, diff,
| pcache->block, pcache->off, pcache->buffer, diff);
| }
|
|-> ...
如在文件寫入數據時,在函數lfs_file_flush中,檢測到壞塊時會重新分配塊再進行寫入操作:
lfs_file_flush(lfs_t *lfs, lfs_file_t *file)
|-> ...
|
|-> while (true) {
| // 調用lfs_bd_flush寫入數據,并比較數據是否寫入正確
| int err = lfs_bd_flush(lfs, &file->cache, &lfs->rcache, true);
| if (err) {
| // 檢測到壞塊則跳轉到relocate
| if (err == LFS_ERR_CORRUPT) {
| goto relocate;
| }
| return err;
| }
| break;
|
| relocate:
| // 重新分配塊并再次進行寫入操作
| LFS_DEBUG("Bad block at 0x%"PRIx32, file->block);
| err = lfs_file_relocate(lfs, file);
| if (err) {
| return err;
| }
| }
2、塊分配
(1)lookahead buffer
littlefs中使用一個lookahead buffer來管理和分配塊。lookahead buffer是一個固定大小的bitmap,記錄一片區域內塊分配的信息。
lookahead buffer圖例如下,其中假設總共有64個塊,lookahead buffer的大小為8,lookahead buffer對應塊中現分配了文件A、D和目錄B、C的塊:
![#littlefs原理分析#[六]磨損均衡-開源基礎軟件社區 #littlefs原理分析#[六]磨損均衡-開源基礎軟件社區](https://dl-harmonyos.51cto.com/images/202211/465bd1427d60d64671b659681997b05528e267.png?x-oss-process=image/resize,w_336,h_221)
lookahead buffer相關數據結構如下:
struct lfs_free {
lfs_block_t off; // lookahead所有block整體的偏移
lfs_block_t size; // lookahead中塊的總數
lfs_block_t i; // 在lookahead_size中的索引,表示當前位于第幾個block
lfs_block_t ack; // 所有剩余空閑block個數
uint32_t *buffer; // lookahead的bitmap塊管理緩存區
} free;
(2)查找已分配的塊
lookahead buffer只記錄了一片區域內塊分配的信息,當需要知道其他區域塊分配的情況時,就需要進行掃描文件系統來查找已分配的塊。如lookahead buffer中已經沒有空閑塊、需要推移lookahead buffer來查找文件系統中的其他空閑塊。
掃描和查找已分配的塊的過程如下:
- 將lookahead buffer位置推移一個lookahead_size,并將lookahead buffer清0。
- 從超級塊開始遍歷文件系統中所有目錄和文件,以遍歷所有已分配的塊。如果塊位于lookahead buffer所管理區域,則將lookahead buffer中相應位置為1。
lookahead buffer只用固定大小的bitmap存儲已分配塊的信息,是littlefs中的一種權衡,這樣雖然更耗費時間,但有效節省了RAM空間資源。
代碼分析如下:
lfs_alloc(lfs_t *lfs, lfs_block_t *block)
| ...
|
| // 當lookahead buffer中沒有空閑塊時,需進行掃描
|
| // 1. 推移lookahead buffer
|-> lfs->free.off = (lfs->free.off + lfs->free.size)
| % lfs->cfg->block_count;
| lfs->free.size = lfs_min(8*lfs->cfg->lookahead_size, lfs->free.ack);
| lfs->free.i = 0;
|
| // 2. 將lookahead buffer清0
|-> memset(lfs->free.buffer, 0, lfs->cfg->lookahead_size);
|
| // 3. 遍歷文件系統進行掃描和查找
|-> lfs_fs_rawtraverse(lfs, lfs_alloc_lookahead, lfs, true);
|
|-> ...
其中,lfs_fs_rawtraverse函數會從超級塊開始遍歷整個文件系統,對整個文件系統中所有已經分配的塊調用回調函數lfs_alloc_lookahead。lfs_alloc_lookahead函數分析如下:
// lfs_fs_rawtraverse函數傳入到lfs_alloc_lookahead函數的參數
// 分別為lfs結構體指針p,和塊號block
lfs_alloc_lookahead(void *p, lfs_block_t block)
|-> lfs_t *lfs = (lfs_t*)p;
|
| // 獲取塊號相對lookahead buffer的偏移
|-> lfs_block_t off = ((block - lfs->free.off)
| + lfs->cfg->block_count) % lfs->cfg->block_count;
|
| // 若該塊處于lookahead buffer所管理的范圍內,
| // 則設置bitmap對應位,表示該塊已分配
|-> if (off < lfs->free.size) {
| lfs->free.buffer[off / 32] |= 1U << (off % 32);
| }
| return 0;
(3)塊分配算法
塊分配算法的過程總結:首先嘗試從lookahead buffer中找到下一個空閑塊,若沒有則將lookahead buffer位置推移一個lookahead_size,執行上一小節中的掃描和查找文件系統過程,再嘗試從lookahead buffer中找到下一個空閑塊,以此循環進行。
以下為幾次分配和掃描的示例:
boot... lookahead:
fs blocks: fffff9fffffffffeffffffffffff0000
scanning... lookahead: fffff9ff
fs blocks: fffff9fffffffffeffffffffffff0000
alloc = 21 lookahead: fffffdff
fs blocks: fffffdfffffffffeffffffffffff0000
alloc = 22 lookahead: ffffffff
fs blocks: fffffffffffffffeffffffffffff0000
scanning... lookahead: fffffffe
fs blocks: fffffffffffffffeffffffffffff0000
alloc = 63 lookahead: ffffffff
fs blocks: ffffffffffffffffffffffffffff0000
scanning... lookahead: ffffffff
fs blocks: ffffffffffffffffffffffffffff0000
scanning... lookahead: ffffffff
fs blocks: ffffffffffffffffffffffffffff0000
scanning... lookahead: ffff0000
fs blocks: ffffffffffffffffffffffffffff0000
alloc = 112 lookahead: ffff8000
fs blocks: ffffffffffffffffffffffffffff8000
(4)均勻分配方法
介紹了塊分配算法后,現在回過來介紹塊分配算法中與磨損均衡相關的策略。
littlefs中使用了一個簡單的策略來實現均勻地分配:
- 使用lookahead buffer線性地分配塊,這樣在一次運行中塊分配是循環磁盤均勻進行的。
- 每次掛載文件系統時,將lookahead buffer推移一個隨機的偏移量,這樣在多次運行過程中,只要這個隨機偏移量是均勻的,那么整體的分配也是均勻的。
相關函數分析:
lfs_mount(lfs_t *lfs, const struct lfs_config *cfg)
|-> lfs_rawmount(lfs_t *lfs, const struct lfs_config *cfg)
|-> ...
|
| // 1. 計算隨機數
|-> lfs_dir_fetchmatch(...)
|-> ...
|
| // 使用crc計算隨機數
|-> lfs->seed = lfs_crc(lfs->seed, &crc, sizeof(crc));
|
|-> ...
|
| // 2. 隨機對lookahead buffer進行偏移
|-> lfs->free.off = lfs->seed % lfs->cfg->block_count;
3、定期重分配元數據所在塊
littlefs中會定期進行元數據對應塊的重分配,以防止元數據塊的磨損。
每次元數據commit過程中因空間不足,而進行compact或split操作時,revision count也會隨著更新。當revision count為block_cycles的整數倍時,會進行元數據對應塊的重分配。其中,block_cycles為用戶配置的值。
相關函數分析:
lfs_dir_compact(lfs_t *lfs,
| lfs_mdir_t *dir, const struct lfs_mattr *attrs, int attrcount,
| lfs_mdir_t *source, uint16_t begin, uint16_t end)
|-> ...
|
| // revision count為block_cycles的整數倍時,進行元數據對應塊的重分配
|-> if (lfs->cfg->block_cycles > 0 &&
| (dir->rev % ((lfs->cfg->block_cycles+1)|1) == 0)) {
| ...
|
| // we're writing too much, time to relocate
| tired = true;
| goto relocate;
| }
|
|-> ...
|
| relocate:
|-> ...
總結
本文介紹了littlefs中與磨損均衡相關的策略以及塊分配算法,到這里littlefs文件系統原理分析系列文章已經結束。小編也是希望通過對littlefs文件系統的仔細分析,讓相關讀者更深入了解OpenHarmony LiteOS-A內核的文件系統的原理,而且littlefs文件系統也不僅僅是在OpenHarmony系統上使用,它也是一個廣泛使用的小型文件系統,相信掌握它的原理對嵌入式開發者有著“鼎力相助”的作用。
??想了解更多關于開源的內容,請訪問:??
??51CTO 開源基礎軟件社區??
??https://ost.51cto.com??。