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