没有完美的软件,因为设计缺陷、业务需求更新,软件始终都在不断升级完善。新软件如何替换正在运行的旧软件就是本文关注的重点,尤其是针对电子产品,设备空中升级OTA,受限于硬件资源,需要选择不同的方案进行软件升级。
在线升级流程,简化就是设备运行旧软件的同时,获取新软件包,再执行特殊操作使用新软件覆盖旧软件,最后运行新软件。
根据硬件资源和系统整体框架,选择不同的升级方案,方案需要结合实际选择最佳的,技术层面是次要的。
以STM8单片机升级为例,单片机最小系统运行流程如下:
要加入在线升级功能,就需要将主应用程序拆分,类似有2套程序在设备内运行,标准称呼是bootload+app,其中bootload始终不变,它接收新软件包并覆盖app区域。
硬件限制 | 解决方案 |
---|---|
单片机自身没有网络连接功能,只能基于外挂的网络模块从远端服务器下载新软件包,再通过串口传输给单片机 | 由外挂主机下载新软件包,并通知单片机进入升级模式 |
单片机内部RAM小,不能进行复制运算或者缓存大量数据;单片EEPROM小,不能保存完整的新软件包 | 只能在Bootload期间分段接收新软件并立刻写入flash |
整体方案如下图:
差分升级软件框架同整包升级一样,区别在于新软件包的提供方式不同。
单片机软件一般不超过50KB,复杂微处理器的软件一般都比较大,但其RAM也大,下载完整的新软件包耗时长且浪费流量,因此可以基于新旧两版软件的差异,将差异文件传给设备,由设备运算还原出新软件包升级。
动态加载在PC软件中很常见,多个exe可执行文件共用dll库文件,这样exe文件很小,缺点是要保证exe正常运行,需要在指定的路径下存放dll文件。
对嵌入式平台,动态加载的可以理解为始终保持底层基础框架不变,修改或替换不同的上层业务逻辑,实现不同的效果。为保证底层和上层之间调用关系,必须固定部分接口地址,也就是两者中间的接口映射,主要通过链接实现。
嵌入式软件从源码到可下载到设备的映像文件,需要经过的步骤:
动态加载就是在链接阶段,将上层代码obj编译成axf可动态加载的文件,而不是直接与其他obj合并成可执行文件。主要是使用armlink配置-entry指定映像文件的初始入口点或者在代码中使用#pragma arm section code关键字,保证动态的上层有固定的入口地址。凡是上层调用的底层接口,在编译阶段函数体指针都赋为空指令保证编译,后续再指向底层的真实地址。
其作用发生在系统启动阶段,从flash加载到内存,整个文件内的接口相对地址不变,整体偏移。这样,软件还是可以计算获取动态映像文件的入口地址。加载到内存区域,需确保该区域不会被占用,否则内存覆盖肯定会导致异常。
底层启动后,只能查到动态加载文件的入口函数,但实际底层与上层交互的接口肯定不止一个,而且上层也必然会调用底层接口,这就需要在第一个明确地址的函数体内实现上下层地址映射。
这里有2种方案,一种是函数指针赋值,一种是根据字符串查找。
底层需要给上层调用的接口,底层映射接口函数指针表,按固定顺序赋值给上层函数指针;或者底层只提供上层一个函数,但是改函数体内查字符串获取函数指针。这样实现上层调用事先固定的底层接口。
上层给底层提供的接口,也是提前固定的函数指针,也用上面的方法对接。
接口映射的核心是动态加载块有一个函数的地址是链接时指定的,在这个函数内实现上下层函数映射。除函数外,全局变量也是同样的使用指针传递。
在线升级为了产品在不召回的情况下,以较低的成本解决售后问题;升级是为了解决问题,但是一旦失败则可能导致设备变砖。前期测试应选择不同设备模拟升级异常,如强制断电或软件包异常,设备必须有自我恢复的机制。