嵌入式Linux OTA差分升级:把固件更新包体积压缩70%,节省带宽成本
蜂窝、NB-IoT、远程工业网关最怕整包rootfs动辄几十MB。差分升级只在服务端对“旧版本镜像/文件”和“新版本”做字节级diff,设备下小块patch,本地合成为完整新固件再写备用分区。改动比例小的时候,差分包常只有全量包10%~30%,相当于传输体积降70%左右;若大版本重排、编译器换工具链,差分包会变大,这时要回退全量。
一、服务端生成差分包
镜像级用xdelta3更省内存,bsdiff压缩率更高但合并吃RAM:
# xdelta3:旧rootfs -> 新rootfs
xdelta3 -e -9 -S none -s rootfs_v1.img rootfs_v2.img rootfs_v1_v2.xd3
# 再二级压缩,弱网更省
zstd -19 rootfs_v1_v2.xd3 -o rootfs_v1_v2.xd3.zst
# bsdiff:应用或镜像微分
bsdiff app_v1 app_v2 app_patch.bsdiff
gzip -9 app_patch.bsdiff
生成后必算摘要并写manifest,避免设备基线不一致:
sha256sum rootfs_v1.img rootfs_v2.img > base.manifest
echo '{"base":"v1","target":"v2","patch":"rootfs_v1_v2.xd3.zst","sha256":"..."}' > ota.json
# 私钥签名
openssl dgst -sha256 -sign ota_priv.pem -out ota.sig ota.json
基线必须按设备当前运行版本来;现场若写过序列号、first-boot改过镜像,patch会合成失败,所以服务端要保留最近N个旧版本基线,更老版本直接发全量。
二、设备端下载与验签
A/B分区布局简化如下:rootfs_a当前运行,rootfs_b备用,misc存active标记。下载只取差分包,按manifest验签名再验patch哈希:
curl -C - -o /data/ota/patch.xd3.zst https://ota/app/rootfs_v1_v2.xd3.zst
zstd -d /data/ota/patch.xd3.zst -o /data/ota/patch.xd3
# 公钥验签
openssl dgst -sha256 -verify ota_pub.pem -signature ota.sig ota.json
sha256sum /data/ota/patch.xd3 # 与manifest比对
-C支持断点续传;弱网用HTTP Range分片,每片写位图,失败只补缺失片。
三、本地合成新镜像
用当前运行旧分区做base,合成到临时文件,再写b分区:
# xdelta3还原
xdelta3 -d -S none -s /dev/mmcblk0p_rootfs_a \
/data/ota/patch.xd3 \
/data/ota/rootfs_new.img
# 或bsdiff还原应用
bspatch /dev/mmcblk0p_rootfs_a /data/ota/rootfs_new.img /data/ota/app_patch.bsdiff
合成完校目标哈希:
calc_sha=$(sha256sum /data/ota/rootfs_new.img | awk '{print $1}')
grep "$calc_sha" base.manifest || { echo "patch mismatch"; exit 1; }
bsdiff合并内存峰值高,大rootfs慎用;小应用、小镜像可用bspatch,RAM紧张改hdiff/hpatch流式合成。
四、写备用分区与原子切换
合成校验通过后写inactive槽,不改当前系统:
dd if=/data/ota/rootfs_new.img of=/dev/mmcblk0p_rootfs_b bs=1M conv=fsync
# U-Boot env切active
fw_setenv active_slot b
reboot
启动策略加看门狗和失败计数:新系统连不上、健康检查失败,U-Boot/用户态agent改回a槽。misc只存一个active标记,切换是改指针不是整包复制,断电也不会半写整系统。
五、带宽与落地账
以64MB rootfs、小版本改驱动和App为例:整包压缩后仍可能10MB+,xdelta3差分包常1~3MB,zstd再压一截,相对整包传输常可省70%以上;若只更app,bsdiff+gzip常几百KB。服务端按“版本对”预生成:v1->v2、v1->v3、v2->v3,超范围发全量。CI里固件编译完自动跑diff,工具链版本固定,避免gcc升级导致二进制全变、差分失效。
六、避坑
差分包不是万能:base哈希不一致、镜像含随机量产地数据、分区有坏块偏移,都应退化全量;
签名验签放合成前,合成后写盘前再验目标哈希,双层防篡改;
A/B存储要留“1.5个整包”空间,差分临时文件、旧base、new镜像别同时落小/tmp;
工业实时板别在业务高峰期合成,xdelta/bsdiff会占CPU,可绑低优先级核或夜间窗口;
回滚别只靠应用层,U-Boot/ATF启动计数+watchdog才是防砖底线。
按“服务端按版本对出差分+设备验签验基线和patch+A/B写备用槽+哈希全量校验+启动失败自动回滚”,嵌入式Linux既把OTA流量压下来,又保留全量兜底,适合4G/5G按流量计费的边缘网关长期运维。





