第四章:差分升级原理
差分升级,说白了就是「只传变化的部分」。
我刚开始做OTA的时候,用的是全量升级。每次固件大一点,用户就抱怨流量耗得快。后来我接触到差分算法,才意识到——原来大部分升级,改动的代码可能不到10%。
你想想看,一个512KB的固件,每次只改了几行代码,却要下载整个镜像。这不合理。差分升级就是来解决这个问题的。
4.1 差分算法简介
差分算法的核心思想很简单:比较新旧两个文件,找出差异,只传输差异部分。
常见的差分算法有几种:
- bsdiff:基于后缀排序的差分算法,压缩率高,但内存消耗大
- hdiffpatch:针对二进制文件的差分算法,速度较快
- xdelta:基于VCDIFF标准的差分算法,通用性强
- courgette:Google Chrome用的差分算法,针对可执行文件优化
我个人习惯用bsdiff。为什么?因为它在嵌入式场景下,压缩率表现最好。我在项目中遇到过一个问题:一个1MB的固件,新旧版本只改了200KB的代码,bsdiff生成的差分包只有50KB左右。这个压缩比,全量升级根本做不到。
关键指标对比:
| 算法 | 压缩率 | 内存消耗 | 速度 | 适用场景 |
|---|---|---|---|---|
| bsdiff | 高 | 高 | 慢 | 固件升级 |
| hdiffpatch | 中 | 中 | 中 | 通用二进制 |
| xdelta | 中 | 低 | 快 | 文件同步 |
| courgette | 极高 | 极高 | 极慢 | PC端应用 |
嗯,这里要注意:嵌入式设备的内存通常很有限。bsdiff在生成差分包时,需要把新旧两个文件都加载到内存里。如果你的MCU只有64KB RAM,那处理512KB的固件就有点吃力了。
4.2 bsdiff/bspatch原理
bsdiff的原理,我尽量用通俗的话讲清楚。
它分三步走:
- 后缀排序:把旧文件的所有后缀字符串排序,构建一个后缀数组
- 匹配查找:在新文件中,用二分查找找到与旧文件最长的匹配片段
- 差异编码:把匹配不上的部分,用增量编码存起来
说白了,bsdiff就是在旧文件里「找相似」。找到相似的就记个偏移和长度,找不到的就存原始数据。
bspatch是反向操作。它读取差分包,根据里面的指令,从旧文件中复制数据,再应用差异数据,最终拼出新文件。
我的经验:bsdiff生成的差分包,通常包含三种指令:
- COPY:从旧文件复制一段数据
- ADD:添加一段新数据
- DIFF:应用差异数据到旧文件片段上
这三种指令组合起来,就能精确还原新文件。
我曾经踩过一个坑:bspatch在合并时,需要同时访问旧文件和差分包。如果旧文件存储在Flash的某个分区,而差分包又下载到另一个分区,那就要小心地址重叠的问题。有一次我忘了检查,结果合并时把旧文件覆盖了,整个设备变砖。嗯,从那以后,我每次做差分合并,都会先做地址有效性检查。
4.3 差分包的生成与合并
差分包的生成,通常在服务器端完成。你只需要把新旧两个固件传上去,跑一下bsdiff命令就行。
# 生成差分包
bsdiff old_firmware.bin new_firmware.bin patch.bin
# 合并差分包(在设备端)
bspatch old_firmware.bin new_firmware.bin patch.bin
看起来很简单对吧?但实际项目中,坑都在细节里。
我建议你在生成差分包时,注意以下几点:
- 对齐检查:新旧固件的起始地址要对齐,否则差分包会很大
- 版本校验:生成差分包时,记录新旧版本的哈希值,合并时先验证
- 内存规划:设备端合并时,需要预留足够的内存来存放新旧文件和差分包
避坑指南:我曾经遇到过一个案例——差分包生成时一切正常,但在设备端合并总是失败。排查了半天,发现是设备端的Flash写入速度太慢,导致bspatch在读取旧文件时超时了。解决方案是:在合并前,先把旧文件的关键部分缓存到RAM里。
差分包的合并流程,我一般这样设计:
- 下载差分包到临时分区
- 校验差分包的完整性(CRC或哈希)
- 读取旧固件,逐段应用bspatch指令
- 将合并后的新固件写入目标分区
- 校验新固件的完整性
- 切换启动分区,重启设备
你想想看,如果第3步失败了,旧固件还在,设备还能回滚。这就是差分升级的另一个好处——容错性强。
内存管理技巧:在嵌入式设备上做差分合并,内存是最大的瓶颈。我常用的策略是「分块处理」:
- 把旧固件分成多个块,每块256KB
- 每次只加载一个块到RAM
- 合并完成后,立即写入Flash,释放内存
- 处理下一个块
这样,即使RAM只有64KB,也能处理1MB以上的固件。
最后说一句:差分升级不是万能的。如果新旧固件差异太大(比如超过50%),差分包的大小可能接近全量包。这时候,我建议你评估一下——是继续用差分,还是直接全量升级更划算。
嗯,差分升级的原理就讲到这里。下一章,我们会深入讨论「差分升级的内存优化策略」,包括如何减少内存碎片、如何优化合并速度等实战技巧。