4、升级包制作与管理:固件版本号规范、升级包签名校验、差分升级(增量包)原理、升级包存储策略
各位同行,今天我们来聊聊升级包的制作与管理。说实话,这块内容看着简单,但坑特别多。我见过不少项目,升级功能写好了,结果因为版本号乱标、升级包没签名,现场设备刷成了砖。嗯,咱们今天就把这些细节掰开揉碎了讲清楚。
4.1 固件版本号规范:别让版本号成为灾难
版本号这东西,说白了就是个标识。但你要是随便写,后期维护起来能让你崩溃。我个人习惯用三段式:主版本.次版本.修订号。
- 主版本:架构或协议有重大变更时加1。比如从Modbus RTU换成Modbus TCP,主版本必须变。
- 次版本:新增功能或接口变化时加1。比如加了OTA功能,次版本升级。
- 修订号:修复bug或小优化时加1。比如修复了某个内存泄漏,修订号递增。
我在项目中遇到过一件事:某厂商把版本号写成"V2024.03.15",看着像日期。结果同一天发布了两个补丁,版本号完全一样,现场根本分不清哪个是最新的。你想想看,这多可怕?
格式:MAJOR.MINOR.PATCH-BUILD
示例:2.1.3-20240315
说明:主版本2,次版本1,修订号3,构建日期2024年3月15日
另外,我建议在固件头部固定位置写入版本号字符串。这样不管是上位机还是Bootloader,都能直接读取。代码里可以这样写:
// 固件版本信息结构体
typedef struct {
uint8_t major; // 主版本
uint8_t minor; // 次版本
uint16_t patch; // 修订号
uint32_t build_date; // 构建日期(Unix时间戳)
uint8_t reserved[8];// 预留
} firmware_version_t;
// 放在固定地址,比如0x08000100
const firmware_version_t g_fw_version __attribute__((section(".version"))) = {
.major = 2,
.minor = 1,
.patch = 3,
.build_date = 1710489600, // 2024-03-15
};
4.2 升级包签名校验:防止被篡改的最后一道防线
升级包如果不做签名校验,就等于把设备的安全拱手让人。我曾经见过一个案例:黑客伪造了一个升级包,里面植入了挖矿程序,结果整个站点的PLC都被感染了。嗯,从那以后,我再也不敢省略签名这一步。
签名校验的核心逻辑很简单:
- 签名端:用私钥对固件哈希值加密,生成签名文件。
- 验证端:用公钥解密签名,比对固件哈希值是否一致。
实际项目中,我推荐使用ECDSA(椭圆曲线数字签名算法)。相比RSA,它的密钥更短,计算更快,特别适合资源受限的嵌入式设备。
公钥一定要烧死在芯片的OTP(一次性可编程)区域,或者放在Bootloader的只读段。千万别放在文件系统里,否则别人直接替换公钥,你的签名校验就形同虚设了。
签名校验的流程,我画了个图,你一看就明白:
4.3 差分升级(增量包)原理:只传变化的部分
差分升级,说白了就是只传输固件中变化的部分。你想想看,一个完整的固件可能10MB,但每次升级可能只改了100KB的代码。如果每次都全量传输,带宽和流量都浪费了。
差分升级的原理,我简单说一下:
- bsdiff算法:比较新旧两个固件,生成一个差分补丁文件。这个补丁里只包含新增、修改、删除的数据块。
- 设备端还原:用旧固件 + 差分补丁,通过算法还原出新固件。
我在项目中遇到过一个问题:差分升级虽然省流量,但还原过程需要消耗大量内存和CPU。有个设备只有256KB RAM,结果还原到一半内存溢出了。后来我改用块级差分,把固件分成4KB一个块,只传输变化的块,这样内存占用就小多了。
差分升级对固件版本有严格要求。如果设备当前版本和差分补丁的基准版本不一致,还原出来的固件就是错的。我曾经因为版本号搞混,导致一批设备升级后全部死机。所以,差分升级前一定要校验当前固件的版本号。
差分补丁的生成命令示例:
# 生成差分补丁(在服务器端执行)
bsdiff old_firmware.bin new_firmware.bin patch.bin
# 应用差分补丁(在设备端执行)
bspatch old_firmware.bin new_firmware.bin patch.bin
4.4 升级包存储策略:放哪里、怎么放、放多久
升级包下载到设备后,存哪里?这是个好问题。我见过三种主流策略:
| 存储策略 | 原理 | 优点 | 缺点 |
|---|---|---|---|
| 双备份区 | 固件分A区和B区,升级时写入备用区 | 升级失败可回滚,安全性高 | 需要双倍Flash空间 |
| 临时存储区 | 升级包先存到临时区,校验通过后再覆盖 | 节省空间,实现简单 | 升级过程中断电可能变砖 |
| 外部存储 | 升级包存到SD卡或U盘 | 容量大,可离线升级 | 速度慢,依赖外部硬件 |
我个人最推荐双备份区。虽然多占一倍Flash,但安全第一。你想想看,一个SCADA站点可能有几百台设备,如果升级失败导致全部离线,那损失可不是一块Flash芯片能比的。
双备份区的存储布局示例:
Flash地址空间:
0x08000000 - 0x0801FFFF: Bootloader(128KB)
0x08020000 - 0x0809FFFF: 固件A区(512KB)
0x080A0000 - 0x0811FFFF: 固件B区(512KB)
0x08120000 - 0x0813FFFF: 配置参数区(128KB)
0x08140000 - 0x0815FFFF: 升级包临时区(128KB)
升级包下载完成后,先做完整性校验(CRC32或SHA-256),再做签名校验。两关都过了,才允许写入固件区。另外,升级包临时区在升级完成后要立即擦除,防止被恶意读取。
嗯,关于升级包制作与管理,今天就聊这么多。核心就三点:版本号要规范、签名校验不能省、存储策略要安全。你把这些做好了,远程升级这块基本就稳了。