首页 | 新闻 | 新品 | 文库 | 方案 | 视频 | 下载 | 商城 | 开发板 | 数据中心 | 座谈新版 | 培训 | 工具 | 博客 | 论坛 | 百科 | GEC | 活动 | 主题月 | 电子展
返回列表 回复 发帖

[推荐]对闪存中的底层操作系统进行下载更新时需要考虑的注意事项

[推荐]对闪存中的底层操作系统进行下载更新时需要考虑的注意事项

将底层操作系统放于闪存中有很多好处,例如设计人员可以随时在现场将软件新版本下载到闪存中对其进行更新,但在下载过程中有一些因素需要注意,否则会造成系统崩溃的灾难性后果。本文介绍一种便利的软件结构,它可以帮助工程师避免犯一些常见的错误。

所有现场更新底层操作系统的方法都存在这样一个问题,即假如升级内容中存在缺陷,那么目标系统可能需要花很大力气才能更正,许多缺陷很简单且显而易见,但有些隐藏很深的缺陷只有在产品完成以后才能显现出来。

用户发现和处理缺陷的能力各有千秋,而更糟糕的是,他们往往不注意诸如“如果在编程过程中电源系统中断系统将受到损坏”一类的警告,在系统更新过程中,他们很愿意重新启动其它功能系统,然后再把“出了问题”的产品拿去维修。

所有设计完好的底层操作系统更新过程,都要能够尽可能从用户的错误和其它灾难性事件中恢复,做到这一点最好的方法就是采取可靠的底层操作系统更新策略,以完全避免这些问题。本文将讨论这样一种策略,它可以直接使用,但是也可以根据实际应用的特性对它进行修正。

微编程器

微编程器是对嵌入式系统在底层操作系统更新过程之前、过程中及之后运行情况的一种系统级描述,这种对工作情形的描述有助于避免与其它底层操作系统下载方法有关的问题,小心对待这些过程可以消除很多其它的担心。

采用微编程器的底层操作系统更新方法第一步是使嵌入式系统进入下载发生时所期望的状态。过渡到这个状态可以采用好几种方式,例如,它可以通过用户按下设备控制界面上的“升级”键来完成,或者在系统探测到文件传输开始或结束时进行,或者用其它方法。不管哪种情况,目标系统都会意识到底层操作系统马上要更新了,于是让其它控制过程进入安全和稳定的停止状态。

下一步,目标系统会收到一个叫做微编程器的应用程序,微编程器将控制系统,并开始接收新的应用底层操作系统,将它写到闪存中。一切完成后,目标系统就开始运行新的底层操作系统。

采用微编程器下载底层操作系统的最大特点之一是它的灵活性,微编程器的执行过程即使在底层操作系统开始更新前的最后一刻也可以更改,以便对系统进行错误纠正和改进。

微编程器不会消耗目标系统的资源,除非已开始进行编程。此外,由于微编程器很小,所以目标系统在更新过程开始时不需要庞大而复杂的通讯协议,只需简单的文本文件传输就足够了。

采用微编程器的底层操作系统更新其安全性一度是最引人注目的优点。当目标系统的闪存芯片只用于存储底层操作系统时,系统将不会有对闪存芯片内容进行删除和编程的代码,除非实际过程中需要这样。因此即使在程序失控的严重情况下,系统也不会意外地删除它本身的底层操作系统。

然而,微编程器也有缺点,它通常作为一个独立的程序执行,因此下载和传输到目标系统的代码需要分开管理。处理程序组件时小心谨慎有助于减少后续工作量。

微编程器一般都载入RAM中运行,这在某些微处理器结构中是不可能的,尤其是古老的8051系列,虽然其结构都是面向硬件的,但它的限制大过它所带来的好处。

下载过程

程序1中的代码显示的是下载目标系统和运行微编程器所需要的功能。在这个例子中,目标系统从某个I/O通道(也许是串口)接收文本形式的摩托罗拉S Record文件,将它译码并写到RAM中,然后在传输结束时目标系统跳转到下载代码启动微编程器。

注意programmer_buf[]存储空间是自动调整的,也就是说它在目标系统的存储单元里没有固定的位置,这意味着新记录的地址是相对的而不是绝对的,新代码的位置是独立的。如果汇编程序不能产生位置独立的代码,那么programmer_buf[]就要在存储器中分配一个固定的位置,新记录的地址也要留在那个位置。

假如目标系统没有资源可长期分配给programmer_buf[],则新来的微编程器内容可以放在其它数据的上面。此时,系统无论如何都要中断其它操作,将RAM大部分空间留给微编程器。

基本微编程器

微型编程器的顶级代码如程序2所示,这个代码也可以从某些地方接收S Record文件并将其译码。微编程器将新接收到的数据记录在闪存中,当文件传输完成后重新启动系统。虽然过于简单了些(用文本文件来传输大的程序也许不十分可靠),但这个代码表示了微编程器的所有重要特征。

