Linux ubi子系統原理分析

廣漠飄羽發表於2019-05-16

本文思維導圖總綱:
思維導圖總綱

綜述

關於ubi子系統,早已有比較正式的介紹,也提供非常形象的介紹ubi子系統ppt
國內的前輩 alloysystem 不辭辛勞為我們提供了部分正式介紹的中文譯文,以及找不到原文的轉載譯文

感謝這些資料讓我迅速入門ubi,進而整理出這博文

此博文是對上文的總結以及中文譯文的補充

在閱讀本文之前,建議先學習PPT中文譯文

概念對比

UBI Vs. MTD

UBI層次

上圖非常形象地描述了從Flash到UBIFS的各個層次。從上圖我們發現,MTD子系統在實際的Flash驅動之上 ,而UBI子系統則在MTD子系統之上。

要對比UBI和MTD的概念,我們不妨問自己一個問題,UBI和MTD兩個不同的層次的"使命"分別是什麼?

Flash驅動直接操作裝置,而MTD在Flash驅動之上,向上呈現統一的操作介面。所以MTD的"使命"是 遮蔽不同Flash的操作差異,向上提供統一的操作介面

UBI基於MTD,那麼UBI的目的是什麼呢? 在MTD上實現nand特性的管理邏輯,向上遮蔽nand的特性

nand有什麼特性呢?
(下文描述的 Nand驅動,是廣義上的操作Nand的集合,包括fs/ubi/mtd的層次,而非純粹的nand驅動)

1. 操作最小單元為頁(Page)/塊(Block)
    Nand不同於Nor,Nor可以以位元組為單位操作Flash,但Nand的讀寫最小單元是頁,擦除最小單元是塊。
    對常見的1Gbit的spinand而言,其頁大小2KBytes,塊大小是128K,表示一個塊有64個頁。
2. 擦除壽命限制
    Nand的物理性質決定了其每個塊都有擦除壽命的限制,SLC約10W次,MLC約5000次,TLC約1000次。
    因此,Nand驅動必須要做到磨損平衡。
    所謂磨損平衡,就是儘可能均衡使用每一個塊,既不讓一個塊太大壓力,也不讓一個塊太過空閒。
3. 位翻轉(bit-flips)
    Nand的物理性質使其可能會在使用、儲存過程中出現位翻轉的現象。
    例如,原始資料為0xFFFC,在儲存過程中Flash的資料卻變成了0xFFFF。
    所以要不在nand內部,要不在nand控制器都會存在ecc校正模組,在位翻轉後校正。
    然而,ecc並不是萬能的,其校正能力有限,所以驅動必須在位翻轉數量進一步變多之前把資料搬移到其他塊。
    萌新可能會有疑問,ecc都已經校正了為什麼還要搬移?因為ecc校正的是從Flash中讀到記憶體中的資料,
    而不是Flash本身儲存的資料,換句話說,此時Flash中的資料依然是錯的,如果不搬移,隨著翻轉的位數量積累,
    ecc就校正不了了,此時就相當於永久丟失正確資料了。
4. 存在壞塊(Bad Block)
    製作工藝和Nand本身的物理性質,導致在出廠和正常使用過程中都會產生壞塊。
    所謂壞塊,就是說這個塊已經損壞,不能再用於儲存資料,因此Nand驅動需要能自動跳過壞塊。

關於SLC/MLC/TLC的比較,可參考這篇部落格

UBI Vs. UBIFS

如果說UBI在MTD之上,在FS之下的中間層,用於抽象MTD遮蔽nand差異,那麼ubifs就是正兒八經的檔案系統。
ubifs是基於UBI子系統的檔案系統,實現檔案系統該有的所有基本功能,例如檔案的實現,例如日誌的實現。

這裡需要特別注意的是,ubifs跟jffs/yaffs相比,並不包含nand特性的管理,而是交由ubi來實現。

UBI Vs. Block Layer

Block Layer是適用於常見塊裝置的通用塊層,其特有的概念有bio、request、電梯演算法等,其典型的裝置有磁碟、SSD、mmc等。
而ubi基於mtd,雖然能模擬塊裝置,從本質上來講其並不是塊裝置。跟蹤UBIFS的IO操作,發現其IO操作並不經過通用塊裝置層。

UBI Vs. FTL

FTL(Flash Translation Layer)是一個"黑盒子",其跟UBI非常像,都是對nand特性進行封裝。

按我的理解,UBI跟FTL的目標不同,導致其實現上會有差異。UBI遮蔽nand特性是為了對接UBIFS,而FTL則是為了對接Block Layer。例如MMC其實也是封裝起來的Nand,只不過在MMC內部實現了FTL,經過FTL的轉換就能以塊裝置層的方法直接操作Nand,就能在mmc上格式化常見的塊檔案系統,例如EXT、VFAT等。

UBI Volume Vs. UBI Device

在UBI中還有兩個概念,分別是UBI卷(UBI Volume)和UBI裝置(UBI Device)。這兩個概念,我們可以這麼理解:

