第3章:Flash存储特性与性能瓶颈
做Bootloader开发,绕不开Flash。说句实在话,很多性能问题追根溯源,都是对Flash特性理解不够深。我见过太多人把NAND当NOR用,或者把NOR当RAM用,结果就是启动慢、容易坏、调试想哭。
这一章,咱们把Flash的底裤扒干净。从存储原理到读写擦除,再到ECC校验对性能的影响,一次性讲透。
3.1 NAND vs NOR:两种完全不同的脾气
先问个问题:为什么有的板子用NOR Flash,有的用NAND?
答案很简单——NOR适合代码执行,NAND适合数据存储。但实际项目中,很多人选型就栽在这上面。
| 特性 | NOR Flash | NAND Flash |
|---|---|---|
| 读取速度 | 快(支持XIP,代码可直接运行) | 较慢(需要先拷贝到RAM) |
| 写入速度 | 慢(按字节写,但实际按Page擦除) | 快(按Page写入,批量操作) |
| 擦除速度 | 慢(Block擦除,通常64KB~256KB) | 相对快(Block擦除,通常128KB~2MB) |
| 寿命 | 10万~100万次擦写 | 1万~10万次擦写 |
| 坏块管理 | 不需要(几乎没有坏块) | 必须(出厂就有坏块,使用中还会增加) |
| ECC校验 | 通常不需要 | 必须(硬件或软件ECC) |
| 接口 | SRAM-like,地址数据分开 | 命令/地址/数据复用 |
核心结论:Bootloader如果放在NOR里,可以直接XIP执行,启动飞快。如果放在NAND里,必须先把Bootloader拷贝到RAM,再跳转执行——这一步就多了几十毫秒甚至上百毫秒。
我个人习惯是:小容量Bootloader(< 512KB)用NOR,大容量固件存储用NAND。但如果你做的是工业级设备,NOR的可靠性会让你少很多麻烦。
3.2 读写擦除特性:三个操作,三种代价
Flash有三个基本操作:读、写、擦除。但它们的代价完全不一样。
- 读:最快。NOR可以按字节随机读,NAND按Page读(通常2KB~16KB)。
- 写:中等。NOR可以按字节写,但实际效率低;NAND按Page写,效率高。
- 擦除:最慢。两者都按Block擦除,NOR擦除一个Block可能要几百毫秒到几秒。
这里有个关键点:Flash不能覆盖写。你想修改一个字节,必须先擦除整个Block,再重新写入。这就是为什么Bootloader里做参数存储时,要设计磨损均衡和增量写入策略。
我的经验:在Bootloader里做参数存储,千万别直接擦写。我一般用“日志式”写入——每次写新数据到下一个空闲位置,满了再擦除整个Block。这样既快又延长寿命。
3.3 Page/Block层级操作:别把层级搞混了
很多新手搞不清Page和Block的关系。我画个图你就明白了。
看到没?擦除以Block为单位,读写以Page为单位。你没法只擦一个Page,也没法只写半个Page(有些控制器支持部分写,但我不建议用)。
我记得有个项目,同事为了省时间,直接写Page里的前几个字节,结果下次擦除时整个Block的数据全丢了。嗯,这就是没搞清楚层级的代价。
避坑指南:我曾经在一个量产项目中,因为没处理好Page对齐,导致Bootloader升级时写入了跨Page的数据。结果一半数据在Page A,一半在Page B,擦除时全乱套了。从那以后,我所有Flash操作都强制按Page对齐,不够的用0xFF填充。
3.4 ECC校验:性能与可靠性的博弈
NAND Flash因为工艺原因,天生会有位翻转(bit flip)。一个Page里偶尔会有几个bit自己变了,这就是为什么需要ECC(Error Correction Code)。
ECC对性能的影响有多大?我直接给你数据:
| ECC算法 | 纠错能力 | 每Page额外开销 | 性能影响 |
|---|---|---|---|
| 无ECC | 0 bit | 0字节 | 无 |
| Hamming(1bit纠错) | 1 bit | ~16字节(2KB Page) | ~5% |
| BCH(4bit纠错) | 4 bit | ~32字节 | ~15% |
| BCH(8bit纠错) | 8 bit | ~64字节 | ~30% |
| LDPC(16bit纠错) | 16 bit | ~128字节 | ~50% |
你想想看,ECC越强,性能越差。但不用ECC,NAND的数据可靠性又没法保证。
我个人建议:Bootloader分区用1bit ECC就够了。因为Bootloader代码是只读的,写入后很少修改,位翻转概率低。而且Bootloader代码量小,即使有1bit错误,重启后重新加载就行。
但如果是文件系统分区或日志分区,我建议用4bit或8bit ECC。这些分区频繁擦写,位翻转概率高,纠错能力必须跟上。
性能优化技巧:很多NAND控制器支持硬件ECC,计算速度比软件ECC快10倍以上。选型时优先选带硬件ECC的控制器。如果只能用软件ECC,记得把ECC计算放在DMA传输完成后,别阻塞CPU。
3.5 实战:Bootloader分区布局优化
说了这么多理论,来点实际的。这是我常用的Bootloader分区布局:
// 分区布局示例(NAND Flash,2GB)
// Block大小:128KB,Page大小:2KB
// 分区0:Bootloader主程序(只读)
// 占用:Block 0 ~ Block 3(512KB)
// ECC:1bit Hamming
// 特点:XIP或拷贝到RAM执行
// 分区1:Bootloader备份(只读)
// 占用:Block 4 ~ Block 7(512KB)
// ECC:1bit Hamming
// 特点:主程序损坏时从备份启动
// 分区2:参数存储(读写)
// 占用:Block 8 ~ Block 11(512KB)
// ECC:4bit BCH
// 特点:日志式写入,磨损均衡
// 分区3:固件升级区(读写)
// 占用:Block 12 ~ Block 1023(~128MB)
// ECC:8bit BCH
// 特点:接收新固件,校验后写入
这个布局我用了好几年,没出过问题。核心思路是:把Bootloader和参数分开,把只读和读写分开,ECC强度按需分配。
小技巧:Bootloader分区里,我习惯在最后留一个“坏块替换区”。如果某个Block坏了,自动映射到替换区。这样即使NAND有坏块,Bootloader也能正常工作。
3.6 本章小结
Flash存储特性是Bootloader性能优化的基础。记住三点:
- NOR适合代码执行,NAND适合数据存储——选型别搞反。
- 擦除最慢,写次之,读最快——优化时优先减少擦除次数。
- ECC是必要的,但别过度——Bootloader用1bit,数据区用4~8bit。