4. 启动流程设计:启动优先级判断、版本号校验、CRC校验、启动标志位管理
双备份升级,说白了就是给系统上了个「双保险」。但保险怎么用,什么时候用哪个,这里面的门道可不少。我见过不少团队,备份方案做得挺漂亮,结果启动逻辑写成一团浆糊——该从A区启动时去了B区,该回滚时却卡死在半路。
这一章,咱们就把启动流程拆开揉碎了讲。核心就四个字:判、验、校、管。
- 判:启动优先级,先看哪个区
- 验:版本号校验,新老版本谁说了算
- 校:CRC校验,固件有没有「受伤」
- 管:启动标志位,系统状态的「记忆卡」
这四个环节串起来,就是一条完整的启动决策链。任何一个环节出问题,系统都可能「翻车」。
4.1 启动优先级判断:谁先上?
双备份嘛,两个区都能跑。但总得有个先后顺序。我个人习惯把启动优先级分成三级:
| 优先级 | 启动源 | 触发条件 |
|---|---|---|
| 最高 | 强制恢复区(Recovery) | 启动标志位为「强制恢复」 |
| 次高 | 备份区(Backup) | 主区校验失败,或版本号异常 |
| 正常 | 主区(Primary) | 主区一切正常,且版本号合规 |
你可能会问:「为什么不是先跑主区?」嗯,这里有个坑。如果主区固件已经坏了,你硬要启动它,轻则死机,重则把备份区也带崩。所以我的做法是:先看标志位,再验主区,不行就切备份。
核心原则:启动优先级不是「谁新谁上」,而是「谁稳谁上」。稳定压倒一切。
4.2 版本号校验:新老版本怎么比?
版本号校验,说白了就是判断「哪个固件该跑」。但这里有个常见的误区:很多人以为版本号高的就一定好。
我在项目中遇到过一件事:OTA升级到一半断电了,主区版本号写成了2.0,但固件只烧了一半。重启后系统一看:「哟,2.0比1.0新,跑主区!」结果直接变砖。
所以版本号校验必须搭配完整性校验一起用。我的做法是:
- 先读版本号:主区和备份区各有一个版本号字段,通常放在固件头的固定偏移处。
- 比较版本高低:版本号建议用「主版本.次版本.修订号」三段式,别用字符串比较,容易出幺蛾子。
- 结合启动计数:如果主区版本号高,但连续启动失败超过N次,自动降级到备份区。
小技巧:版本号字段里可以塞一个「兼容性掩码」。比如bit0表示「是否支持双备份」,bit1表示「是否强制回滚」。这样升级时不用改启动逻辑,改个掩码就行。
4.3 CRC校验:固件有没有「受伤」?
CRC校验,是启动流程里最不能省的一步。你想想看,Flash存储是有寿命的,读写过程中也可能出现位翻转。如果固件本身已经坏了,你还让它跑,那不是找死吗?
我一般会在固件生成时,对整个固件区(包括固件头和应用代码)算一个CRC32值,烧录时一起写进Flash。启动时再算一遍,比对是否一致。
// 伪代码:CRC校验流程
uint32_t calc_crc = crc32(APP_BASE_ADDR, APP_SIZE);
uint32_t stored_crc = *(uint32_t*)(APP_BASE_ADDR + CRC_OFFSET);
if (calc_crc != stored_crc) {
// 固件损坏,标记为无效
set_boot_flag(BOOT_FLAG_APP_CORRUPT);
switch_to_backup();
} else {
// 固件完好,继续启动
jump_to_app();
}
注意:CRC校验要覆盖整个固件区,包括中断向量表。我见过有人只校验应用代码,结果中断向量表坏了,程序一跑就飞。
另外,CRC算法别选太弱的。CRC8太容易碰撞,CRC16勉强能用,我建议直接用CRC32。虽然多花点时间,但安全第一。
4.4 启动标志位管理:系统的「记忆卡」
启动标志位,就是系统用来记住「上次发生了什么」的小本本。它通常放在Flash的最后一个扇区,或者一个专用的配置区。
我常用的标志位有这几个:
| 标志位 | 值 | 含义 |
|---|---|---|
| BOOT_FLAG_NORMAL | 0xA5 | 正常启动,无异常 |
| BOOT_FLAG_UPDATE | 0x5A | 正在升级,启动后进入升级模式 |
| BOOT_FLAG_ROLLBACK | 0x3C | 上次升级失败,需要回滚 |
| BOOT_FLAG_RECOVERY | 0xC3 | 强制恢复模式,跳过所有校验 |
标志位的管理,有几个要点:
- 写前先擦:Flash写入前必须擦除,别直接写,会出数据错乱。
- 冗余存储:我习惯把标志位存三份,启动时「三取二」投票。防止单bit翻转导致误判。
- 启动时清零:正常启动后,把标志位清为BOOT_FLAG_NORMAL。不然下次重启还会进升级模式。
避坑指南:我曾经在一个项目里,把标志位和固件放在同一个扇区。结果升级时擦除固件区,把标志位也擦没了。系统重启后不知道要干嘛,直接死循环。后来我把标志位单独放在最后一个扇区,再也没出过这个问题。
4.5 启动流程图:一图胜千言
说了这么多,不如一张图来得直观。下面这张图,就是双备份启动的核心流程。你照着这个逻辑写代码,基本不会跑偏。
这张图里,我把整个启动流程分成了三个关键决策点:
- 标志位决策:先看系统「上次想干嘛」,决定走哪条路
- CRC校验决策:再看固件「身体好不好」,决定能不能跑
- 切换决策:如果主区不行,果断切备份,别犹豫
嗯,这套流程我用了好几年,在好几个量产项目上验证过。只要你的标志位管理不出错,CRC算法选对,启动优先级逻辑清晰,双备份就能真正起到「双保险」的作用。
最后说一句:启动流程写完后,一定要做「异常注入测试」。比如手动篡改Flash里的CRC值、把标志位写成非法值、模拟升级中途断电。只有把这些「坏情况」都跑一遍,你才能放心地把设备交出去。