1 随着嵌入式系统在各个领域的广泛应用,嵌入式软件的维护变得日益重要[1]。嵌入式系统投入实际环境中运行后,一部分在软件开发过程中无法充分测试的错误便会暴露出来;在嵌入式系统的运行期内,用户也往往会对嵌入式软件提出新的功能要求和性能要求。因此,嵌入式软件的更新逐渐成为嵌入式系统实际应用的一个重要问题。当嵌入式系统安装数量较多,或安装位置不方便的情况下,采用人工更新方式会花费较大的人力和物力。远程自动更新则在嵌入式系统中设计一个有线或无线的通信接口,在异地采用远程通信的方式实现嵌入式软件的自动更新。该方式能有效地降低嵌入式软件的更新和维护成本,因此受到了广泛的关注。当前对嵌入式软件远程自更新技术的研究主要停留在更新方法的设计上,对如何确保更新过程的可靠性还没有深入的研究。更新的可靠性主要受两个方面的影响:一是更新数据远程传输的可靠性;另一方面是系统更新后启动的可靠性。在采用嵌入式Linux、Windows CE等较为复杂的操作系统时,一般设计一个独立的bootloader程序[2][3],对系统进行初始化并引导嵌入式操作系统。这种结构的嵌入式系统的启动与bootloader紧密相关。本文针对采用bootloader的嵌入式系统,提出了一种高可靠的嵌入式软件远程自动更新机制,并以基于ARM微处理器[4]、嵌入式Linux操作系统[5]和无线通信接口的嵌入式系统为例进行了软硬件设计。然后将更新机制应用到实际系统中进行测试,最后给出了本文的结论。
2 更新系统的总体结构
本部分以ARM和嵌入式Linux操作系统为例,介绍远程自更新系统的整体结构。支持远程自更新的嵌入式系统可以划分为前端运行模块和后台控制模块两部分。前端运行模块采用基于ARM9核心的S3C2410微处理器,最高主频可达200Mhz,配有8M的RAM和32M的FLASH存储器,运行ARM-Linux嵌入式操作系统,具有独立设计的bootloader程序。后台控制模块用于实现对前端运行模块的远程异地控制,是系统的控制核心。前端运行模块通过无线模块BCM860接入到CDMA2000-1X无线通信系统,继而连接到Internet。CDMA2000-1X是第2.5代移动通信系统,支持较高速率的数据分组业务。后台控制模块采用有线方式直接与Internet相连接,与前台运行模块之间进行标准的基于TCP/IP协议的通信。状态信息、控制信息和更新文件都在这一数据链路上进行传输。
图1 系统结构
3 更新系统的软件设计
3.1 Flash存储器的布局
传统的采用Linux的嵌入式系统的FLASH存储器的布局如图2(a)所示。为了叙述方便,我们将Linux内核和文件系统合并称为Linux镜像文件。系统进行软件更新时,先把新镜像文件下载到内存中,然后烧写到FLASH的镜像文件存储区,覆盖旧的镜像文件。该方法没有考虑更新过程中可能遇到的干扰。如果在更新中因为突发的干扰出现更新数据错误或烧写错误,就可能导致软件更新后bootloader无法启动新的镜像文件,前端模块无法运行,并失去了与后台控制中心的联系,成为“孤立系统”。这种情况发生后只能到前端系统的安装地点进行人工处理,从而导致花费较大的代价。
图2 (a) 传统的FLASH布局 (b) 自更新系统的FLASH布局
为了提高软件更新的可靠性,本机制对FLASH存储器的布局进行了重新设计,如图2(b)所示。Bootloader存储区后面设计了3个镜像文件存储区。其中一个存储区用于存放当前要启动的最新的镜像文件,称为当前区;另一个存储区用于存放上一版本的镜像文件,称为前版本区;剩下一个存储区用于存放最初版本的镜像文件,称为初版本区。当前区和前版本区位于前两个存储区,随着软件的更新而动态交替变化。每次进行软件更新,把新的镜像文件写入前版本区,这样当前区就变成了前版本区,前版本区则变成了当前区。初版本区固定在第三个分区,所存放的镜像文件是嵌入式系统投入运行时的最初程序,每次的软件更新都不更改该存储区的内容。此外,在FLASH的高地址处设计一个参数存储区,用于存放需要固化的系统配置参数。更新程序需要用到启动点和更新位两个配置参数。启动点用来指示bootloader需要从哪个存储区加载镜像文件,更新位用来指示系统进行软件更新的状态。
3.2 更新任务的设计
在嵌入式Linux系统中,将更新该任务设计成一个阻塞在更新信号量上的进程。系统每次启动后,更新进程首先向后台控制模块报告当前的软件版本号,随后检查FLASH上的更新位,然后进入阻塞状态。当系统收到控制模块发来的更新指令后,释放更新信号量,更新进程开始进行软件更新。更新进程采用TCP协议接收控制模块发来的新程序镜像文件。考虑到无线数据传输的特点,为了增强传输可靠性,在应用层设计一套具有校验、确认和重传功能的收发协议,以保证新镜像文件的数据能够准确无误的通过Internet和移动通信系统传输到前端模块的内存中。当新镜像文件下载完毕后,更新进程先判断前版本区在FLASH上的位置,然后调用FLASH的读写函数将新镜像文件写入前版本存储区中。写完后将更新位置1,并更改启动点。如果当前运行的程序版本为最初版本,则将新镜像文件同时写入前两个存储区。更新进程的程序流程如图3所示。
图3 更新进程流程图
图4 异常处理流程图
3.3 更新后的启动流程
前端运行模块具有独立的bootloader,并固化在FLASH存储器的低地址处,系统启动后总是能够进入bootloader。Bootloader通过读取参数存储区的启动点参数来引导3个程序存储区的某一个镜像文件。在正常情况下,启动点参数总是指向当前区。当软件更新后,启动点也随之在存储区1和存储区2之间交替切换,指向新镜像文件的存储区。这样,当更新进程重新启动前端模块后,bootloader便会引导新的镜像文件。
为了增强软件更新后系统启动的可靠性,本机制利用ARM体系的异常来处理启动时的出错情况。在ARM体系结构中有7种异常,与启动过程密切相关的异常有未定义指令、指令预取中止和数据访问中止3种。通过设计异常处理程序来加载备份存储区的镜像文件。当bootloader引导新镜像文件失败后,进入异常处理函数,在此函数中将启动点更改为指向前版本区,并把更新位置为2,表示已经更改过两次启动点。重启系统后bootloader便会恢复引导上一版本的程序镜像文件。由于软件更新时只是在相邻的存储区写入了新镜像文件,并未对上一版本程序的存储区进行任何操作,而且上一版本程序是更新前已经正常启动运行过的程序,所以一般情况下上一版本程序应该能够正常引导成功。如果由于意外干扰导致上一版本程序仍然无法启动成功,此时将再次进入异常处理程函数。在函数中先读取更新位的值,如果更新位为2则将启动点指向初版本区,并把更新位置为3。重启系统后bootloader便会引导最初版本的程序镜像文件。最初版本的镜像文件在前端模块安装后从未更改过,并在安装前进行过验证测试,具有非常高的可靠性。当新版本程序和上一版本程序都无法启动成功时,系统尝试启动最初版本的镜像文件,以保证系统基本功能的正常运行,并确保前端模块与后台控制模块之间不会失去联系。后台控制模块根据前端模块启动后报告的版本号来获知软件更新的信息并采取相应的措施。在异常处理函数中读取启动点值的时候,如果读取值异常或读取出错,则对启动点进行修复,异常处理函数的流程如图4所示。通过利用启动点、更新位和异常处理程序,当软件更新过程中遇到了干扰和错误时,本机制能够依次选择启动两个备份的软件版本,有效地提高了软件更新的可靠性,防止了“孤立系统”的出现。
4 测试结果