今天在论坛看到这么一个提问:
在嵌入式产品开发初期,我们一般都会用下载器或者仿真器接到板子的下载口进行下载和调试,而一旦产品进入到市场,由于批量品的下载口往往会在产品外壳内部,有些产品甚至在发布时会去掉下载口,此时再使用下载器就会变得非常麻烦,对于去掉下载口的直接无法完成程序的升级!
因此我们一般都会在产品中加入在线升级功能,即通过产品内部的一段升级程序将应用程序更新掉,从而实现无需借助外部烧录工具就可以将程序进行升级。如今的手机、智能手表的升级功能就是使用了这种设计。
普遍的做法是将程序分为两个部分,升级程序和应用程序,对应称呼为 Bootloader 和 APP。设备每次开机都会先进入Bootloader 程序,此时进行更新检测,不存在新升级包的情况下, Bootloader 会直接跳转到 APP 区域运行应用程序,一旦存在新的升级包,则 Bootloader 会将升级包解析,并将新的 APP 程序写入到 APP 区域,写入完成后再跳转到 APP 去运行,此时用户使用的就是升级过的 APP 程序。
此时除了开头截图中提到的重复跳入问题,还会产生一个致命的问题:
一旦在 Bootloader 更新程序的过程中出现了问题,比如更新到一半没电了、由于种种原因写入的数据出错了或者更新的 APP 区域地址出错了,就会导致最终 APP 区域的数据是有问题的,再次启动时如果跳转到这个存在异常数据的 APP 区去运行,那大概率程序会直接停在某条非法指令处,并且由于此时应用程序已经不是正常的程序,也就没办法再使设备回到 Bootloader 模式,无法进退,这时候这个设备就宣告变砖了!
只要能有办法判断 APP 是否有效,必须确认有效再跳转运行,无效的话还是继续运行 Bootloader 程序,此时可以再次传入有效的 APP 数据启动更新,这样就能避免设备变砖,即使升级失败,还能重新再来。
如何判断 APP 是否有效呢?
APP 数据中加入校验
原本升级逻辑中的 APP 数据仅仅是 APP 的数据,因此你无法知道这个数据是对的还是错的,既然如此,我们完全可以在 APP 数据中加入一些校验信息,常见的有 APP 数据长度、CRC、版本号和发布时间等。
此时我们在 Bootloader 中就可以对 APP 数据先进行一个校验,校验成功则说明 APP 没有问题,此时再进行跳转就能保证设备能够正常运行。这个校验信息可以放在 APP 数据之前,也可以放在 APP 数据后,具体可以根据实际情况去选择。
同时这里的校验信息还可以加入加密信息与签名信息从而实现发布固件的安全保证与避免被第三方恶意篡改。
存储区中加入标志位
如果每次启动设备都要进行 APP 的校验,对于 APP 比较小的设备尚能接受,但如果 APP 比较大,如手机的固件动辄 GB 起步,那此时的启动时间就会如同龟速,甚至还不如龟速!
这时候标志位就不失为一个好办法:
在进行升级操作的每个步骤前都将当前状态写入存储区(如 FLASH)的某个区域,并且升级结束后进行一次校验,并将校验结果也更新到存储区,后续开机只需要判断状态位即可判断 APP 是否有效且合法,没有问题则直接进行跳转,省去了每次都要校验完整 APP 数据的时间耗费:
以上为一个简单的升级流程,实际可以更加细化。可以看到,这种设计不仅加快了每次的启动时间,更重要的是借助于存储区的断电非易失特性(即断电后数据不会丢失),即使遇到升级过程中断电导致升级流程中断,再次上电后,不仅不会盲目跳转,还能基于存储区中的标志位继续上一个未完成的步骤,做到异常恢复功能!
以上方案在嵌入式在线升级场景中非常常用,并且实现起来也不是很复杂,最重要的是其解决了设备升级过程中出现异常后仍然有办法进行恢复的问题,大大提高了设备的稳定性和后续的维护性。
客户再也不用担心设备变砖啦!
—— The End ——