erase_flash()除了实际擦除闪存中的内容外,它还管理简单的数据结构,继续跟踪哪一段闪存中的数据需要擦除,哪一段已经被擦除。S Record文件中经常会出现数据杂乱无章的情况,这时数据结构要通过is_section_erased()来检查以防止闪存重复擦除。

只读存储器

不管如何修改采用微编程器的系统描述来适应自己系统的要求,在最后执行的时候还是会遇到一些常见的问题。

有些调试器特别是仿真器,将会与目标处理器争夺可写入代码的存储空间。多数调试工具将代码空间当作是只读的,当它们探测到要写入内容时,有些会产生错误信息或简单地不允许写入。

一般来说,调试器保护代码空间本身是好意,因为程序在自身代码空间写入数据通常会引发严重的程序错误,除非那时正在进行底层操作系统的更新。不幸的是调试器并不能够判别出它们之间的区别。补救的方法是在调试器不会认为是只读的其它存储空间里,对闪存芯片进行“化名(alias)寄存”。

常用化名寄存方法很简单,只需要将所选择激活的器件存储空间加倍,然后将指向“物理”地址区域的地址转移到指向“化名”区域就行了,进行转移的最好的场所通常是闪存芯片驱动器本身,如程序3中的假设write_flash()函数。
完整的编程器

采用微编程器方式的另一种方法是将微编程器的功能建在目标系统中,即所谓的启动加载,而不是把将它下载到RAM作为底层操作系统更新过程的第一步。这种策略有它的优点,但是在闪存芯片擦除重新编程前需要将闪存中的编程器代码复制到RAM中。换句话说,就是代码在运行的时候,要自己重新定位到RAM中的某一点上,然后才能擦除并写到闪存芯片中。这种方法同样需要涉及到的代码是位置独立的。

程序4中的程序表明如何将完整编程器的代码复制到RAM中,以及programmer()函数如何找到RAM的形式。符号RAM_PROG_START、PROG_LEN及ROM_PROG_START是RAM和ROM区域里的标记,那里存放着编程器代码(也可能是整个应用程序的一部分),通常可以用程序连接器自动计算出来。这种在entrypoint中看似复杂的计算方法迫使汇编程序在算entrypoint的值时按字节大小进行计算。

当调用程序调用relocate_programmer()返回地址处的函数时,控制权就交给微编程器代码在RAM中的拷贝,如果这时调试器还在运行,就会停止显示任何与programmer()函数有关的符号信息。为什么会这样呢?这是因为programmer()现在是从不同于以前分配的地址开始运行,因此任何通过连接器提供给调试器的符号信息都是毫无意义的。

解决这个问题的方法是将应用程序和RAM地址中的programmer()重新连接,然后将代码信息导入调试器中。这是一种很方便的纠正方法,只不过有些调试器的代码表不支持新增内容。另外一种方法就是忍受,直到programmer()调试完成,不管怎样这时理论上是不需要再看代码的了。

重新定位

不是所有嵌入式系统开发工具链都可以产生在运行时可以重定位的代码,这种依赖位置的代码需要从存放有连接的地址开始运行,否则系统有可能崩溃。

如果只有位置固定的微编程器,则它必须下载到连接器希望运行的RAM地址中。正如前面所说,它表明为微编程器而保留的内存必须安排在一个已知的位置。

采用启动加载方式时,可以有两种选择。第一个是将编程器代码作为一个单独程序进行汇编并放于它在RAM中的目标地址处,然后将这个代码映像包含在应用程序映像中(也许是通过将其二进制映像转换成固定的字符阵列),最后在开始更新底层操作系统时复制到RAM中。这个过程有点类似于微编程器的执行过程,即从目标板上的存储器下载微编程器,而不是从串口下载。

第二种是将完整的微编程器代码作为初始数据来处理,然后用运行时间环境的正常初始化程序将代码复制到RAM中。GNU汇编程序使用_attribute_语言扩展支持这种方式,还有好几种商用汇编程序也可提供这种能力。这种方法的唯一局限在于它需要足够的RAM空间来容纳全部程序代码以及程序的其它数据。

防止系统崩溃

哪怕再小心设计底层操作系统下载,也还是存在目标硬件从空白存储器进行启动的可能性。要避免这种情况出现,就要迅速激活应用程序的转换机制,但这样会使毫无准备的用户手忙脚乱。解决这一问题需要仔细研究目标处理器对非法指示及闪存中未编程部分用0xff表示的数据的反应,最好是在用户实际感受到问题前就开始研究。有些处理器最后会停止下来,并使其控制信号处于三态,悬浮于外部硬件要求的任一值上,如果此时没有上拉电阻或采取其它预防措施迫使这些不受控制的信号进入安全状态,很容易导致预料不到的死机结果。

监测计数器

