第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的关系。我画个图你就明白了。

NAND Flash层级结构 Block 0(128KB ~ 2MB) Page 0(2KB ~ 16KB) Page 1 Page 2 Page ... Block 1 Block 2(坏块示例) Block N-1 Block(擦除单位) Page(读写单位) 坏块

看到没?擦除以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。