4. 分区方案设计原则:Flash存储器类型、分区大小规划、冗余与备份策略
分区方案设计,说白了就是给Bootloader和系统在Flash上画格子。格子画得好不好,直接决定了产品能不能稳定启动、能不能安全升级。我这些年经手的项目,从几KB的NOR Flash到几十GB的eMMC,踩过的坑不少,今天就把这些经验掰开揉碎了讲给你听。
4.1 先搞清楚你的Flash是什么类型
不同Flash的物理特性天差地别,分区策略也得跟着变。我见过有人把NOR的方案硬搬到NAND上,结果启动时各种坏块问题,折腾了两周才找到原因。
| 特性 | NOR Flash | NAND Flash | eMMC |
|---|---|---|---|
| 读取速度 | 快(支持XIP) | 较快(需拷贝到RAM) | 快(内置控制器) |
| 写入速度 | 慢(按字节写) | 快(按页写) | 快(FTR优化) |
| 擦除方式 | 按扇区擦除 | 按块擦除 | 按擦除组 |
| 坏块管理 | 无坏块问题 | 必须处理坏块 | 内置管理 |
| 典型容量 | 1MB ~ 64MB | 128MB ~ 8GB | 4GB ~ 256GB |
| Bootloader分区 | 直接存放 | 需BBT + ECC | GPT分区表 |
核心原则:NOR适合存放Bootloader代码本身(支持XIP),NAND适合存放文件系统,eMMC则用GPT分区表统一管理。别把Bootloader直接塞NAND里,除非你做好了坏块跳过和ECC校验。
4.2 分区大小规划——别拍脑袋,算清楚
分区大小怎么定?我见过最离谱的方案是给Bootloader分了512KB,结果代码编译出来1.2MB,启动直接挂掉。嗯,这里要注意,分区大小不是拍脑袋定的,得按公式算。
4.2.1 Bootloader分区大小公式
Bootloader分区大小 =
Bootloader二进制大小 × (1 + 预留余量)
+ 环境变量区大小
+ 冗余备份区大小
我个人习惯预留余量取50%。为什么?因为产品迭代过程中,Bootloader功能会不断增加。我做过一个项目,第一版Bootloader只有300KB,三年后迭代到1.2MB,幸好当初预留了足够空间。
| 组件 | 建议大小 | 说明 |
|---|---|---|
| SPL/TPL(如果使用) | 64KB ~ 256KB | 受SRAM大小限制,通常很小 |
| U-Boot主镜像 | 512KB ~ 2MB | 取决于驱动数量和功能 |
| 环境变量区 | 64KB ~ 256KB | 至少2个备份 |
| 冗余备份 | 等于主镜像大小 | 用于安全升级 |
| 总计 | 1.5MB ~ 5MB | 根据实际调整 |
避坑指南:我曾经在一个项目里只给环境变量区留了32KB,结果客户要求增加20个启动参数,直接爆了。从那以后,环境变量区我至少给128KB,而且必须做冗余。
4.3 冗余与备份策略——别把鸡蛋放一个篮子里
为什么需要冗余?你想想看,升级过程中突然断电怎么办?Flash某个扇区物理损坏怎么办?没有冗余,一次升级失败,设备就变砖了。
4.3.1 三种常见的冗余策略
- 双备份策略(A/B分区)
- 两个完全相同的Bootloader分区
- 一个激活,一个备用
- 升级时写备用分区,成功后再切换
- 缺点:占用空间翻倍
- 增量备份策略
- 只备份关键数据(环境变量、设备树)
- Bootloader本身只存一份
- 适合空间紧张的NOR Flash
- 缺点:Bootloader损坏无法恢复
- 混合策略
- Bootloader用双备份
- 环境变量用三备份(多数表决)
- 我目前在大多数项目中都用这个方案
警告:双备份不是简单的复制粘贴。两个分区的起始地址必须不同,且不能跨坏块。我曾经见过有人把两个备份放在相邻扇区,结果一个坏块把两个都干掉了。
4.3.2 环境变量的冗余设计
环境变量是Bootloader的命根子。启动参数、网络配置、内核命令行都存这里。一旦损坏,系统可能连内核都起不来。
/* U-Boot环境变量冗余布局示例 */
分区布局:
[环境变量主区] [环境变量备份区] [环境变量校验区]
64KB 64KB 4KB
校验机制:
1. 启动时读取主区,计算CRC32
2. 如果CRC校验通过,使用主区
3. 如果校验失败,读取备份区
4. 如果备份区也失败,使用默认环境变量
5. 如果默认环境变量也无法启动,进入恢复模式
我的经验:环境变量的校验一定要用CRC32或更强的算法。我见过有人只用简单的checksum,结果两个bit翻转了,checksum没变,但数据已经错了。CRC32至少能检测出单bit和双bit错误。
4.4 分区对齐与擦除块边界
这是个容易被忽略的细节。Flash的擦除操作是按块(Block)进行的,如果你的分区没有对齐到擦除块边界,就会出现跨块擦除的问题。
| Flash类型 | 擦除块大小 | 对齐要求 |
|---|---|---|
| NOR Flash | 64KB 或 128KB | 分区起始地址必须是擦除块大小的整数倍 |
| NAND Flash | 128KB 或 256KB | 同上,且要考虑坏块跳过 |
| eMMC | 512KB ~ 4MB | 建议对齐到1MB边界 |
小技巧:在Yocto的机器配置文件中,可以用BOOTLOADER_PARTITION_ALIGN变量来指定对齐大小。我习惯设为1MB,这样无论什么Flash都能对齐。
4.5 整体分区方案决策流程
说了这么多,到底怎么选?我画了一张流程图,帮你理清思路。
4.6 实战中的分区布局示例
最后,给你一个我在实际项目中用过的分区布局。这个方案用在i.MX6ULL平台上,NOR Flash 16MB,支持安全升级。
/* 16MB NOR Flash 分区布局 */
地址范围 大小 用途
0x00000000 - 0x0003FFFF 256KB SPL (首次启动加载器)
0x00040000 - 0x000BFFFF 512KB U-Boot主镜像 (A分区)
0x000C0000 - 0x0013FFFF 512KB U-Boot主镜像 (B分区)
0x00140000 - 0x0017FFFF 256KB 环境变量主区
0x00180000 - 0x001BFFFF 256KB 环境变量备份区
0x001C0000 - 0x001FFFFF 256KB 设备树 (DTB)
0x00200000 - 0x00FFFFFF 14MB 内核+文件系统
注意:这个布局里,SPL和U-Boot之间留了256KB的间隙。为什么?因为SPL受内部SRAM大小限制,通常只有64KB~128KB。留点空间,方便以后SPL功能扩展。我曾经因为没留间隙,SPL升级时发现空间不够,不得不重新调整整个分区表,那叫一个痛苦。