嵌入式系统中实现安全软件升级的技术有哪些?

9

升级嵌入式设备软件时常常会出现“变砖”的可能性,例如在写入FLASH时发生了电源故障。两个问题:

  1. 实施升级机制的最佳实践是什么,以最小化设备“变砖”的概率?
  2. 使升级过程具有失效安全性的最佳实践是什么,以便可以从安装软件到FLASH期间发生的断电等事件中恢复?
9个回答

11

这完全取决于应用的重要性。有两种基本方法(备份和引导程序),有时也会将它们结合起来。

许多系统具有只读引导程序(例如redboot),然后是两个闪存存储器块(通常在同一芯片上)。然后,引导程序有一个标志来选择从哪个存储器块启动。该标志会根据升级(失败或成功)等事件而更改。

因此,在升级时,运行版本会将新负载复制到备份存储器块中,检查校验和,切换引导标志,然后重新启动设备。设备将在新存储器块上重新启动,使用新的负载。重新启动后,新的负载可以将自己复制到备份存储器块中。

通常还有一个硬件复位的看门狗定时器。这样,如果固件失控,则无法启动看门狗,硬件复位将重新启动设备,引导程序将寻找一个稳定的负载。

Open Mesh项目是此方法的一个很好的例子。


只是好奇 - 如果需要升级引导加载程序怎么办? - sybreon
@sybreon:应用程序和引导加载程序反转角色,使用类似的策略。换句话说,应用程序代码可以包含下载新引导加载程序的能力,并保留两个引导加载程序的空间。一旦下载并验证了新的引导加载程序,就会进行最终写入(很可能是重置向量或某些不可变的启动代码),以确保在启动时调用新的引导加载程序。 - Steve Melnikoff
1
一般来说,引导程序非常简单,只执行基本操作,因此无需升级。 - mouviciel

4
更具体地说...
将替换图像下载到内存中的某个区域,而不覆盖当前的任何程序空间。等待下载完成,然后计算并比较CRC。
如果空间真的是一个问题,你可以做'默认备份'或称为'recovery mode'的事情,但最好不要破坏性地这样做。
如果你非常聪明... 你可以进行一次单写更新到FLASH,以指示设备从新代码位置启动。这将在两个完全分离的代码部分之间来回切换。这是你可以做到的最安全的方法:
- 始终有一个不可更新的恢复引导加载程序(Nano-loader),如果出现问题,可以通过某种方式发出信号来加载新代码。 - 两个不同的程序空间 - 每个程序空间都有一个"CRC"字段、一个"burn number"(高于另一个代码页的数字)和一个"invalid"单词(全部为Fs-不需要擦除以更新"invalid"标记) - 下载完成后,验证CRC。如果它是好的,在旧版本的程序空间上烧录'invalid'标记。 - Nano-loader检查'invalid'标记以知道要引导哪一个。如果它们都有效,则进行CRC检查。如果它们仍然都有效,则采用更高的burn number条目。
哦,当人们说校验和时... 不要'检查和'... 要做一个正确的CRC。

2

在内部闪存上使用校验和,如果CRC/校验和无法正常工作,则默认备份。这样,如果设备出现错误的校验和,它就知道升级未完成,并可以重置到另一设备上存储的默认/先前的固件。

这需要一些预启动(可能在引导加载程序中)来检查校验和。一个静态的代码位。

编辑:进一步评论。如果您想检查错误的固件,而不仅仅是损坏的固件,则您的校验和/检查数据也可以封装版本信息(以及该标头的检查)。我认为Linksys路由器就是这样做的,这可能使它们难以使用自定义固件重新刷写。


2

校验和很好,但只能保护您免受闪存损坏的数据。如果您闪存了一个具有有效校验和但适用于不同产品型号的图像文件呢?在紧急情况下可以访问的只读默认引导加载程序是我见过的最好的东西。


1
你可以将模型信息添加到图像中(这将作为校验和的一部分进行验证),引导加载程序会检查并标记具有错误模型的图像为无效图像,并从备份中恢复。 - Aaron Maenpaa
校验和/检查数据可以封装版本数据。我认为Linksys WRT*路由器会这样做。 - Aiden Bell
使用必须首先检查的头文件;请参见本页面其他地方的答案。 - Steve Melnikoff
加载固件(而不是引导程序)后,检查产品并进行自检或其他操作。完成后,在图像中设置一个标志以表示启动成功。重置或看门狗后,引导程序检查该位是否设置,如果没有,则使用旧的备份图像。 - Tom Leys

