我们正在编译一个嵌入式C++应用程序,它部署在电离辐射轰击的环境中的屏蔽设备中。我们正在为ARM使用GCC和交叉编译。在部署时,我们的应用程序会生成一些错误的数据,并且比我们希望的更频繁地崩溃。硬件是为这个环境而设计的,我们的应用程序已经在这个平台上运行了几年。

我们是否可以对代码进行更改,或对编译时进行改进,以识别/纠正由单个事件扰乱导致的软错误和内存损坏?是否有其他开发人员成功地减少了软错误对长时间运行的应用程序的有害影响?


当前回答

如果你的硬件出现故障,你可以使用机械存储来恢复它。如果你的代码库很小,并且有一些物理空间,那么你可以使用一个机械数据存储。

材料表面不会受到辐射的影响。将有多个档位。机械读卡器将在所有齿轮上运行,并且可以灵活地上下移动。向下表示为0,向上表示为1。从0和1可以生成代码库。

其他回答

你需要3台以上的从机,在辐射环境外有一台主机。所有I/O都通过包含表决和/或重试机制的主机。每个从设备必须有一个硬件监视器,并且撞击它们的调用应该被CRC等包围,以降低非自愿撞击的概率。转发应该由主机控制,因此与主机的连接丢失等于几秒钟内重新启动。

此解决方案的一个优点是,您可以对主机和从机使用相同的API,因此冗余成为一种透明的特性。

编辑:从评论中,我觉得有必要澄清“CRC的想法”。如果你用CRC来围绕碰撞,或者对来自主设备的随机数据进行摘要检查,那么从设备碰撞它自己的看门狗的可能性接近于零。只有当受监视的从设备与其他设备对齐时,才从主设备发送随机数据。随机数据和CRC/摘要在每次碰撞后立即清除。主从缓冲频率应超过看门狗超时的两倍。每次从主机发送的数据都是唯一生成的。

如果你的硬件出现故障,你可以使用机械存储来恢复它。如果你的代码库很小,并且有一些物理空间,那么你可以使用一个机械数据存储。

材料表面不会受到辐射的影响。将有多个档位。机械读卡器将在所有齿轮上运行,并且可以灵活地上下移动。向下表示为0,向上表示为1。从0和1可以生成代码库。

这是一个非常广泛的主题。基本上,您无法真正从内存损坏中恢复,但至少可以尝试立即失败。以下是您可以使用的一些技巧:

校验和常量数据。如果有任何配置数据长期保持不变(包括已配置的硬件寄存器),请在初始化时计算其校验和并定期验证。当您看到不匹配时,应该重新初始化或重置。冗余存储变量。如果你有一个重要的变量x,把它的值写在x1、x2和x3中,然后读为(x1==x2)?x2:x3。实施程序流程监控。将全局标志与从主循环调用的重要函数/分支中的唯一值进行异或。在接近100%测试覆盖率的无辐射环境中运行程序,应为您提供循环结束时标志的可接受值列表。如果看到偏差,则重置。监视堆栈指针。在主循环的开头,将堆栈指针与其预期值进行比较。偏差复位。

有一点似乎没有人提到。你说你在GCC中开发,并在ARM上交叉编译。你怎么知道你的代码中没有关于空闲RAM、整数大小、指针大小、执行某个操作需要多长时间、系统将持续运行多长时间等的假设?这是一个非常普遍的问题。

答案通常是自动单元测试。编写在开发系统上执行代码的测试线束,然后在目标系统上运行相同的测试线束。寻找差异!

还要检查嵌入式设备上的勘误表。您可能会发现“不要这样做,因为它会崩溃,所以启用编译器选项,编译器会解决它”。

简而言之,崩溃的最可能来源是代码中的错误。在你确定这不是事实之前,不要担心更深奥的故障模式。

为放射性环境编写代码实际上与为任何任务关键型应用程序编写代码没有什么不同。

除了已经提到的内容外,还有一些杂项提示:

使用任何半专业嵌入式系统都应具备的日常“面包和黄油”安全措施:内部看门狗、内部低电压检测、内部时钟监视器。这些事情在2016年甚至不需要提及,它们几乎是每个现代微控制器的标准。如果您有一个面向安全和/或汽车的MCU,它将具有某些看门狗功能,例如给定的时间窗口,您需要在其中刷新看门狗。如果您有任务关键型实时系统,则首选此选项。一般来说,使用适用于这类系统的MCU,而不是在一包玉米片中收到的普通主流绒毛。现在几乎每个MCU制造商都有专门为安全应用设计的MCU(TI、Freescale、Renesas、ST、Infineon等)。它们有很多内置的安全功能,包括锁步内核:这意味着有两个CPU内核执行相同的代码,它们必须彼此一致。重要事项:您必须确保内部MCU寄存器的完整性。硬件外设的所有可写控制和状态寄存器可能位于RAM内存中,因此易受攻击。为了保护自己免受寄存器损坏,最好选择具有内置寄存器“一次写入”功能的微控制器。此外,您需要在NVM中存储所有硬件寄存器的默认值,并定期将这些值复制到寄存器中。您可以以同样的方式确保重要变量的完整性。注意:始终使用防御性编程。这意味着您必须在MCU中设置所有寄存器,而不仅仅是应用程序使用的寄存器。你不希望一些随机的硬件外设突然醒来。有各种各样的方法来检查RAM或NVM中的错误:校验和、“行走模式”、软件ECC等。现在最好的解决方案是不使用任何这些,而是使用内置ECC和类似检查的MCU。因为在软件中这样做很复杂,因此错误检查本身可能会引入错误和意外问题。使用冗余。您可以将易失性和非易失性内存存储在两个相同的“镜像”段中,这两个段必须始终相等。每个段可以附加CRC校验和。避免使用MCU外部的外部存储器。为所有可能的中断/异常实现默认中断服务例程/默认异常处理程序。即使是你不使用的。默认例程除了关闭自己的中断源之外,不应该做任何事情。理解并接受防御性编程的概念。这意味着您的程序需要处理所有可能的情况,即使是理论上无法发生的情况。示例。高质量的任务关键型固件检测到尽可能多的错误,然后以安全的方式处理或忽略它们。不要编写依赖于指定不良行为的程序。这种行为可能会因辐射或EMI引起的意外硬件变化而发生剧烈变化。确保您的程序没有此类垃圾的最佳方法是使用像MISRA这样的编码标准,以及静态分析器工具。这也有助于防御编程和消除bug(为什么您不想在任何类型的应用程序中检测bug?)。重要提示:不要依赖静态存储持续时间变量的默认值。也就是说,不要信任.data或.bss的默认内容。从初始化点到实际使用变量的点之间可能有任何时间,RAM可能有足够的时间损坏。相反,编写程序,以便在运行时从NVM中设置所有此类变量,就在首次使用此类变量之前。在实践中,这意味着如果变量在文件范围内声明或声明为静态,则永远不应该使用=来初始化它(或者可以,但这是没有意义的,因为无论如何都不能依赖于值)。始终在运行时设置,就在使用之前。如果可以从NVM中重复更新这些变量,那么就这样做。类似地,在C++中,对于静态存储持续时间变量,不要依赖构造函数。让构造函数调用公共的“设置”例程,您也可以稍后在运行时直接从调用方应用程序调用该例程。如果可能的话,请完全删除初始化.data和.bss(并调用C++构造函数)的“向下复制”启动代码,这样在编写依赖于这些的代码时就会出现链接器错误。许多编译器都可以选择跳过这一步,通常称为“最小/快速启动”或类似操作。这意味着必须检查任何外部库,以便它们不包含任何此类依赖。实现并定义程序的安全状态,以便在发生严重错误时恢复到该状态。实施错误报告/错误日志系统总是有帮助的。