UBI裝置 相當於 磁碟裝置(sda,mmcblk0)
UBI卷 相當於 磁碟上對應分割槽(sda1,mmcblk0p1)

換句話說,UBI裝置是在MTD裝置上建立出來的裝置,而UBI卷則是從UBI裝置上劃分出來的分割槽, 從裝置節點名(ubi0)和卷名(ubi0_3)可以看出端倪。

上面的描述是為了方便理解UBI卷和UBI裝置,實際上UBI卷和分割槽的概念之間還是有差別的。

LEB Vs. PEB

在UBI子系統中,還有LEB和PEB的概念:

LEB指Logical Erase Block,即邏輯擦除塊,簡稱邏輯塊,表示邏輯卷中的一個塊
PEB指Physical Erase Block,即物理擦除塊,簡稱物理塊,表示物理Nand中的一個塊

為什麼要劃分邏輯塊和物理塊?從PPT中我們可以發現,物理塊和邏輯塊存在動態對映關係,且由於UBI頭的存在,邏輯塊一般會比物理塊小2個頁。

UBI子系統扮演的角色及其作用

UBI子系統就是ubifs與mtd之間的中間層,其向下連線MTD裝置,實現nand特性的管理邏輯,向上呈現無壞塊的卷。

所以UBI子系統的作用,主要包括兩點:

1. 遮蔽nand特性(壞塊管理、磨損平衡、位翻轉)
2. UBI卷的實現

UBI卷的邏輯擦除塊(LEB)與物理擦除塊(PEB)之間是動態對映的,詳細可以看PPT

UBI相關的工具

ubi的工具整合在包mtd-utils中,分別有以下工具及其作用

工具 作用
ubinfo 提供ubi裝置和卷的資訊
ubiattach 連結MTD裝置到UBI並且建立相應的UBI裝置
ubidetach ubiattach相反的操作,將MTD裝置從UBI裝置上去連結
ubimkvol 從UBI裝置上建立UBI卷
ubirmvol 從UBI裝置上刪除UBI卷
ubiblock 管理UBI捲上的block
ubiupdatevol 更新卷,例如OTA直接更新某個分割槽映象
ubicrc32 使用與ubi相同的基數計算檔案的crc32
ubinize 製作UBI映象
ubiformat 格式化空的Flash裝置,擦除Flash,儲存擦除計數,寫入UBI映象到Flash
mtdinfo 報告從系統中找到的UBI裝置的資訊

UBI頭部

UBI子系統需要往每個物理塊的開頭寫入兩個關鍵資料,這兩個關鍵資料就叫做UBI的頭部。

這兩個資料分別是 此物理塊擦除次數頭此物理塊的邏輯卷標記頭,也分別稱為 EC頭(Erase Count)VID頭(Volume IDentifier)

不管是EC頭還是VID頭,都是64Bytes,分別記錄與Nand塊的第一個頁和第二個頁。

以Q&A的形式介紹UBI頭:

Q:為什麼要這兩個頭?  
A:前文有說道,nand每個block有擦除壽命限制,因此需要記錄擦除次數,以實現磨損平衡,因此需要EC頭。此外,為了實現卷,必須記錄卷的邏輯塊與物理塊之間的對映關係,因此需要VID頭。

Q:為什麼不合併成1個頭?
A:兩者寫入的時機不一致,導致兩個頭必須分開寫入。EC頭在每次擦除後,必須馬上寫入以避免丟失,而VID頭只有在對映卷後才會寫入。

Q:不管是EC頭還是VID頭都是64B,為什麼要用2個Page?  
A:使用2個Page是對Nand來說的。前文有說過,Nor的讀寫最小單元是Byte,而Nand的讀寫最小單元是Page,因此對Nor可以只使用64Bytes,對Nand則必須使用2個Page,就是說,即使只有64Bytes有效資料,也需要用無效資料填充滿1個Page一次性寫入。

Q:在記錄擦除次數時掉電等,導致丟失實際擦除次數怎麼辦?  
A:取所有物理塊的擦除次數的平均數

關於UBI頭部的詳細介紹,可參考連結

UBI卷表(UBI Volume Table)

UBI子系統有個對使用者隱藏的特殊卷,叫層卷(layout volume),用來記錄卷表。我們可以把卷表等價於分割槽表,記錄各個卷的資訊。卷表大小為2個邏輯擦除塊,每個邏輯擦除塊記錄一份卷表,換句話說,UBI子系統為了保證卷表的可靠性,用2個邏輯記錄2分卷標資訊。

由於層卷的大小是固定的(2個邏輯塊),導致能儲存的卷資訊受限,所以最大支援的卷數量是隨著邏輯塊的大小改變而改變的,但最多不超過128個。

卷表中每個卷都儲存了什麼資訊?

