本文思維導圖總綱:
綜述
關於ubi子系統,早已有比較正式的介紹,也提供非常形象的介紹ubi子系統ppt
國內的前輩 alloysystem 不辭辛勞為我們提供了部分正式介紹的中文譯文,以及找不到原文的轉載譯文
感謝這些資料讓我迅速入門ubi,進而整理出這博文
此博文是對上文的總結以及中文譯文的補充
概念對比
UBI Vs. MTD
上圖非常形象地描述了從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
詳細參考原文連結