4. 启动流程设计:启动优先级判断、版本号校验、CRC校验、启动标志位管理

双备份升级,说白了就是给系统上了个「双保险」。但保险怎么用,什么时候用哪个,这里面的门道可不少。我见过不少团队,备份方案做得挺漂亮,结果启动逻辑写成一团浆糊——该从A区启动时去了B区,该回滚时却卡死在半路。

这一章,咱们就把启动流程拆开揉碎了讲。核心就四个字:判、验、校、管。

  • 判:启动优先级,先看哪个区
  • 验:版本号校验,新老版本谁说了算
  • 校:CRC校验,固件有没有「受伤」
  • 管:启动标志位,系统状态的「记忆卡」

这四个环节串起来,就是一条完整的启动决策链。任何一个环节出问题,系统都可能「翻车」。

4.1 启动优先级判断:谁先上?

双备份嘛,两个区都能跑。但总得有个先后顺序。我个人习惯把启动优先级分成三级:

优先级 启动源 触发条件
最高 强制恢复区(Recovery) 启动标志位为「强制恢复」
次高 备份区(Backup) 主区校验失败,或版本号异常
正常 主区(Primary) 主区一切正常,且版本号合规

你可能会问:「为什么不是先跑主区?」嗯,这里有个坑。如果主区固件已经坏了,你硬要启动它,轻则死机,重则把备份区也带崩。所以我的做法是:先看标志位,再验主区,不行就切备份。

核心原则:启动优先级不是「谁新谁上」,而是「谁稳谁上」。稳定压倒一切。

4.2 版本号校验:新老版本怎么比?

版本号校验,说白了就是判断「哪个固件该跑」。但这里有个常见的误区:很多人以为版本号高的就一定好。

我在项目中遇到过一件事:OTA升级到一半断电了,主区版本号写成了2.0,但固件只烧了一半。重启后系统一看:「哟,2.0比1.0新,跑主区!」结果直接变砖。

所以版本号校验必须搭配完整性校验一起用。我的做法是:

  1. 先读版本号:主区和备份区各有一个版本号字段,通常放在固件头的固定偏移处。
  2. 比较版本高低:版本号建议用「主版本.次版本.修订号」三段式,别用字符串比较,容易出幺蛾子。
  3. 结合启动计数:如果主区版本号高,但连续启动失败超过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 启动流程图:一图胜千言

说了这么多,不如一张图来得直观。下面这张图,就是双备份启动的核心流程。你照着这个逻辑写代码,基本不会跑偏。

双备份启动流程图 双备份启动流程图 系统上电/复位 读取启动标志位 标志位判断 Normal/Update/Rollback/Recovery Recovery 强制恢复模式 Normal 主区启动 升级/回滚处理 CRC32完整性校验 CRC校验结果 通过 / 失败 失败 切换备份区 通过 跳转应用 启动完成 说明:Recovery模式跳过CRC校验,直接进入强制恢复;Normal模式走完整校验流程

这张图里,我把整个启动流程分成了三个关键决策点:

  1. 标志位决策:先看系统「上次想干嘛」,决定走哪条路
  2. CRC校验决策:再看固件「身体好不好」,决定能不能跑
  3. 切换决策:如果主区不行,果断切备份,别犹豫

嗯,这套流程我用了好几年,在好几个量产项目上验证过。只要你的标志位管理不出错,CRC算法选对,启动优先级逻辑清晰,双备份就能真正起到「双保险」的作用。

最后说一句:启动流程写完后,一定要做「异常注入测试」。比如手动篡改Flash里的CRC值、把标志位写成非法值、模拟升级中途断电。只有把这些「坏情况」都跑一遍,你才能放心地把设备交出去。