本文轉(zhuǎn)自公眾號(hào),歡迎關(guān)注
Micrium全家桶之uC-FS: 0x01 NAND FTL (qq.com)
前言
這一篇我們來講講Micrium全家桶的uC-FS。文件系統(tǒng)是一個(gè)比較龐大的組件,我們以從下往上的順序介紹,即先以一個(gè)具體的設(shè)備:NAND為例,講其FTL的原理和實(shí)現(xiàn),然后再講FS部分。本文先講NAND驅(qū)動(dòng)(FTL)部分的基本原理和代碼使用,后面會(huì)分幾篇詳細(xì)介紹其實(shí)現(xiàn)。
uC-FS介紹
uC-FS是一個(gè)緊湊、可靠、高性能和線程安全的嵌入式文件系統(tǒng)??捎糜?a target="_blank">微處理器、微控制器和DSP等,并且其提供一個(gè)可選的日志組件提供掉電,故障安全操作,同時(shí)保持FAT兼容性。
uC-FS是Micrium的一款商業(yè)的嵌入式文件系統(tǒng)組件,是uC-XXX全家桶的一員,后來Micrium被Silicon收購,代碼全開源了。
- 支持以下介質(zhì)
SD/MMC
NAND
NOR
MSC
SD/MMC MSC等支持異步插入刪除
NAND NOR等提供相應(yīng)的FTL和驅(qū)動(dòng),實(shí)現(xiàn)壞塊管理,磨損均衡等處理。
- 資源需求少:
典型的ROM 65KB RAM 5KB左右,最少可達(dá)ROM 7KB RAM 1KB,由于只需要一個(gè)緩沖區(qū),可以在只有1kB的可用RAM的情況下運(yùn)行。根據(jù)實(shí)際使用可能更少或者更多,可以在編譯時(shí)根據(jù)需要的特性進(jìn)行調(diào)整。
- 提供POSIX 兼容API
- 支持FAT12/16/32,長文件名支持,UTF-8編碼支持
- 日志組件能保證掉電可靠(FAT文件系統(tǒng)格式本身是掉電不安全的,uC-FS提供了日志組件以實(shí)現(xiàn)掉電安全)
- 性能和可靠性兼顧,在不犧牲可靠性的情況下提供高性能。與日志記錄兼容的復(fù)雜回寫緩存機(jī)制。高級(jí)鎖定方案在允許高并發(fā)性的同時(shí)確保線程安全。
這里還有一個(gè)自己值得提一下的故事:
還記得之前在一家頭部電力行業(yè)企業(yè),公司的一款采集終端產(chǎn)品使用的NAND FLASH作為文件系統(tǒng)存儲(chǔ)介質(zhì),使用的是FAT文件系統(tǒng)+第三方的NAND FTL組件實(shí)現(xiàn)壞塊管理和磨損均衡和日志功能實(shí)現(xiàn)掉電保護(hù),同時(shí)硬件實(shí)現(xiàn)了大電容儲(chǔ)能,實(shí)現(xiàn)掉電檢測以做掉電保護(hù)。產(chǎn)品發(fā)貨一萬多臺(tái)運(yùn)行半年左右出現(xiàn)了大量的文件系統(tǒng)損壞問題導(dǎo)致了數(shù)據(jù)丟失。最終統(tǒng)計(jì)有8000多臺(tái)出現(xiàn)了問題,急需解決該質(zhì)量問題。正是這個(gè)背景下,我臨危受命負(fù)責(zé)解決這個(gè)問題,經(jīng)過大量測試分析確認(rèn)第三方FTL和日志組件有缺陷,于是考慮使用商業(yè)文件系統(tǒng),最終購買了uC-FS,因?yàn)楫?dāng)時(shí)也在使用uC-OSIII,并且市面上也沒有其他嵌入式商業(yè)文件系統(tǒng)可選,經(jīng)過移植,大量測試工作下確認(rèn)了uC-FS的可靠性,最終遠(yuǎn)程升級(jí)解決了該問題。整個(gè)過程其實(shí)也經(jīng)歷了幾個(gè)月,比較艱難的:確認(rèn)問題, 分析問題就花了一半多時(shí)間, 確認(rèn)解決方案,確認(rèn)購買商業(yè)軟件又艱難推進(jìn),需要承受巨大壓力(因?yàn)楫?dāng)時(shí)之前也沒用過uc-FS不能確保沒問題), 確認(rèn)遠(yuǎn)程升級(jí)方案現(xiàn)場升級(jí)方案,現(xiàn)場技術(shù)支持協(xié)調(diào),客戶溝通等等每一項(xiàng)都不容易,這也是本人經(jīng)歷過的最大的一個(gè)項(xiàng)目了,實(shí)際比任何一個(gè)開發(fā)項(xiàng)目都更復(fù)雜,雖然這個(gè)項(xiàng)目的開發(fā)工作實(shí)際不多,編寫的代碼也不多,這個(gè)項(xiàng)目也使得本人成長巨大。這個(gè)過程也發(fā)現(xiàn)了uC-FS本身的一些BUG,都不是很嚴(yán)重,也反饋給了官方得到了確認(rèn),經(jīng)過這一役也更加確認(rèn)了Micrium產(chǎn)品的可靠性。
代碼
https://github.com/weston-embedded/uC-FS.git
文檔
https://micrium.atlassian.net/wiki/spaces/fsdoc/overview
uC-FS Documentation V40700.pdf
可以直接在線閱讀,也可以下載pdf版本
NAND 介紹
老規(guī)矩我們需要理論結(jié)合實(shí)踐,先講講NAND相關(guān)知識(shí),NAND的FTL軟件,然后再進(jìn)入正餐使用uC-FS的NAND驅(qū)動(dòng)。
NAND FLASH不同于NOR FLASH因?yàn)槠浼軜?gòu)特性不一樣。
讀: SLC NAND隨機(jī)讀延遲比NOR FLASH高所以一般不適合直接運(yùn)行代碼,但是SLC NAND一般有DRAM的緩存可以解決這個(gè)問題,并且可以按照PAGE 2KB或者4KB等讀寫,所以整體吞吐率實(shí)際非常高的,所以網(wǎng)上的一些對(duì)比資料上來就說NAND比NOR讀的慢是不對(duì)的,確切的來說應(yīng)該是NAND的隨機(jī)讀性能低,但是持續(xù)讀性能高。
寫: NAND的寫吞吐率明顯高于NOR所以適合用于做數(shù)據(jù)存儲(chǔ)。
擦除:NAND的擦除速度同樣的明顯高于NOR。
NAND和NOR的典型參數(shù)對(duì)比如下
以下參數(shù)對(duì)比自MX29GL512F 和 MX30LF1G08AA,不同廠家不同型號(hào)有差異。
特征 | NOR | SLC NAND | |
---|---|---|---|
存儲(chǔ)密度 | 1Mb-2Gb | 51Mb-8Gb | 存儲(chǔ)領(lǐng)域單位一般用b(位)不用B(字節(jié)) |
隨機(jī)讀延遲 | 0.1uS | 25uS | 隨機(jī)讀NAND延遲大 |
x8 I/O連續(xù)讀 | 30MB/S | 30MB/S | 持續(xù)讀NAND速度不比NOR低 |
讀PAGE緩存 | 16B | 2048B | NAND緩存大,用來降低延遲影響,實(shí)現(xiàn)大數(shù)據(jù)量大吞吐速度 |
隨機(jī)寫速度 | 11uS | 250uS | 隨即寫NAND延遲大 |
寫PAGE大小 | 64B | 2048B | 同讀PAGE緩存存 |
持續(xù)寫速度 | 0.5MB/S | 8MB/S | 持續(xù)寫NAND速度遠(yuǎn)大于NORNAND還有Dual Plane 操作進(jìn)一步提高吞吐率。 |
擦除 | 0.6S | 2mS | NAND速度遠(yuǎn)大于NOR |
單元面積 | 10F^2 | 4F^2 | NAND一個(gè)存儲(chǔ)單元面積小所以密度大 |
工藝 | 適合更先進(jìn)制程小型化 |
從以上也可以看出NOR適合容量需求不是特別大,經(jīng)常讀,很少寫,需要隨機(jī)訪問的場景,比如存代碼
NAND適合容量需求大,經(jīng)常讀寫,且是持續(xù)大塊讀寫的場景,比如適合數(shù)據(jù)存儲(chǔ)。
以上性能行為和特征的差異根本原因就是來自于其基本的存儲(chǔ)單元cell陣列結(jié)構(gòu)的不同。
NAND陣列的基本結(jié)構(gòu)是由一組稱為string的存儲(chǔ)元件組成的串聯(lián)結(jié)構(gòu)。每一個(gè)string由32或者64個(gè)cells緊密的排列, 每一個(gè)string都連接到頂部的讀取和IO接線。
而在NOR體系結(jié)構(gòu)中,每一對(duì)cells必須連接到頂部接線(要求至少有一個(gè)金屬擴(kuò)散觸點(diǎn)連接到每一對(duì)cells)。
仔細(xì)對(duì)比如下圖的灰色和淺藍(lán)色部分
NOR每2個(gè)cells下面必須有觸點(diǎn),所以其空間需求就大了,5F2F=10F^2,而NAND 一條string 32或者64個(gè)cell再一起連接到頂部接線所以密度高,2F2F=4F^2
所以NAND的密度比NOR高60%但是綜合考慮NAND的讀寫電路會(huì)復(fù)雜一些,所以差異會(huì)再小一點(diǎn)。
除了在面積方面的優(yōu)勢(由于NAND單元結(jié)構(gòu)),NAND技術(shù)更容易縮放(適合縮小到更先進(jìn)的技術(shù)節(jié)點(diǎn))。
NAND和NOR的技術(shù)路線對(duì)比如下,可以看出主要的閃存行業(yè)供應(yīng)商都沒有計(jì)劃在未來幾年內(nèi)將NOR工藝遷移到45納米以下,而NAND則在不斷追求更先進(jìn)的制程。
MLC NAND
一個(gè)cell劃分為4個(gè)等級(jí),4個(gè)狀態(tài)可以存儲(chǔ)2bit的數(shù)據(jù),采用多層單元(MLC)技術(shù),可以獲得成本較低的存儲(chǔ)器性能和可靠性。
SLC和MLC主要區(qū)別在于可靠性和耐用性(壽命)以及相關(guān)的ECC要求。SLC更適合于
工業(yè)用代碼和關(guān)鍵應(yīng)用。SLC版本的密度范圍要小得多。
如下可以看到擦寫次數(shù)100k降低到了5k,差了20倍。
從MLC又有了TLC,QLC分別一個(gè)cell存儲(chǔ)3bit,4bit。
現(xiàn)在又有了3D NAND技術(shù),大家拼疊層,都到200+層數(shù)了,使得大容量NAND越來越便宜。
對(duì)比NAND和NOR的優(yōu)劣勢
NAND | NOR | ||
---|---|---|---|
成本 | 低 | ||
寫速度 | 快 | ||
接口引腳 | 并口x8,x16(地址數(shù)據(jù)復(fù)用)SPI/QSPI | 并口(引腳多,數(shù)據(jù)地址分開)SPI/QSPI | 都有串并接口,并口NOR的引腳多 |
隨機(jī)讀 | 延遲大,不適合直接執(zhí)行程序,但是現(xiàn)在的NAND都加了緩存也有適合程序執(zhí)行的型號(hào)了 | ||
可靠性 | RAM NAND可靠性低需要ECC校正 | ||
壞塊 | NAND在出廠時(shí)就有可能有壞塊使用中也會(huì)產(chǎn)生,而NOR認(rèn)為幾乎是完美的 | ||
供應(yīng) | NAND都在拼多層堆疊,反而SLC價(jià)格不低波動(dòng)大,供貨不穩(wěn)定 | ||
NAND的讀寫
一般通過一個(gè)PAGE的緩存來實(shí)現(xiàn),有些有兩個(gè)緩存來實(shí)現(xiàn)ping-pang流水操作
比如下面的Cache register和Data register,緩存內(nèi)部本身是可隨機(jī)讀寫訪問的,緩存寫完再通過命令進(jìn)行真實(shí)的寫。
而NOR一般讀只有1632字節(jié)的緩存,寫是64256字節(jié)的緩存。
NAND FTL介紹
NAND的特性決定了其驅(qū)動(dòng)軟件的復(fù)雜性,NAND的讀寫要考慮壞塊管理ECC校驗(yàn),磨損均衡。用來實(shí)現(xiàn)這個(gè)功能的軟件就叫做FTL(Flash Translation Layer )
現(xiàn)在逐漸流行的SD NAND即是集成了管理固件的NAND芯片,即FTL已經(jīng)寫入了芯片內(nèi),無需應(yīng)用進(jìn)行管理,使用起來非常方便,可以替代TF卡等。
ECC
SLC一般使用ECC用來實(shí)現(xiàn)1位錯(cuò)誤校正,比較常用,而BCH可以用來實(shí)現(xiàn)多位錯(cuò)誤校正。
由于隨著制程升高,堆疊升高,MLC,TLC,QLC,3D NAND等都不適合嵌入式領(lǐng)域使用了,因?yàn)槠湟蟾嗟腻e(cuò)誤位校正能力,需要專門的管理固件。
ECC等校驗(yàn)信息一般存于NAND 的PAGE的額外區(qū)域,一般2048B的PAGE 額外有64B用于存儲(chǔ)這些信息,這些區(qū)域一般稱為Spare Area 或OOB(Out Of Bound)。
一般有以下幾種組織形式
ECC校驗(yàn)?zāi)芰皖~外空間需求的對(duì)應(yīng)
例如對(duì)于1位錯(cuò)誤校正,使用ECC則
512B有4096b
2^13 >= 4096+13+1
即13位可以表示2^13種情況,4096位數(shù)據(jù)和13位校驗(yàn)信息只考慮錯(cuò)1位的情況有4096+13種,還有一種正確的.所以需要2^n >= r + n +1 (其中n位校驗(yàn)信息位,r位數(shù)據(jù)位)
關(guān)于ECC可以參考uC-CRC也提供了軟件ECC的計(jì)算組件,現(xiàn)在一般NAND芯片硬件是帶ECC的。
https://mp.weixin.qq.com/s/FKVvzwL7wzxLJCkx3gOdJQ
壞塊管理
另一個(gè)獨(dú)特的NAND特性是壞塊(BB Bad Blocks )的存在。壞塊有不能通過ECC校正來糾正的錯(cuò)誤,比如多個(gè)bit錯(cuò)誤超過ECC糾正能力,這些塊出廠就被標(biāo)記(一般標(biāo)記在塊的前面的PAGE的OOB區(qū)域)不得使用,數(shù)據(jù)手冊上有最大壞塊數(shù)量的參數(shù)。
為什么是壞塊不是壞PAGE呢,因?yàn)椴脸前凑諌K進(jìn)行的,所以單位是塊。
壞塊由Bad Block Table (BBT) 管理,BBT如果放在RAM中則每次啟動(dòng)都需要掃描一遍,也可以放在NAND的好塊中,比如最后面的塊中,但是BBT所在的塊也要做壞塊管理并且由于要經(jīng)常更新BBT內(nèi)容也要做磨損均衡處理。
一般一塊新的NAND要避免一開始就進(jìn)行擦除操作,否則壞塊信息會(huì)丟失,而是先掃描壞塊信息進(jìn)行存儲(chǔ)。
比如查找BBT所在塊的流程如下
磨損均衡
磨損均衡就是用空間換壽命,每個(gè)塊擦寫次數(shù)有限,大家輪流用使得壽命變長,平衡頻繁寫和不需要頻繁寫的區(qū)域,使得所有塊擦寫次數(shù)保持基本一致。
磨損均衡需要對(duì)物理塊Physical Block Address (PBA) 和邏輯塊Logical Block Address (LBA) 進(jìn)行映射,軟件需要維護(hù)該映射表。
磨損均衡可以靜態(tài)或者動(dòng)態(tài)均衡
動(dòng)態(tài)均衡
動(dòng)態(tài)均衡時(shí)更新塊時(shí)新的數(shù)據(jù)先寫入一個(gè)擦寫次數(shù)少的空閑塊中,再更新映射表。原來塊可以擦除設(shè)置為空閑,這種情況有個(gè)問題就是沒有更新數(shù)據(jù)的塊可能沒有寫操作就不會(huì)參與均衡。
靜態(tài)均衡
靜態(tài)均衡目的是保證所有的塊磨損都趨于一致,包括那些不需要經(jīng)常更新數(shù)據(jù)的塊。
就上面動(dòng)態(tài)均衡提到的,就算那個(gè)塊不需要寫也有可能將他的數(shù)據(jù)挪到其他塊,將這個(gè)塊用起來參與到均衡中去。實(shí)際有一些區(qū)域比如code區(qū)域確實(shí)是不想?yún)⑴c均衡的,可以預(yù)留出來不參與靜態(tài)均衡。
靜態(tài)均衡實(shí)際上比動(dòng)態(tài)均衡實(shí)現(xiàn)更復(fù)雜,但是可以達(dá)到更加明顯的整體均衡,也不是所有的實(shí)現(xiàn)需要實(shí)現(xiàn)靜態(tài)均衡,也有可能兩者綜合使用。
從NAND啟動(dòng)
一般SLC可以保證BLOCK0無錯(cuò)誤,保證其使用壽命內(nèi)不會(huì)出錯(cuò),一般BLOCK是64個(gè)PAGE,比如PAGE大小是2K則有128KB空間。BOOT程序小于該值時(shí)可以直接使用,否則boot程序需要考慮后面的壞塊。
有些NAND芯片甚至?xí)?dòng)時(shí)直接將BLOCK0加載到緩存,這樣可以直接就讀出,加快啟動(dòng)速度。NAND一般是不支持直接執(zhí)行XIP的,一般固化在ROM的程序先加載NAND的BLOCK0到RAM中執(zhí)行,有些帶NAND控制器的芯片可能支持直接NAND執(zhí)行。雖然BLOCK0保證正確但是隨著擦寫次數(shù)增多還是有可能出現(xiàn)bit錯(cuò)誤所以最好還是對(duì)BLOCK0進(jìn)行ECC校正處理,這樣就可以保證可靠性。
uC-FS的NAND驅(qū)動(dòng)
下載代碼
先下載代碼
git clone https://github.com/weston-embedded/uC-FS.git
文件介紹
NAND驅(qū)動(dòng)代碼位于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層,之前是針對(duì)并口NAND進(jìn)行的設(shè)計(jì),我將其改為了更抽象的讀寫擦除接口,更適合各種接口 |
Cfg\\Template\\fs_dev_nand_cfg.h | 配置文件 |
Ctrlr\\fs_dev_nand_ctrlr_gen.c/h | 控制層用于實(shí)現(xiàn)快的讀寫擦除等接口,調(diào)用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等擴(kuò)展控制層實(shí)現(xiàn)ECC校驗(yàn)等 |
Part\\fs_dev_nand_part_onfi.c/hfs_dev_nand_part_static.c/h | 芯片相關(guān)的信息,ONFI是通過讀芯片的參數(shù)PAGE自動(dòng)獲取參數(shù),static則是手動(dòng)傳入?yún)?shù) |
fs_dev_nand.c/h | 核心算法代碼,調(diào)用控制層的接口進(jìn)行讀寫擦除 |
依賴
見移植修改后的nand_port.c nand_port.h
參考文檔
https://micrium.atlassian.net/wiki/spaces/fsdoc/pages/488006/NAND+Flash+Driver
uC-FS的NAND驅(qū)動(dòng)修改移植到PC
有時(shí)我們希望只使用uC-FS的NAND驅(qū)動(dòng)本身,不需要其他組件,也不希望依賴于uC-xxx。我這里就對(duì)uC-FS的NAND驅(qū)動(dòng)進(jìn)行了剝離,可以完全獨(dú)立使用,不依賴其他組件。且移植到了PC上進(jìn)行仿真測試。如果需要移植到其他環(huán)境只需要實(shí)現(xiàn)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
創(chuàng)建測試bin文件代表FLASH
touch nand.bin
運(yùn)行測試
./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