双分区(A/B 分区)升级中,分区的跳转执行主要通过Bootloader 引导程序和分区状态标志实现,核心逻辑是 “启动时根据标志位选择加载的分区,运行中通过修改标志位实现下次启动的分区切换”。以下是具体实现方式:
一、核心依赖:分区状态标志与 Bootloader 决策
双分区跳转的关键是用一个独立的 “参数区” 存储分区状态,并由 Bootloader 在设备启动时读取该状态,决定加载 A 分区还是 B 分区。
1. 参数区设计
参数区(通常是 Flash 中的一小块独立区域,不可被固件升级覆盖)需存储以下关键标志:
激活分区标志:记录当前应启动的分区(如0x01表示 A 分区,0x02表示 B 分区);升级状态标志:记录是否处于升级过程中(如0x00表示正常,0x01表示待验证新分区);固件校验结果:存储 A/B 分区固件的校验值(如 CRC32、SHA256),用于 Bootloader 验证固件完整性。
示例参数区结构(简化):
struct partition_flag {
uint8_t active_part; // 0x01=A分区, 0x02=B分区
uint8_t upgrade_state; // 0x00=正常, 0x01=新分区待验证
uint32_t crc_a; // A分区固件CRC
uint32_t crc_b; // B分区固件CRC
};
2. Bootloader 的启动流程(核心跳转逻辑)
Bootloader 是设备上电后第一个运行的程序,负责分区选择和引导,流程如下:
读取参数区标志:上电后,Bootloader 首先读取active_part和upgrade_state;验证目标分区固件:根据active_part,读取对应分区(A 或 B)的固件,计算 CRC 并与参数区的crc_a/crc_b比对,确认固件完整;检查升级状态:
若upgrade_state为0x00(正常状态):直接加载active_part对应的分区固件,跳转执行;若upgrade_state为0x01(新分区待验证):加载新分区固件,但需等待固件运行后反馈 “启动成功”; 失败回滚机制:若新分区固件验证失败,或运行后未在规定时间内反馈成功,Bootloader 自动将active_part切回旧分区,重启后加载旧固件。
二、分区切换的触发时机与实现
分区跳转(从 A 到 B 或反之)通常在新固件升级完成并验证通过后触发,由应用程序或 Bootloader 修改参数区标志实现。
1. 升级过程中切换标志(应用程序层)
以 “A 分区运行旧固件,升级到 B 分区新固件” 为例:
设备下载新固件并写入 B 分区,完成后计算 B 分区 CRC,更新参数区的crc_b;应用程序修改参数区:将active_part设为0x02(B 分区),upgrade_state设为0x01(待验证);设备重启,Bootloader 读取标志,加载 B 分区固件。
2. 新固件验证通过后确认切换(新固件层)
新固件(B 分区)启动后,需主动 “确认存活” 以完成最终切换:
新固件运行后,初始化关键模块(如网络、外设),若正常启动,向参数区写入 “成功标志”:将upgrade_state改回0x00(正常状态);若新固件启动失败(如卡壳、崩溃),未在规定时间(如 10 秒)内更新upgrade_state,Bootloader 判定升级失败,下次启动时自动切回 A 分区。
三、硬件与代码层面的具体实现
1. 存储地址映射(以 MCU 为例)
需在链接脚本(如 STM32 的.ld文件)中固定各分区地址,确保 Bootloader 能正确定位:
// 示例:STM32 Flash分区地址定义
#define BOOTLOADER_ADDR 0x08000000 // Bootloader起始地址
#define PARTITION_A_ADDR 0x08008000 // A分区起始地址
#define PARTITION_B_ADDR 0x08048000 // B分区起始地址(与A分区等大)
#define PARAM_ADDR 0x08088000 // 参数区起始地址
2. Bootloader 跳转代码(关键片段)
Bootloader 验证分区后,通过函数指针跳转到目标分区的固件入口(通常是复位向量表的首地址):
// 从参数区读取激活分区
uint8_t active_part = read_param(PARAM_ADDR, offsetof(struct partition_flag, active_part));
// 确定目标分区地址
uint32_t target_addr = (active_part == 0x01) ? PARTITION_A_ADDR : PARTITION_B_ADDR;
// 验证目标分区固件CRC(简化逻辑)
if (verify_crc(target_addr, active_part) != 0) {
// 校验失败,切回旧分区
active_part = (active_part == 0x01) ? 0x02 : 0x01;
target_addr = (active_part == 0x01) ? PARTITION_A_ADDR : PARTITION_B_ADDR;
}
// 跳转至目标分区固件(关键步骤)
typedef void (*boot_func)(void);
boot_func jump_to_app = (boot_func)(*(uint32_t *)(target_addr + 4)); // 固件入口地址(复位向量表第二项)
// 关闭中断,设置栈顶指针(固件复位向量表第一项是栈顶地址)
__set_MSP(*(uint32_t *)target_addr);
jump_to_app(); // 跳转执行新固件
3. 应用程序修改参数区(切换标志)
应用程序升级完成后,通过 Flash 写入函数修改参数区标志:
// 升级完成后,设置下次启动B分区
struct partition_flag flag;
read_param_block(PARAM_ADDR, &flag, sizeof(flag));
flag.active_part = 0x02; // 激活B分区
flag.upgrade_state = 0x01; // 标记为待验证
flag.crc_b = calculate_crc(B_ADDR, B_SIZE); // 更新B分区CRC
write_param_block(PARAM_ADDR, &flag, sizeof(flag)); // 写入参数区
NVIC_SystemReset(); // 重启生效
四、关键注意事项
参数区保护:参数区是分区跳转的 “核心大脑”,需防止意外篡改(如加写保护、冗余存储多份标志,取多数值);固件入口地址:各分区固件的链接地址必须与分区物理地址一致(通过链接脚本保证),否则跳转后会运行异常;中断向量表重映射:部分 MCU(如 STM32)的中断向量表默认在 0 地址,跳转至新分区后需通过寄存器将向量表重映射到当前分区地址,否则中断会失效;原子操作:修改参数区标志时需用 “原子操作”(如先擦除再写入,避免断电导致标志位错乱),必要时分两步写入(先写临时标志,确认后再写正式标志)。
总结
双分区的跳转执行本质是 **“Bootloader 根据参数区标志选择启动分区,应用程序通过修改标志控制下次启动的分区”**,核心依赖三个要素:独立的参数区存储状态、Bootloader 的校验与决策逻辑、固件运行后的存活确认机制。这种设计确保了升级失败时能自动回滚,是双分区方案可靠性的关键。