Micrium全家桶之uC-FS: 0x01 NAND FTL (qq.com)
前言
這一篇我們來講講Micrium全家桶的uC-FS。文件系統是一個比較龐大的組件,我們以從下往上的順序介紹,即先以一個具體的設備:NAND為例,講其FTL的原理和實現,然后再講FS部分。本文先講NAND驅動(FTL)部分的基本原理和代碼使用,后面會分幾篇詳細介紹其實現。
uC-FS介紹
uC-FS是一個緊湊、可靠、高性能和線程安全的嵌入式文件系統。可用于微處理器、微控制器和DSP等,并且其提供一個可選的日志組件提供掉電,故障安全操作,同時保持FAT兼容性。
uC-FS是Micrium的一款商業的嵌入式文件系統組件,是uC-XXX全家桶的一員,后來Micrium被Silicon收購,代碼全開源了。
- 支持以下介質
SD/MMC
NAND
NOR
MSC
SD/MMC MSC等支持異步插入刪除
NAND NOR等提供相應的FTL和驅動,實現壞塊管理,磨損均衡等處理。
- 資源需求少:
典型的ROM 65KB RAM 5KB左右,最少可達ROM 7KB RAM 1KB,由于只需要一個緩沖區,可以在只有1kB的可用RAM的情況下運行。根據實際使用可能更少或者更多,可以在編譯時根據需要的特性進行調整。
- 提供POSIX 兼容API
- 支持FAT12/16/32,長文件名支持,UTF-8編碼支持
- 日志組件能保證掉電可靠(FAT文件系統格式本身是掉電不安全的,uC-FS提供了日志組件以實現掉電安全)
- 性能和可靠性兼顧,在不犧牲可靠性的情況下提供高性能。與日志記錄兼容的復雜回寫緩存機制。高級鎖定方案在允許高并發性的同時確保線程安全。
這里還有一個自己值得提一下的故事:
還記得之前在一家頭部電力行業企業,公司的一款采集終端產品使用的NAND FLASH作為文件系統存儲介質,使用的是FAT文件系統+第三方的NAND FTL組件實現壞塊管理和磨損均衡和日志功能實現掉電保護,同時硬件實現了大電容儲能,實現掉電檢測以做掉電保護。產品發貨一萬多臺運行半年左右出現了大量的文件系統損壞問題導致了數據丟失。最終統計有8000多臺出現了問題,急需解決該質量問題。正是這個背景下,我臨危受命負責解決這個問題,經過大量測試分析確認第三方FTL和日志組件有缺陷,于是考慮使用商業文件系統,最終購買了uC-FS,因為當時也在使用uC-OSIII,并且市面上也沒有其他嵌入式商業文件系統可選,經過移植,大量測試工作下確認了uC-FS的可靠性,最終遠程升級解決了該問題。整個過程其實也經歷了幾個月,比較艱難的:確認問題, 分析問題就花了一半多時間, 確認解決方案,確認購買商業軟件又艱難推進,需要承受巨大壓力(因為當時之前也沒用過uc-FS不能確保沒問題), 確認遠程升級方案現場升級方案,現場技術支持協調,客戶溝通等等每一項都不容易,這也是本人經歷過的最大的一個項目了,實際比任何一個開發項目都更復雜,雖然這個項目的開發工作實際不多,編寫的代碼也不多,這個項目也使得本人成長巨大。這個過程也發現了uC-FS本身的一些BUG,都不是很嚴重,也反饋給了官方得到了確認,經過這一役也更加確認了Micrium產品的可靠性。
代碼
https://github.com/weston-embedded/uC-FS.git
文檔
https://micrium.atlassian.net/wiki/spaces/fsdoc/overview
uC-FS Documentation V40700.pdf
可以直接在線閱讀,也可以下載pdf版本
NAND 介紹
老規矩我們需要理論結合實踐,先講講NAND相關知識,NAND的FTL軟件,然后再進入正餐使用uC-FS的NAND驅動。
NAND FLASH不同于NOR FLASH因為其架構特性不一樣。
讀: SLC NAND隨機讀延遲比NOR FLASH高所以一般不適合直接運行代碼,但是SLC NAND一般有DRAM的緩存可以解決這個問題,并且可以按照PAGE 2KB或者4KB等讀寫,所以整體吞吐率實際非常高的,所以網上的一些對比資料上來就說NAND比NOR讀的慢是不對的,確切的來說應該是NAND的隨機讀性能低,但是持續讀性能高。
寫: NAND的寫吞吐率明顯高于NOR所以適合用于做數據存儲。
擦除:NAND的擦除速度同樣的明顯高于NOR。
NAND和NOR的典型參數對比如下
以下參數對比自MX29GL512F 和 MX30LF1G08AA,不同廠家不同型號有差異。
特征 | NOR | SLC NAND | |
---|---|---|---|
存儲密度 | 1Mb-2Gb | 51Mb-8Gb | 存儲領域單位一般用b(位)不用B(字節) |
隨機讀延遲 | 0.1uS | 25uS | 隨機讀NAND延遲大 |
x8 I/O連續讀 | 30MB/S | 30MB/S | 持續讀NAND速度不比NOR低 |
讀PAGE緩存 | 16B | 2048B | NAND緩存大,用來降低延遲影響,實現大數據量大吞吐速度 |
隨機寫速度 | 11uS | 250uS | 隨即寫NAND延遲大 |
寫PAGE大小 | 64B | 2048B | 同讀PAGE緩存存 |
持續寫速度 | 0.5MB/S | 8MB/S | 持續寫NAND速度遠大于NORNAND還有Dual Plane 操作進一步提高吞吐率。 |
擦除 | 0.6S | 2mS | NAND速度遠大于NOR |
單元面積 | 10F^2 | 4F^2 | NAND一個存儲單元面積小所以密度大 |
工藝 | 適合更先進制程小型化 |
從以上也可以看出NOR適合容量需求不是特別大,經常讀,很少寫,需要隨機訪問的場景,比如存代碼
NAND適合容量需求大,經常讀寫,且是持續大塊讀寫的場景,比如適合數據存儲。
以上性能行為和特征的差異根本原因就是來自于其基本的存儲單元cell陣列結構的不同。
NAND陣列的基本結構是由一組稱為string的存儲元件組成的串聯結構。每一個string由32或者64個cells緊密的排列, 每一個string都連接到頂部的讀取和IO接線。
而在NOR體系結構中,每一對cells必須連接到頂部接線(要求至少有一個金屬擴散觸點連接到每一對cells)。
仔細對比如下圖的灰色和淺藍色部分
NOR每2個cells下面必須有觸點,所以其空間需求就大了,5F2F=10F^2,而NAND 一條string 32或者64個cell再一起連接到頂部接線所以密度高,2F2F=4F^2
所以NAND的密度比NOR高60%但是綜合考慮NAND的讀寫電路會復雜一些,所以差異會再小一點。
除了在面積方面的優勢(由于NAND單元結構),NAND技術更容易縮放(適合縮小到更先進的技術節點)。
NAND和NOR的技術路線對比如下,可以看出主要的閃存行業供應商都沒有計劃在未來幾年內將NOR工藝遷移到45納米以下,而NAND則在不斷追求更先進的制程。
MLC NAND
一個cell劃分為4個等級,4個狀態可以存儲2bit的數據,采用多層單元(MLC)技術,可以獲得成本較低的存儲器性能和可靠性。
SLC和MLC主要區別在于可靠性和耐用性(壽命)以及相關的ECC要求。SLC更適合于
工業用代碼和關鍵應用。SLC版本的密度范圍要小得多。
如下可以看到擦寫次數100k降低到了5k,差了20倍。
從MLC又有了TLC,QLC分別一個cell存儲3bit,4bit。
現在又有了3D NAND技術,大家拼疊層,都到200+層數了,使得大容量NAND越來越便宜。
對比NAND和NOR的優劣勢
NAND | NOR | ||
---|---|---|---|
成本 | 低 | ||
寫速度 | 快 | ||
接口引腳 | 并口x8,x16(地址數據復用)SPI/QSPI | 并口(引腳多,數據地址分開)SPI/QSPI | 都有串并接口,并口NOR的引腳多 |
隨機讀 | 延遲大,不適合直接執行程序,但是現在的NAND都加了緩存也有適合程序執行的型號了 | ||
可靠性 | RAM NAND可靠性低需要ECC校正 | ||
壞塊 | NAND在出廠時就有可能有壞塊使用中也會產生,而NOR認為幾乎是完美的 | ||
供應 | NAND都在拼多層堆疊,反而SLC價格不低波動大,供貨不穩定 | ||
NAND的讀寫
一般通過一個PAGE的緩存來實現,有些有兩個緩存來實現ping-pang流水操作
比如下面的Cache register和Data register,緩存內部本身是可隨機讀寫訪問的,緩存寫完再通過命令進行真實的寫。
而NOR一般讀只有1632字節的緩存,寫是64256字節的緩存。
NAND FTL介紹
NAND的特性決定了其驅動軟件的復雜性,NAND的讀寫要考慮壞塊管理ECC校驗,磨損均衡。用來實現這個功能的軟件就叫做FTL(Flash Translation Layer )
現在逐漸流行的SD NAND即是集成了管理固件的NAND芯片,即FTL已經寫入了芯片內,無需應用進行管理,使用起來非常方便,可以替代TF卡等。
ECC
SLC一般使用ECC用來實現1位錯誤校正,比較常用,而BCH可以用來實現多位錯誤校正。
由于隨著制程升高,堆疊升高,MLC,TLC,QLC,3D NAND等都不適合嵌入式領域使用了,因為其要求更多的錯誤位校正能力,需要專門的管理固件。
ECC等校驗信息一般存于NAND 的PAGE的額外區域,一般2048B的PAGE 額外有64B用于存儲這些信息,這些區域一般稱為Spare Area 或OOB(Out Of Bound)。
一般有以下幾種組織形式
ECC校驗能力和額外空間需求的對應
例如對于1位錯誤校正,使用ECC則
512B有4096b
2^13 >= 4096+13+1
即13位可以表示2^13種情況,4096位數據和13位校驗信息只考慮錯1位的情況有4096+13種,還有一種正確的.所以需要2^n >= r + n +1 (其中n位校驗信息位,r位數據位)
關于ECC可以參考uC-CRC也提供了軟件ECC的計算組件,現在一般NAND芯片硬件是帶ECC的。
https://mp.weixin.qq.com/s/FKVvzwL7wzxLJCkx3gOdJQ
壞塊管理
另一個獨特的NAND特性是壞塊(BB Bad Blocks )的存在。壞塊有不能通過ECC校正來糾正的錯誤,比如多個bit錯誤超過ECC糾正能力,這些塊出廠就被標記(一般標記在塊的前面的PAGE的OOB區域)不得使用,數據手冊上有最大壞塊數量的參數。
為什么是壞塊不是壞PAGE呢,因為擦除是按照塊進行的,所以單位是塊。
壞塊由Bad Block Table (BBT) 管理,BBT如果放在RAM中則每次啟動都需要掃描一遍,也可以放在NAND的好塊中,比如最后面的塊中,但是BBT所在的塊也要做壞塊管理并且由于要經常更新BBT內容也要做磨損均衡處理。
一般一塊新的NAND要避免一開始就進行擦除操作,否則壞塊信息會丟失,而是先掃描壞塊信息進行存儲。
比如查找BBT所在塊的流程如下
磨損均衡
磨損均衡就是用空間換壽命,每個塊擦寫次數有限,大家輪流用使得壽命變長,平衡頻繁寫和不需要頻繁寫的區域,使得所有塊擦寫次數保持基本一致。
磨損均衡需要對物理塊Physical Block Address (PBA) 和邏輯塊Logical Block Address (LBA) 進行映射,軟件需要維護該映射表。
磨損均衡可以靜態或者動態均衡
動態均衡
動態均衡時更新塊時新的數據先寫入一個擦寫次數少的空閑塊中,再更新映射表。原來塊可以擦除設置為空閑,這種情況有個問題就是沒有更新數據的塊可能沒有寫操作就不會參與均衡。
靜態均衡
靜態均衡目的是保證所有的塊磨損都趨于一致,包括那些不需要經常更新數據的塊。
就上面動態均衡提到的,就算那個塊不需要寫也有可能將他的數據挪到其他塊,將這個塊用起來參與到均衡中去。實際有一些區域比如code區域確實是不想參與均衡的,可以預留出來不參與靜態均衡。
靜態均衡實際上比動態均衡實現更復雜,但是可以達到更加明顯的整體均衡,也不是所有的實現需要實現靜態均衡,也有可能兩者綜合使用。
從NAND啟動
一般SLC可以保證BLOCK0無錯誤,保證其使用壽命內不會出錯,一般BLOCK是64個PAGE,比如PAGE大小是2K則有128KB空間。BOOT程序小于該值時可以直接使用,否則boot程序需要考慮后面的壞塊。
有些NAND芯片甚至會啟動時直接將BLOCK0加載到緩存,這樣可以直接就讀出,加快啟動速度。NAND一般是不支持直接執行XIP的,一般固化在ROM的程序先加載NAND的BLOCK0到RAM中執行,有些帶NAND控制器的芯片可能支持直接NAND執行。雖然BLOCK0保證正確但是隨著擦寫次數增多還是有可能出現bit錯誤所以最好還是對BLOCK0進行ECC校正處理,這樣就可以保證可靠性。
uC-FS的NAND驅動
下載代碼
先下載代碼
git clone https://github.com/weston-embedded/uC-FS.git
文件介紹
NAND驅動代碼位于uC-FS\\uC-FS\\Dev\\NAND下
│ fs_dev_nand.c
│ fs_dev_nand.h
│
├─BSP
│ └─Template
│ bsp_fs_dev_nand_ctrlr_gen.c
│
├─Cfg
│ └─Template
│ fs_dev_nand_cfg.h
│
├─Ctrlr
│ │ fs_dev_nand_ctrlr_gen.c
│ │ fs_dev_nand_ctrlr_gen.h
│ │
│ └─GenExt
│ fs_dev_nand_ctrlr_gen_micron_ecc.c
│ fs_dev_nand_ctrlr_gen_micron_ecc.h
│ fs_dev_nand_ctrlr_gen_soft_ecc.c
│ fs_dev_nand_ctrlr_gen_soft_ecc.h
│ fs_dev_nand_ctrlr_imx28_bch.c
│ fs_dev_nand_ctrlr_imx28_bch.h
│
└─Part
fs_dev_nand_part_onfi.c
fs_dev_nand_part_onfi.h
fs_dev_nand_part_static.c
fs_dev_nand_part_static.h
BSP\\Template\\bsp_fs_dev_nand_ctrlr_gen.c | Bsp層,之前是針對并口NAND進行的設計,我將其改為了更抽象的讀寫擦除接口,更適合各種接口 |
Cfg\\Template\\fs_dev_nand_cfg.h | 配置文件 |
Ctrlr\\fs_dev_nand_ctrlr_gen.c/h | 控制層用于實現快的讀寫擦除等接口,調用bsp層接口 |
Ctrlr\\GenExt\\fs_dev_nand_ctrlr_gen_micron_ecc.c/hfs_dev_nand_ctrlr_gen_soft_ecc.c/hfs_dev_nand_ctrlr_imx28_bch.c/h | Xxx_ecc,_bch等擴展控制層實現ECC校驗等 |
Part\\fs_dev_nand_part_onfi.c/hfs_dev_nand_part_static.c/h | 芯片相關的信息,ONFI是通過讀芯片的參數PAGE自動獲取參數,static則是手動傳入參數 |
fs_dev_nand.c/h | 核心算法代碼,調用控制層的接口進行讀寫擦除 |
依賴
見移植修改后的nand_port.c nand_port.h
參考文檔
https://micrium.atlassian.net/wiki/spaces/fsdoc/pages/488006/NAND+Flash+Driver
uC-FS的NAND驅動修改移植到PC
有時我們希望只使用uC-FS的NAND驅動本身,不需要其他組件,也不希望依賴于uC-xxx。我這里就對uC-FS的NAND驅動進行了剝離,可以完全獨立使用,不依賴其他組件。且移植到了PC上進行仿真測試。如果需要移植到其他環境只需要實現bsp_fs_dev_nand_ctrlr_gen.c
的讀寫擦除接口和nand_port.c/nand_port.h即可。
見代碼庫
https://github.com/qinyunti/uC-NAND-PC.git
Linux下使用
Windows下的gcc工具鏈也一樣。
編譯
make
創建測試bin文件代表FLASH
touch nand.bin
運行測試
./nand
打印如下:
nand test
NAME:nand
FS_NAND_Init ok
NAND FLASH FOUND: Name : "nand:744:"
Sec Size : 512 bytes
Size : 247296 sectors
Update blks: 10
FS_NAND_LowMountHandler(): Can't read device header.
FS_NAND_Open err 314
FS_NAND_BlkEraseHandler(): Erase block 0.
FS_NAND_HdrWr(): Creating device header at block 0.
Marking blk 1 dirty.
Marking blk 2 dirty.
Marking blk 3 dirty.
Marking blk 4 dirty.
Marking blk 5 dirty.
Marking blk 6 dirty.
Marking blk 7 dirty.
Marking blk 8 dirty.
Marking blk 9 dirty.
... ...
Marking blk 1019 dirty.
Marking blk 1020 dirty.
Marking blk 1021 dirty.
Marking blk 1022 dirty.
Marking blk 1023 dirty.
Adding blk 1 to avail blk tbl at ix 0.
Adding blk 2 to avail blk tbl at ix 1.
Adding blk 3 to avail blk tbl at ix 2.
Adding blk 4 to avail blk tbl at ix 3.
FS_NAND_BlkGetAvailFromTbl(): Warning -- unable to get a committed available block table entry -- using an uncommitted entry.
FS_NAND_BlkEnsureErased(): No need to erase block 1 (not used).
Metadata sector 0 commit at offset 0 of block 1 (seq 0).
Removing blk 1 from avail blk tbl at ix 0.
Metadata sector 1 commit at offset 1 of block 1 (seq 0).
FS_NAND_LowFmtHandler(): Low-level fmt'ing complete.
Found meta block at physical block 1 with ID 0.
FS_NAND_MetaBlkFind(): Found metadata block at block index 1.
FS_NAND_LowMountHandler(): Low level mount succeeded.
FS_DEV_IO_CTRL_LOW_FMT ok
FS_DEV_IO_CTRL_REFRESH ok
FS_NAND_Wr: start=0, cnt=10.
Adding blk 5 to avail blk tbl at ix 0.
Metadata sector 0 commit at offset 2 of block 1 (seq 0).
FS_NAND_BlkEnsureErased(): No need to erase block 4 (not used).
Create UB 0 at phy ix 4.
Associate update blk 0 with blk ix logical 0.
Wr sector 0 in SUB 0 at sec offset 0.
Removing blk 4 from avail blk tbl at ix 3.
Wr sector 1 in SUB 0 at sec offset 1.
Wr sector 2 in SUB 0 at sec offset 2.
Wr sector 3 in SUB 0 at sec offset 3.
Wr sector 4 in SUB 0 at sec offset 4.
Wr sector 5 in SUB 0 at sec offset 5.
Wr sector 6 in SUB 0 at sec offset 6.
Wr sector 7 in SUB 0 at sec offset 7.
Wr sector 8 in SUB 0 at sec offset 8.
Wr sector 9 in SUB 0 at sec offset 9.
Metadata sector 0 commit at offset 3 of block 1 (seq 0).
Metadata sector 1 commit at offset 4 of block 1 (seq 0).
FS_NAND_Wr ok
FS_NAND_Rd ok
test ok