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 三种常见的冗余策略

  1. 双备份策略(A/B分区)
    • 两个完全相同的Bootloader分区
    • 一个激活,一个备用
    • 升级时写备用分区,成功后再切换
    • 缺点:占用空间翻倍
  2. 增量备份策略
    • 只备份关键数据(环境变量、设备树)
    • Bootloader本身只存一份
    • 适合空间紧张的NOR Flash
    • 缺点:Bootloader损坏无法恢复
  3. 混合策略
    • 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 整体分区方案决策流程

说了这么多,到底怎么选?我画了一张流程图,帮你理清思路。

分区方案决策流程图 分区方案决策流程图 确定Flash类型 容量 < 64MB ? 是 NOR Flash方案 否 NAND/eMMC方案 计算分区大小(含余量) 是否需要安全升级? 是 A/B双备份方案 否 单分区+环境变量冗余 提示:无论哪种方案,环境变量区都必须做冗余备份

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升级时发现空间不够,不得不重新调整整个分区表,那叫一个痛苦。