Bootloader、App 和升级分区之间是什么关系?

在嵌入式设备中,固件升级通常不是简单地"把新程序写进去"。
一个完整的升级系统,通常包含三个重要部分:
- Bootloader:启动程序
- App:业务应用程序
- 升级分区:临时存放新固件的区域
它们共同完成设备的启动、运行、升级和回滚。
一句话概括:
Bootloader 负责选择和启动,App 负责实现功能,升级分区负责暂存新固件并保障升级安全。
一、设备上电后,谁先运行?
设备上电或复位后,CPU 通常会从固定地址开始执行代码,这个地址一般指向 Bootloader。
典型启动流程如下:
text
设备上电
↓
Bootloader 启动
↓
初始化时钟、RAM、看门狗等硬件
↓
检查升级标志
↓
校验 App 固件
↓
决定启动旧 App、新 App 或升级模式
↓
跳转到 App
↓
App 开始运行
所以,Bootloader 的运行时机早于 App。
App 并不是设备上电后第一个运行的程序,它需要等待 Bootloader 完成检查后,才能获得 CPU 的控制权。
二、Bootloader 是什么?
Bootloader 可以理解为设备中的"启动管家"或"系统引导程序"。
它通常位于 Flash 的固定区域,并且不会频繁变化。设备每次上电时,Bootloader 都会先执行。
Bootloader 的主要职责
1. 初始化基础硬件
Bootloader 一般会完成一些最基本的硬件初始化,例如:
- 设置系统时钟
- 初始化 SRAM
- 配置看门狗
- 初始化 Flash
- 初始化串口、USB、CAN、以太网等升级接口
- 检查按键或升级触发条件
Bootloader 不一定会初始化所有外设,它通常只初始化启动和升级所必需的硬件。
2. 检查是否需要升级
Bootloader 可以通过多种方式判断设备是否需要进入升级流程:
- 检查 Flash 中的升级标志
- 检查是否存在有效的新固件
- 检查是否按下升级按键
- 检查上位机是否发送升级命令
- 检查 App 是否请求进入升级模式
- 检查上一次升级是否异常中断
例如,可以在固定地址保存一个升级标志:
c
#define UPDATE_FLAG_ADDR 0x0800F000
#define UPDATE_REQUEST 0x5AA55AA5
App 收到升级命令后,把标志写入 Flash,然后重启设备。
Bootloader 启动后读取这个标志:
c
if (read_update_flag() == UPDATE_REQUEST)
{
enter_upgrade_mode();
}
else
{
jump_to_app();
}
3. 校验 App 固件
Bootloader 不能直接相信 Flash 中的程序,需要先判断固件是否完整、合法。
常见校验方式包括:
- CRC 校验
- MD5 校验
- SHA-256 校验
- 数字签名校验
- 固件版本校验
- 固件长度校验
- 固件头信息校验
一个简单的固件头可能包含:
text
固件标识
固件版本号
固件长度
固件 CRC
编译时间
硬件型号
数字签名
Bootloader 校验通过后,才会跳转到 App。
如果校验失败,就不能直接运行这份固件,否则可能出现:
- 程序跑飞
- 设备无法启动
- 反复重启
- 设备变砖
4. 跳转到 App
当 Bootloader 确认 App 有效后,就会跳转到 App 的入口地址。
跳转前一般需要完成以下操作:
- 关闭中断
- 停止 Bootloader 使用的外设
- 清除中断状态
- 设置 App 的栈顶指针
- 获取 App 的复位入口地址
- 跳转到 App 的复位处理函数
以 Cortex-M 单片机为例,App 起始地址可能是:
c
#define APP_START_ADDR 0x08010000
跳转逻辑通常类似:
c
typedef void (*app_entry_t)(void);
uint32_t app_stack = *(volatile uint32_t *)APP_START_ADDR;
uint32_t app_reset = *(volatile uint32_t *)(APP_START_ADDR + 4);
__set_MSP(app_stack);
app_entry_t app_entry = (app_entry_t)app_reset;
app_entry();
需要注意,实际项目中还要根据芯片型号处理中断向量表、Cache、MPU 和外设复位等问题。
三、App 是什么?
App 是设备真正执行产品功能的程序,也可以称为应用固件或业务固件。
例如:
- 智能锁的密码识别
- 温湿度计的数据采集
- 电机控制器的控制算法
- 传感器数据上传
- 蓝牙通信
- 屏幕显示
- 按键处理
- 网络通信
- 参数存储
Bootloader 负责"启动谁",App 负责"设备具体做什么"。
四、App 固件内部通常包含什么?
一个典型的 App 固件通常由以下部分组成:
text
+---------------------------+
| 中断向量表 |
+---------------------------+
| 复位入口 |
+---------------------------+
| 程序代码 .text |
+---------------------------+
| 只读常量 .rodata |
+---------------------------+
| 已初始化数据 .data |
+---------------------------+
| 未初始化数据 .bss |
+---------------------------+
| 用户参数 / 配置区域 |
+---------------------------+
| 固件校验信息 |
+---------------------------+
1. 中断向量表
中断向量表中保存了:
- 初始栈顶地址
- 复位处理函数地址
- 各种中断服务函数地址
Bootloader 跳转到 App 时,实际上会使用 App 自己的向量表。
2. 程序代码
程序代码包括:
- 主函数
- 驱动程序
- 通信协议
- 控制算法
- 数据处理逻辑
- 状态机
- 故障处理逻辑
这些代码最终会被编译、链接,并生成 .bin、.hex 或 .elf 文件。
3. 版本信息和校验信息
为了支持可靠升级,App 固件通常会携带一些元数据:
text
产品型号:DEVICE_A
硬件版本:V2.0
软件版本:V1.3.5
固件长度:128 KB
CRC 校验值:0x12345678
Bootloader 可以根据这些信息判断:
- 固件是否属于当前设备
- 固件版本是否可以升级
- 固件是否完整
- 固件是否被篡改
五、升级分区是什么?
升级分区是 Flash 中专门用于存放新固件的区域。
它也可以叫:
- Download 分区
- OTA 分区
- Secondary 分区
- Backup 分区
- App B 分区
- 暂存分区
升级分区的核心作用是:
让新固件先写入一个独立区域,验证成功后再切换运行。
这样可以避免直接覆盖当前正在运行的 App。
六、为什么不能直接覆盖当前 App?
假设设备只有一个 App 区域,升级时直接擦除并写入新固件。
如果升级过程中发生以下情况:
- 设备突然断电
- 通信中断
- Flash 写入失败
- 固件数据不完整
- CRC 校验失败
- 看门狗复位
那么原来的 App 可能已经被破坏,新 App 又没有写完整,设备就无法再次启动。
这就是常说的"设备变砖"。
因此,更可靠的方式是:
text
当前 App 继续运行
↓
新固件下载到升级分区
↓
完整校验新固件
↓
重启设备
↓
Bootloader 切换到新固件
七、典型的 Flash 分区方式
一种常见的分区结构如下:
text
Flash 起始地址
+---------------------------+
| Bootloader |
+---------------------------+
| App A:当前运行版本 |
+---------------------------+
| App B:升级分区 |
+---------------------------+
| 参数区 / 标志位 / 日志区 |
+---------------------------+
Flash 结束地址
例如:
text
0x08000000 - 0x0800FFFF:Bootloader
0x08010000 - 0x0807FFFF:App A
0x08080000 - 0x080EFFFF:App B
0x080F0000 - 0x080FFFFF:参数和升级标志
具体地址需要根据以下因素进行设计:
- 芯片 Flash 容量
- Bootloader 大小
- App 固件大小
- 是否采用双分区
- 是否需要保存参数
- 是否需要加密和签名
- 是否需要保存升级日志
八、一次完整升级流程
下面以 App A 正在运行、App B 作为升级分区为例。
第一步:App 请求升级
用户通过手机、上位机或云端发起升级。
App 收到升级命令后,进入升级准备状态:
text
停止非必要任务
保存设备参数
设置升级标志
重启设备
第二步:Bootloader 进入升级模式
设备重启后,Bootloader 读取升级标志,判断需要进入升级流程。
此时 Bootloader 可能通过以下接口接收固件:
- UART
- USB
- CAN
- RS485
- Ethernet
- Wi-Fi
- BLE
- 4G
第三步:新固件写入升级分区
Bootloader 将接收到的新固件写入 App B 区域。
写入过程中需要注意:
- 按 Flash 擦除粒度擦除
- 按芯片要求对齐写入
- 进行分包传输
- 对每个数据包校验
- 记录当前写入偏移
- 处理断点续传
- 防止越界写入
第四步:校验新固件
写入完成后,Bootloader 对 App B 进行完整校验:
text
固件长度是否正确?
固件 CRC 是否正确?
产品型号是否匹配?
硬件版本是否兼容?
数字签名是否有效?
版本号是否允许升级?
只有全部通过,才允许切换。
第五步:设置启动标志
校验通过后,Bootloader 或升级程序会设置启动标志:
text
BOOT_TARGET = APP_B
IMAGE_STATE = PENDING
然后重启设备。
第六步:试运行新 App
Bootloader 再次启动后,发现 App B 处于待确认状态,于是尝试启动 App B。
新 App 启动后,需要完成自检,例如:
- 外设初始化成功
- 参数读取成功
- 通信正常
- 关键任务正常运行
- 看门狗没有异常复位
如果自检成功,App B 将状态修改为:
text
IMAGE_STATE = CONFIRMED
此时 App B 成为正式运行版本。
第七步:升级失败时自动回滚
如果 App B 启动失败,或者在规定时间内没有完成确认,Bootloader 会认为升级失败。
然后执行回滚:
text
启动 App A
清除 App B 的待确认标志
记录升级失败原因
这样设备仍然可以使用旧版本运行。
九、A/B 分区和单分区有什么区别?
单分区升级
text
Bootloader + App
升级时直接覆盖原来的 App。
优点:
- 占用空间小
- 设计简单
- 成本较低
缺点:
- 断电容易变砖
- 回滚困难
- 对升级流程要求高
A/B 双分区升级
text
Bootloader + App A + App B
一个分区运行,另一个分区接收升级包。
优点:
- 升级过程更安全
- 支持快速回滚
- 可以保留旧版本
- 适合 OTA 和无人值守设备
缺点:
- 需要更大的 Flash
- 分区管理更复杂
- 需要设计版本和状态管理
对于工业设备、智能家居、车载设备和远程 OTA 产品,A/B 分区通常更加可靠。
十、三者之间的关系
可以用下面这句话理解:
text
Bootloader:负责启动和选择
App:负责设备功能
升级分区:负责暂存和保护
它们的关系如下:
text
设备上电
↓
Bootloader 先运行
↓
检查 App 和升级分区
↓
选择有效固件
↓
跳转到 App
↓
App 执行业务功能
↓
需要升级时,将新固件写入升级分区
↓
Bootloader 校验并切换版本
十一、开发中最容易踩的坑
1. Bootloader 和 App 的地址不一致
链接脚本、烧录地址和跳转地址必须保持一致。
例如 App 实际链接地址是:
text
0x08010000
但 Bootloader 却跳转到了:
text
0x08000000
就会导致程序无法正常启动。
2. 忘记修改 App 的中断向量表
App 不再位于 Flash 起始地址后,需要重新设置向量表偏移。
Cortex-M 中通常需要配置:
c
SCB->VTOR = APP_START_ADDR;
否则中断可能跳转到错误地址。
3. 跳转前没有关闭中断和外设
Bootloader 使用过的中断、定时器或串口,如果没有清理,可能干扰 App 的正常运行。
4. 没有做固件完整性校验
只判断"有没有数据"是不够的,必须校验:
- 长度
- CRC
- 版本
- 型号
- 签名
5. 升级标志没有设计状态机
建议至少区分以下状态:
text
EMPTY 空分区
DOWNLOADING 下载中
DOWNLOADED 下载完成
VERIFIED 校验通过
PENDING 待启动确认
CONFIRMED 启动成功
ROLLBACK 需要回滚
如果只使用一个简单标志位,遇到断电或异常复位时,很难判断当前处于什么状态。
十二、总结
Bootloader、App 和升级分区并不是三个互相独立的程序区域,而是一套完整的启动和升级体系。
- Bootloader:设备上电后首先运行,负责初始化、校验、升级和跳转。
- App:实现设备的核心业务功能,是用户真正使用的程序。
- 升级分区:存放待升级的新固件,避免直接破坏当前运行版本。
- A/B 分区:通过保留新旧两份固件,实现安全升级和失败回滚。
最终可以记住这句话:
Bootloader 是门卫,App 是业务主体,升级分区是新版本的候场区,也是设备升级失败时的安全保障。