4. 升级镜像与包格式
在多分区 OTA 部署中,升级包并不是一个简单的“固件文件”,而是由镜像、元数据、签名、压缩层共同组成的结构化容器。理解包格式,是设计升级流程、控制升级体积、保证升级安全的前提。本节从包类型、镜像组织、签名机制、压缩策略和包结构解析五个维度展开,帮助你在实际工程中做出正确的格式选型。
一、四种升级包类型的本质区别
OTA 升级包按“传输内容”可分为全量包、增量包、差分包和混合包。它们的核心差异在于:升级时设备端需要写入多少数据,以及包本身携带的是“完整镜像”还是“变化描述”。
- 全量包(Full Package):包含所有分区的完整镜像,设备端直接擦写目标分区。优点是逻辑简单、可独立恢复;缺点是体积大,通常为固件总大小的 100%。
- 增量包(Incremental Package):只包含发生变化的文件或分区块,通常以文件系统级差异描述。体积约为全量包的 10%~30%,但依赖设备端当前版本与基线版本严格一致。
- 差分包(Differential Package):基于二进制差分算法(如 bsdiff、hdiffpatch)生成旧镜像到新镜像的补丁。体积最小,可低至全量包的 5%~15%,但需要设备端具备旧镜像并执行 patch 运算。
- 混合包:对关键分区使用全量镜像,对非关键分区使用差分补丁,兼顾可靠性与体积。
二、镜像签名与信任链
升级包在分发前必须经过签名,设备端在写入前必须验签。典型做法是对包内元数据(manifest)计算摘要,再用私钥签名;设备端用预置公钥验签,验签通过后才允许解压和写入。签名对象通常包括:包版本号、目标设备型号、分区列表、各镜像哈希值、时间戳和随机数。
# 典型签名与验签流程(示意)
# 1. 生成镜像哈希
sha256sum system.img > system.img.sha256
# 2. 生成 manifest
cat manifest.json
{
"version": "2.3.1",
"device": "edge-gw-x1",
"partitions": [
{"name": "boot", "hash": "a1b2..."},
{"name": "system", "hash": "c3d4..."}
],
"timestamp": 1710000000
}
# 3. 对 manifest 签名
openssl dgst -sha256 -sign private.pem -out manifest.sig manifest.json
# 4. 设备端验签
openssl dgst -sha256 -verify public.pem -signature manifest.sig manifest.json
签名必须覆盖“包内容摘要”,而不是仅覆盖文件名或版本号。否则攻击者可以替换镜像内容而保留合法签名。推荐使用 ECDSA P-256 或 RSA-3072,并在设备端安全存储公钥,防止被篡改。
三、压缩格式与包结构解析
升级包通常采用“外层容器 + 内层压缩”的结构。外层容器负责描述分区布局和元数据,内层压缩负责减小传输体积。常见压缩格式包括 gzip、xz、zstd 和 lz4。zstd 在压缩率和解压速度之间平衡较好,适合资源受限设备;xz 压缩率最高但解压内存占用大;lz4 解压极快但压缩率一般。
| 压缩格式 | 压缩率 | 解压速度 | 内存占用 | 适用场景 |
|---|---|---|---|---|
| gzip | 中 | 中 | 低 | 通用兼容 |
| xz | 高 | 慢 | 高 | 带宽受限、存储充足 |
| zstd | 中高 | 快 | 中 | 推荐默认 |
| lz4 | 低 | 极快 | 低 | 实时性要求高 |
一个典型的 OTA 包结构如下:包头(magic、版本、包类型)、manifest(分区表、哈希、签名)、压缩数据段(各分区镜像或补丁)、尾部校验(CRC 或整体哈希)。解析时先读包头定位 manifest,验签后再按分区表逐段解压写入。
# 包结构示意
OTA_PACKAGE
├── header (magic, version, type)
├── manifest.json
├── manifest.sig
├── payload/
│ ├── boot.img.zst
│ ├── system.img.zst
│ └── vendor.patch.zst
└── footer (crc32, total_hash)
四、实战场景:差分包生成与校验
假设设备当前运行 v2.2.0,目标版本 v2.3.1。使用 hdiffpatch 生成差分包:
# 生成差分包
hdiffz -f -c-zstd old_system.img new_system.img system.patch
# 设备端应用补丁
hpatchz old_system.img system.patch new_system.img
# 校验新镜像哈希
sha256sum new_system.img
设备端应用补丁前,必须校验旧镜像哈希与 manifest 中记录的基线哈希一致。若不一致,说明设备当前版本被篡改或分区损坏,应拒绝升级并回退到全量包。这是差分包最容易出问题的环节。
五、包结构解析的工程要点
- 分区对齐:镜像写入需按分区块大小对齐,避免写入越界或校验失败。
- 流式解压:设备端应支持边解压边写入,避免将整个镜像缓存在内存中。
- 断点续传:包结构应支持分块校验,便于网络中断后从断点继续下载。
- 版本兼容:manifest 中应声明最低可升级版本和最高可升级版本,防止跨版本差分失败。
- 回滚包:建议同时保留一份全量回滚包,在升级失败时快速恢复。
下图展示了升级包从生成到设备端解析的完整数据流,包括全量包、增量包、差分包三种路径在签名、压缩和校验环节的差异。
总结来看,升级包格式的选择本质上是“体积、可靠性、计算开销”三者的权衡。全量包最稳但最大,差分包最小但依赖基线,增量包居中。无论选择哪种,签名覆盖内容摘要、旧镜像哈希校验、流式解压写入和写入后回读校验,都是不可省略的工程底线。