struct ubi_vtbl_record {                                                         
        __be32  reserved_pebs; //物理塊數量
        __be32  alignment; //卷對齊
        __be32  data_pad;                                                        
        __u8    vol_type; //靜態卷or動態卷標識
        __u8    upd_marker; //更新標識
        __be16  name_len; //卷名長度
        __u8    name[UBI_VOL_NAME_MAX+1]; //卷名
        __u8    flags; //常用語自動重分配大小標記
        __u8    padding[23]; //保留區域
        __be32  crc; //卷資訊的CRC32校驗值
} __packed;

由這個結構體我們可以發現,卷資訊是被CRC32保護著的。比較有意思的有兩個成員:vol_type 和 flags

動態卷 & 靜態卷

vol_type成員標記了卷的型別,在建立卷時指定,可選動態卷和靜態卷。那麼什麼是動態卷?什麼又是靜態卷?

動態卷和靜態卷是兩種卷的型別,靜態卷標記此卷只讀,於是UBI子系統使用CRC32來校驗保護整個卷的資料,動態卷是可讀寫的卷,資料的完整性由檔案系統來保證。

關於靜態卷和動態卷的介紹,可參考連結

更新標識

flags成員常用於標識是否自動重分配大小。怎麼樣自動充分配大小呢?在首次執行時自動resize卷,讓卷大小覆蓋所有未使用的邏輯塊。

例如Flash大小是128M,在燒錄的映象中分配的所有卷加起來只用了100M,如果有卷被表示為autoresize,那麼在首次執行時,那個卷會自動擴大,把剩餘的28M囊括在內。

這個功能挺實用的,例如某個方案規劃中,除去rootfs、核心等必要空間外,把剩餘所有空間儘可能分配給使用者資料分割槽。
在開發過程中加了個應用,導致rootfs卷需要更大的空間,進而需要壓縮user_data卷的空間。
如果user_data空間是autoresize的,那麼user_data卷的空間就會自動壓縮。

再例如舊方案用的是128M的nand,後面升級為256M,即使使用相同的韌體,也不用擔心多出來的128M浪費掉了,
因為user_data卷自動擴大囊括多出來的128M。

需要注意的是,只允許1個卷設定autoresize標誌

關於更新標識更多的介紹,參考連結

壞塊標記

我們知道Nand的物理性質,導致在使用久之後會產生壞塊,那麼UBI是如何判斷好塊是否變成了壞塊的呢?

有兩個場景可能會標識壞塊,分別是寫失敗和擦除失敗。擦除失敗且返回是EIO,則直接標記壞塊。比較有意思的是寫失敗的判斷邏輯。

UBI子系統有後臺程式對疑似的壞塊進行"嚴刑拷打"(torturing),有5個步驟:

1. 擦除嫌疑壞塊
2. 讀取擦除後的值,判斷是否都是0xFF(擦除後理應全為0xFF)
3. 寫入特定資料
4. 讀取並校驗寫入的資料
5. 以不同的資料模式重複步驟1-4

如果"嚴刑拷打"出問題,則標記壞塊,詳細的實現邏輯可參考函式torture_peb()

原文可參考連結

UBI管理開銷

什麼是管理開銷呢?為了管理Nand的空間,實現磨損平衡、壞塊管理等等功能,必須佔用一部分空間來儲存關鍵資料,就好像檔案系統的後設資料。管理佔用的空間是不會呈現給使用者空間使用的,這空間即為管理的開銷。

對Nand來說,UBI管理開銷主要包含5個部分:

1. 層卷(卷表) : 佔用兩個物理塊
2. 磨損平衡:佔用一個物理塊
3. 邏輯塊修改原子操作:佔用一個物理塊
4. 壞塊管理:預設每1024個塊則預留20個塊(核心引數可配:CONFIG_MTD_UBI_BEB_LIMIT)
5. UBI頭:(物理塊總數*2)個頁

壞塊管理預留的塊數量,也可以理解為最大能容納多少個壞塊;再考慮壞塊的存在,管理開銷計算公式為:

UBI管理總開銷 = 特性開銷 + UBI頭開銷

其中:
壞塊預留 = MAX(壞塊數量,壞塊管理預留數量)
特性開銷 = (壞塊預留 + 1個磨損平衡開銷 + 1個原子操作開銷 + 2個層捲開銷) * 物理塊大小
UBI頭開銷 = 2 * 頁大小 * (含壞塊的總塊數 - 壞塊預留 - 1個磨損平衡開銷 + 1個原子操作開銷 + 2個層捲開銷)

也就是說:
UBI管理總開銷 = (壞塊預留 + 4) * 物理塊大小 + 2 * 頁大小 * (含壞塊的總塊數 - 壞塊預留 - 4)

以128M的江波龍的FS35ND01G-S1F1 SPI Nand為例,其規格為:

總大小:128M(1Gbit)
頁大小:2K bytes
塊大小:128K
塊數量:1024

假設是完全無壞塊的片子,其管理開銷為:

UBI管理開銷 = (20 + 4) * 128K + 2 * 2K * (1024 - 20 - 4) = 7072K ≈ 7M

詳細參考原文連結

相關文章