2
  1. 无论如何都要在内存中保留只读引导程序。
  2. 在引导程序中,允许一种故障安全的方法(例如通过在重新启动期间按住X按钮)从可用的输入源(SD卡、RS232等)重新加载新的程序内存。

你的方法只有在能够派遣技术人员到每个设备的情况下才可行(即不适用于传感器网格网络)。 - Tom Leys

2
为了回答这两个问题,无论硬件资源如何:
  • 确保在运行任何应用程序代码之前(在启动时或下载完成后),引导加载程序对应用程序进行CRC检查。如果不合法,则引导加载程序不会运行该代码。

  • 如果引导加载程序决定无法运行应用程序代码,则必须具有向用户发出信号并重新开始下载的能力。

如果处理器的闪存不足以存储备份应用程序,或者RAM不足以存储新应用程序直到下载完成,则这些显然变得更加重要。

在这些情况下,下载的文件最好具有一个小标题,使得引导加载程序可以确定该文件是否适用于该系统。此标题也可以具有CRC。如果此系统的标题有效且CRC正确,则引导加载程序可以擦除闪存(但不包括自身!)并继续下载。否则,它将中止而不触及现有的应用程序代码。


2
根据我的经验,这取决于您的成本点。如果您能够负担得起将设备上的代码/数据空间增加一倍并重新启动,则简单易行:将新版本存储在所有额外空间中,并对新镜像进行适当的校验和检查。我还建议对新固件映像进行更深入的检查,因为如果不这样做,可能会出现欺骗性的情况,例如某种加密密钥保证新映像的来源。您也可以使用外部内存完成此操作。例如,在使用PIC的一个项目中,我使用了一个外部EEPROM来存储新的固件映像,并设置了一个标志,如果在启动时设置,则从EEPROM加载新映像。
如果您没有那么幸运,即无法负担那些空间,那么情况就更有趣了。在这种情况下,很可能没有完全避免更新期间失败的绝对方法。在所有情况下,引导加载程序应该位于受写保护的内存区域中,并且如果您有足够的空间,则应具有某种基本形式的外部连接性。我曾经为引导加载程序编写过非常简单的USB驱动程序,并为系统提供了只有UDP网络协议栈的驱动程序。两者都可以让您至少将新映像传输到设备上,以防更新期间出现故障。在这些情况下,强烈建议将引导加载程序放在只读内存区域中,虽然您失去了更新它的能力,但是脱轨更新也不会使您拥有一个完全损坏的设备。在这种情况下,引导加载程序足够小,可以保证其正确性。
最后一种可能性是在运行时需要更新某些代码的系统...这真的很棘手,通常需要对内存中函数的位置进行复杂控制,并且需要提前规划内存布局才能实现。虽然不那么愉快,但仍然是可行的。

+1 - 很好的解释为什么备份和恢复方案并不总是奏效。我已经在一项部署方案中使用了相同的离板EEPROM想法。 - Tom Leys

1

我知道这个问题已经有答案了,但有些人需要更可靠的解决方案。如果你的项目确实是使命关键的,你可以选择这种方法。

基本计划是始终拥有一个不能失败的备份计划。

  1. 使用 PIC 或其他可以编程真正处理器闪存的微控制器。您可以让它在一块数据上使用校验和,并通过串行、USB 甚至以太网接口进行交互(不要笑,这并不难)。这个设备在现场(甚至可能永远)无法重新编程,因此您始终有一个备份计划。PIC 可以在 PPP/SLIP 或以太网上运行 Web 服务器,因此与它的接口并不一定很麻烦。搜索 TCP-Lean。尽量不要嘲笑。(该网站适合工作场所)。将编程端口放在其他地方。安全性不能得到保证。

  2. 程序在主 CPU 上运行,并运行自己的引导加载程序。您有三个程序:引导加载程序、维护程序和真正的程序。

这允许升级引导加载程序过程以及程序。您可以在一些额外的闪存中运行备份引导加载程序,带有看门狗来重置您并使用备份,如果您无法启动。

所以你有可升级的嵌入式应用程序、可升级的引导加载程序和可升级的维护模式。还有一个备份模式,不能失败。

希望没有人需要发现这很有用。


0

在一些实验室设备(而不是消费者设备)上,我看到了使用程序员电路构建的板子。在最糟糕的情况下,您可以打开外壳,插入程序员并重新加载默认软件 - 或将其送回供您执行相同操作。当然,这需要真正的金钱。

在我的一些项目中使用的一些自定义板上有可替换的ROM。这更便宜,但不太方便。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接