第三讲:Flash分区设计——分区表结构、分区类型与大小规划原则
大家好,欢迎来到第三讲。
上一讲我们聊了Bootloader的启动流程,今天要讲的是一个更基础、也更关键的话题——Flash分区设计。说实话,我见过太多项目因为分区规划不合理,后期改得焦头烂额。我自己就踩过这个坑,所以今天想把这些经验好好跟你聊聊。
一、分区表结构——你的Flash地图
分区表,说白了就是一张地图。它告诉Bootloader:哪块区域放什么,从哪里开始,到哪里结束。
我习惯把分区表设计成一个结构体数组,放在Flash的固定位置。比如这样:
typedef struct {
uint32_t magic; // 魔数,用于校验
uint32_t version; // 分区表版本
uint32_t entry_count; // 分区条目数
partition_entry_t entries[MAX_PARTITIONS];
} partition_table_t;
typedef struct {
char name[16]; // 分区名称
uint32_t type; // 分区类型
uint32_t offset; // 起始偏移
uint32_t size; // 分区大小
uint32_t flags; // 属性标志
uint32_t checksum; // 校验和
} partition_entry_t;
嗯,这里要注意:魔数和版本号一定要放在最前面。为什么?因为Bootloader启动时,第一件事就是检查分区表是否有效。如果魔数不对,说明分区表损坏了,这时候就要走恢复流程。
核心要点:分区表通常放在Flash的起始位置(比如0x00000000),紧挨着就是Bootloader本身。这样设计的好处是——Bootloader启动时能最快找到分区信息。
二、分区类型——各司其职
嵌入式设备里,常见的分区类型就这四种:Boot、Recovery、System、Data。我一个个说。
1. Boot分区
这是Bootloader的老家。它存放的是第一级启动代码,也就是我们常说的SPL(Secondary Program Loader)或者Preloader。这个分区通常很小,我见过最小的只有16KB,大的也不过128KB。
为什么这么小?因为Bootloader的任务很纯粹——初始化硬件、加载下一级镜像。代码量本身就不大。
我的经验:Boot分区最好单独放在一个独立的Flash扇区里。我曾经遇到过一个项目,Boot分区和分区表放在同一个扇区,结果一次擦写操作把分区表也带走了……从那以后,我坚持让Boot分区独占一个扇区。
2. Recovery分区
这个分区是救命用的。当System分区损坏或者升级失败时,设备会启动到Recovery模式,从这里加载一个最小化的系统,用来执行恢复操作。
Recovery分区的大小取决于你要放多少东西。如果只是放一个简单的命令行工具,1MB就够了。但如果要放一个完整的Linux内核+文件系统,那至少得32MB起步。
3. System分区
这是主系统所在的分区。它存放着操作系统内核、驱动、应用程序等。这个分区是最大的,通常占整个Flash的一半以上。
我建议把System分区再细分成两个子分区:System_A和System_B。这就是我们常说的A/B分区方案。为什么这么做?你想想看,升级的时候,如果只有一个System分区,升级失败设备就变砖了。但如果有A/B两个分区,升级时先写B分区,写完后切换启动到B,如果启动失败还能回滚到A。
注意:A/B分区方案虽然安全,但会占用双倍的空间。如果你的Flash容量紧张,可以考虑用“单分区+备份”的方案,但风险会高一些。
4. Data分区
这个分区存放用户数据,比如配置文件、日志、缓存等。它通常是可读写的,而且支持擦写操作。
Data分区的大小规划比较灵活。如果设备只是用来做简单的数据采集,1MB就够了。但如果要存大量的日志或者用户文件,那至少得留出几十MB。
三、分区大小规划原则——别拍脑袋
分区大小怎么定?我见过有人直接拍脑袋:Boot给64KB,System给2MB,剩下的全给Data。结果呢?后期系统膨胀了,System分区不够用,只能重新调整分区表,所有数据都得擦掉重来。
所以,我总结了几条原则:
- 留足余量——每个分区的大小至少要比当前需求大30%。系统是会成长的,你永远不知道下个版本会多出什么功能。
- 对齐扇区——Flash的擦写单位是扇区(通常是4KB或64KB),分区起始地址和大小都要对齐到扇区边界。否则,一次擦写操作可能会影响到相邻分区。
- 考虑磨损均衡——Data分区经常被擦写,所以它的位置最好放在Flash的末尾,避免频繁擦写影响到Boot和System分区。
- 预留备份空间——至少留出一个分区大小的空间作为备份区。比如System分区是8MB,那就在它后面留出8MB的空闲空间,用于升级时的临时存储。
一个典型的分区布局:
| 分区名称 | 起始地址 | 大小 | 说明 |
|---|---|---|---|
| Boot | 0x00000000 | 64KB | Bootloader主程序 |
| Partition Table | 0x00010000 | 4KB | 分区表信息 |
| Recovery | 0x00011000 | 2MB | 恢复系统 |
| System_A | 0x00211000 | 8MB | 主系统A |
| System_B | 0x00A11000 | 8MB | 主系统B |
| Data | 0x01211000 | 剩余空间 | 用户数据 |
四、避坑指南——我踩过的那些坑
最后,分享几个我亲身经历过的教训:
- 分区表不要放在Boot分区内部——我曾经把分区表放在Boot分区的末尾,结果一次Boot分区升级,把分区表也覆盖了。设备直接变砖,只能拆机用编程器救回来。
- 分区大小不要用固定值——我建议在分区表中使用相对偏移,而不是绝对地址。这样即使调整了分区大小,也不用修改所有代码。
- 别忘了校验和——分区表中的每个条目都要有校验和。我曾经遇到过Flash位翻转导致分区类型被篡改,Bootloader加载了错误的分区,系统直接崩溃。
- 预留一个“救急分区”——在Flash末尾留出一个小分区(比如256KB),用来存放紧急恢复代码。当所有分区都损坏时,Bootloader可以从这个分区加载一个最小化的恢复程序。
好了,这一讲的内容就到这里。分区设计看似简单,但实际做起来细节很多。下一讲我们会深入聊聊分区表的动态管理,以及如何在运行时调整分区大小。到时候见。
课后思考:如果你的Flash只有4MB,你会怎么规划分区?A/B方案肯定用不了,那你会用什么方案来保证升级安全?欢迎在评论区留言讨论。