在支持底层操作系统下载的系统中,一些无法避免的应用程序缺陷会使看门狗计数器停顿下来并使系统复位,从而将微编程器排除在嵌入式系统的外面。极端的情况是在程序的main()函数中意外出现的while(1)语句,产生的循环嵌套(无限程序循环外面又是无限看门狗超时及系统复位循环)使目标对升级按钮没有反应,因为在检查按钮之前系统就已经重启了。

支持底层操作系统下载的系统要仔细检查所有可能的状态,以便决定为什么系统要启动,并在探测到大量连续的看门狗信号和其它复位时迫使它转换成升级模式。很多系统在看门狗计数器终止时不会中断RAM的电源,因此可以在那里安全地存储关键数据以及复位的计算次数。计数器每复位一次时就加一,到达某一个数量时,启动代码将停止应用程序,以避免代码强制产生复位。

电源中断

如果在闪存写程序过程中电源意外中断,目标闪存芯片在电源恢复时将可能处于矛盾的状态。也许可能已完成写程序的操作,一切正常,但也可能不是这样。最好的情形是系统启动代码完整无缺,但部分应用程序代码丢失;最糟的情况是闪存芯片还是空白,什么也没写进去。

对第一种情形可以用校验和的方法检测问题所在,这时不管用户有没有要求系统都会转换到升级模式,对第二种情形唯一的解决办法就是不要让这种情况发生。

防止出现空白闪存芯片的方法就是不要删除闪存中包含的系统启动代码部分,将这些代码始终原封不动地保存起来,这样即使电源中断也可以维持足够的启动环境。然而这种方法也不是任何时候都适用。闪存芯片也许只有一个扇区,要么全部删除,要么全部保留;或者闪存芯片的“启动区域”比它包含的启动代码要大,这样会浪费存储应用代码的空间。

删除以及写入闪存芯片启动区域时小心一点可以在电源突然中断时减少系统受损的风险,有时可以完全避免。方法是这样的,当检测到需要对含启动代码的闪存区重新写入程序时,程序器首先会在另一个区域中创建一个与位置无关的代码副本(也可以设想有一个在预先位置克隆好的系统启动代码)。然后删除启动代码,同时目标复位向量迅速恢复到临时启动代码副本的位置。一旦启动区域的程序写进去之后,复位向量就重新写入指向新的启动代码位置。

成功实现这种方法要依赖两个重要因素,其中之一是应仔细选择临时和永久存放启动代码副本的地址。如果永久地址比临时地址低两级,只要数据位从1变成0临时复位向量就会转成永久复位向量,这样就可能造成闪存区域不必要的删除。使临时复位向量转成永久复位向量是一个细微的操作,不管电源何时中断,向量始终应指向有效的位置。

另外一个关键因素是应避免在删除启动区域和写入临时复位向量之间电源中断的风险。实现该目标所需能量可以从闪存芯片和微处理器规格表中的功耗计算出来,通常是在系统电源电路中增加一些电容弥补断电造成的中断,确认电源在运行时继续保持,并一直不中断直至区域删除且临时复位向量写好为止,这样可以避免可能产生的损坏。该方法的局限在于它依赖处理器启动程序,要读取复位向量得到初始程序计数器的值。复位后处理器简单地从固定地址运行代码,也可将它修改成智能操作代码的混合体以实现同样的目的。

硬件选项

采用微编程器的底层操作系统更新方法及由这种方法衍生而来的其它的方法都是以底层操作系统为基础的,因为它们都要求在代码重新编程前目标系统中已有代码存在。这就产生了一个先有鸡还是先有蛋的问题。如果在嵌入式系统中需要有代码才能将新的代码写进去,怎样才能将那些最初的代码放进去呢?

要启动这一过程现在有两种现成的硬件可选,分别是后台调试模式(BDM)和JTAG。

带有BDM端口的处理器可以提供一个基本的直接连接处理器本身内部结构的串口,通过向这个端口发送正确的指令和数据,可以在目标系统的RAM中增加微编程器的副本,并将控制权转交给它。BDM端口也可以用来激励闪存芯片的I/O线,这样就可以直接对它进行编程。很多以BDM为基础的开发系统都包括一些脚本和程序可实现这一功能,但也可以在仔细研究处理器说明书之后,通过几个芯片和电脑打印端口用人工方式来完成。

JTAG是一种设计为便于芯片I/O线进行读写的完全不同的技术,通常是在芯片的微处理器(如果有的话)保持复位状态时采用。然而与BDM相同,这种能力可以用来激励RAM或闪存把微编程器加入到里面,像BDM一样,JTAG接口也可以通过研究目标处理器说明书然后用几个元件来实现。JTAG总线收发芯片有多种型号,可从不同的供应商处得到,并加在不支持JTAG的系统中。

作者:Bill Gatliff
Email: bgat@bill-gatliff.com
返回列表