Linux ubi子系統原理分析,包括概念的澄清,壞塊標誌原理,管理開銷等 ...
本文思維導圖總綱:
綜述
關於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
詳細參考原文鏈接