编译用于高放射性环境的应用程序

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

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

147706 次浏览

美国航天局有关于辐射硬化的论文软件。

  1. 定期监控内存中的错误,然后清除这些错误,
  2. 强大的错误恢复机制,以及
  3. 如果某些东西不再工作,则重新配置的能力。

请注意,内存扫描速率应该足够频繁,很少发生多位错误,因为大多数ECC内存可以从单位错误中恢复,而不是多位错误。

稳健的错误恢复包括控制流传输(通常在错误发生之前重新启动进程)、资源释放和数据恢复。

他们对数据恢复的主要建议是通过将中间数据视为临时数据来避免数据恢复的需要,以便在错误发生之前重新启动也将数据回滚到可靠状态。这听起来类似于数据库中的“事务”概念。

他们讨论了特别适用于面向对象语言(如C++)的技术

  1. 基于软件的连续内存对象ECC
  2. 合同编程:验证前置条件和后置条件,然后检查对象以验证它是否仍然处于有效状态。

而且,碰巧的是,美国航天局已经将C++用于火星探测器等重大项目。

C++类抽象和封装使多个项目和开发人员之间的快速开发和测试成为可能。

他们避免了某些可能产生问题的C++功能:

  1. 例外
  2. 模板
  3. Iostream(无控制台)
  4. 多重继承
  5. 运算符重载(newdelete除外)
  6. 动态分配(使用专用内存池和放置new以避免系统堆损坏的可能性)。

您可能还对算法容错主题的丰富文献感兴趣。这包括旧的赋值:编写一个排序,当恒定数量的比较失败时(或者,稍微更邪恶的版本,当失败比较的渐近数量缩放为log(n)n比较)正确排序其输入。

从黄和亚伯拉罕1984年的论文“基于算法的矩阵运算容错”开始阅读。他们的想法与同态加密计算有点相似(但实际上并不相同,因为他们正在尝试在操作级别进行错误检测/纠正)。

该论文最近的后代是Bosilca,Delmas,Tangarra和Langou的“基于算法的容错应用于高性能计算”。

以下是一些想法和想法:

更具创造性地使用ROM。

将任何可以存储的内容存储在ROM中。而不是计算东西,而是将查找表存储在ROM中。(确保您的编译器将查找表输出到只读部分!在运行时打印内存地址以检查!)将您的中断向量表存储在ROM中。当然,运行一些测试以查看您的ROM与您的RAM相比的可靠性。

为堆栈使用您最好的RAM。

堆栈中的SEU可能是崩溃的最有可能的来源,因为它是索引变量、状态变量、返回地址和各种类型的指针通常存在的地方。

实现计时器滴答和看门狗计时器例程。

你可以在每个计时器周期运行一个“健全性检查”例程,也可以运行一个看门狗例程来处理系统锁定。你的主代码也可以定期增加一个计数器来指示进度,健全性检查例程可以确保这已经发生。

在软件中实现纠错码。

您可以为数据添加冗余,以便能够检测和/或纠正错误。这将增加流转时长,可能使处理器暴露在辐射中的时间更长,从而增加出错的机会,因此您必须考虑权衡。

记住缓存。

检查你的CPU缓存的大小。你最近访问或修改的数据可能会在缓存中。我相信你至少可以禁用一些缓存(以很大的性能成本);你应该尝试一下,看看缓存对SEU的敏感程度。如果缓存比RAM更坚固,那么你可以定期读取和重写关键数据,以确保它保持在缓存中并使RAM恢复一致。

巧妙地使用页面错误处理程序。

如果您将内存页标记为不存在,则在您尝试访问它时,CPU将发出页面错误。您可以创建一个页面错误处理程序,该处理程序在为读取请求提供服务之前进行一些检查。(PC操作系统使用它来透明地加载已交换到磁盘的页面。)

将汇编语言用于关键事物(可能是一切)。

使用汇编语言,您可以知道寄存器中的内容和RAM中的内容;您可以知道 CPU正在使用的特殊RAM表,并且您可以以迂回的方式设计事物以降低风险。

使用objdump实际查看生成的汇编语言,并计算出每个例程占用多少代码。

如果您使用的是像Linux这样的大型操作系统,那么您就是在自找麻烦;有太多的复杂性和太多的错误。

记住,这是一个概率游戏。

一位评论者说

您为捕获错误而编写的每个例程都将因相同的原因而失败。

虽然这是真的,但检查例程正常运行所需的(例如)100字节代码和数据中的错误几率远小于其他地方的错误几率。如果您的ROM非常可靠并且几乎所有代码/数据都在ROM中,那么您的几率就更大了。

使用冗余硬件。

使用2个或更多具有相同代码的相同硬件设置。如果结果不同,应触发重置。对于3个或更多设备,您可以使用“投票”系统来尝试识别哪个设备已被破坏。

小型化卫星*的软件/固件开发和环境测试方面工作了大约4-5年,我想在这里分享我的经验。

*(小型化卫星比大型卫星更容易发生单一事件扰动,因为它的电子元件相对较小,尺寸有限

非常简洁和直接:没有从可检测的错误中恢复的机制情况由软件/固件本身没有,至少,一个复制软件/固件某处最低工作版本用于恢复目的-并与支持恢复的硬件(功能)。

现在,这种情况通常在硬件和软件级别都得到处理。在这里,根据您的要求,我将分享我们在软件级别可以做些什么。

  1. 提供在真实环境中更新/重新编译/刷新您的软件/固件的能力。这是高度电离环境中任何软件/固件几乎必备的功能。没有这个,你可以有尽可能多的冗余软件/硬件,但在某一时刻,它们都会爆炸。所以,准备这个功能吧!

  2. …最低工作版本…在您的代码中拥有响应式的、多个副本的、最低版本的软件/固件。这就像Windows中的安全模式。与其只有一个功能齐全的软件版本,不如拥有多个最低版本的软件/固件副本。最小副本通常比完整副本的大小小得多,并且几乎总是具有只有以下两到三个功能:

    1. 能够听取来自外部系统的命令,
    2. 能够更新当前的软件/固件,
    3. 能够监控基本操作的内务数据。
  3. …复制…某处…在某处有冗余的软件/固件。

    1. 在没有冗余硬件的情况下,可以尝试在ARM uC中添加冗余软件/固件。这通常是通过使用两个或多个相同的软件/固件在不同的地址来完成的,这些软件/固件在不同的地址相互发送心跳信号,但一次只有一个处于活动状态。如果已知一个或多个软件/固件没有响应,请切换到其他软件/固件。使用这种方法的好处是我们可以在错误发生后立即进行功能替换-无需与负责检测和修复错误的任何外部系统/方(在卫星情况下,通常是任务控制中心(MCC))进行任何联系。

      严格来说,在没有冗余硬件的情况下,这样做的缺点是,实际上不能消除了所有的单点故障。至少,你仍然会有一个的单点故障,也就是开关本身(或者通常是代码的开头)。尽管如此,对于受高度电离环境中尺寸限制的设备(例如Pico/feto卫星),将单点故障减少到一点没有额外硬件仍然值得考虑。有时,切换的代码肯定会比整个程序的代码少得多——大大降低了在其中获取单事件的风险。

    2. 但是,如果您没有这样做,您应该在您的外部系统中至少有一个副本,可以与设备接触并更新软件/固件(在卫星情况下,它仍然是任务控制中心)。

    3. 您也可以有副本在您的固定存储器存储在您的设备可以触发恢复正在运行的系统的软件/固件
  4. 错误必须是可检测的,通常由硬件纠错/检测电路或一小段代码来纠错/检测。这样的代码最好很小,多个,并且独立于主软件/固件。它的主要任务是仅用于检查/纠正。如果硬件电路/固件是可靠的(例如它比其余部分更抗辐射或具有多个电路/逻辑),那么你可能会考虑用它来纠错。但如果不是,最好将其作为错误检测。纠错可以通过外部系统/设备进行。对于纠错,你可以考虑使用像Hamming/Golay23这样的基本纠错算法,因为它们可以更容易地在电路/软件中实现。但这最终取决于你的团队的能力。对于错误检测,通常使用CRC。

  5. 现在,来到这个问题上最困难的方面。最终,恢复要求负责恢复的硬件至少能正常工作。如果硬件永久损坏(通常发生在其总电离剂量达到一定水平后),那么(可悲的是)软件没有办法帮助恢复。因此,对于暴露在高辐射水平下的设备(例如卫星),硬件理所当然地是最重要的问题。

除了上面的建议,预计固件的错误,由于单一事件的干扰,我还想建议你有:

  1. 子系统间通信协议中的差错检测和/或纠错算法这是为了避免从其他系统接收到的不完整/错误信号而几乎必须具备的另一种算法

  2. 过滤ADC读数。不要直接使用ADC读数。通过中值滤波器、均值滤波器或任何其他滤波器对其进行过滤-永远不要信任单个读数值。采样更多,而不是更少-合理。

使用C编写在此类环境中表现强劲的程序是可能的,但前提是大多数形式的编译器优化都被禁用。优化编译器旨在用“更有效”的编码模式替换许多看似冗余的编码模式,并且可能不知道程序员在编译器知道x不可能持有其他任何内容时测试x==42的原因是因为程序员想阻止x持有其他值的某些代码的执行——即使在系统收到某种电气故障的情况下,它可以持有该值的唯一方法是。

将变量声明为volatile通常是有帮助的,但可能不是灵丹妙药。特别重要的是,请注意安全编码通常需要危险操作有硬件联锁,需要多个步骤才能激活,并使用模式编写代码:

... code that checks system stateif (system_state_favors_activation){prepare_for_activation();... code that checks system state againif (system_state_is_valid){if (system_state_favors_activation)trigger_activation();}elseperform_safety_shutdown_and_restart();}cancel_preparations();

如果编译器以相对文字的方式翻译代码,并且如果所有系统状态的检查在prepare_for_activation()之后重复,该系统可能对几乎任何可能的单一故障事件都很强大,即使是那些会任意损坏程序计数器和堆栈的。如果调用prepare_for_activation()后发生故障,这意味着这种激活是适当的(因为没有其他原因)prepare_for_activation()将在故障之前被调用)。如果故障导致代码不适当地达到prepare_for_activation(),但有没有后续的故障事件,代码将无法随后在没有通过验证检查或先调用cancel_preparations的情况下到达trigger_activation()[如果堆栈出现故障,执行可能会在调用prepare_for_activation()的上下文返回后进入trigger_activation()之前的一个点,但是对cancel_preparations()的调用会发生在调用prepare_for_activation()trigger_activation()之间,从而使后者无害。

这样的代码在传统的C语言中可能是安全的,但在现代C语言编译器中可能不是。在这种环境中这样的编译器可能是非常危险的,因为激进的编译器努力只包含与可能通过某种定义良好的机制发生的情况相关的代码,这些情况的后果也会定义良好。目的是在故障后检测和清理的代码在某些情况下可能会让事情变得更糟。如果编译器确定尝试的恢复在某些情况下会调用未定义的行为,它可能会推断在这种情况下需要这种恢复的条件不可能发生,从而消除本应检查它们的代码。

如何运行你的应用程序的多个实例。如果崩溃是由于随机内存位更改造成的,那么你的一些应用实例很可能会通过并产生准确的结果。计算给定位翻转概率需要多少实例可能很容易(对于有统计背景的人来说),以实现你希望的最小整体错误。

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

  • 校验和常量数据。如果您有任何配置数据长时间保持不变(包括您配置的硬件寄存器),请在初始化时计算其校验和并定期验证。当你看到不匹配时,是时候重新初始化或重置了。

  • 使用冗余存储变量。如果您有一个重要变量x,请将其值写入x1x2x3并将其读取为(x1 == x2) ? x2 : x3

  • 实现程序流监控。异或一个全局标志,在主循环调用的重要函数/分支中具有唯一值。在测试覆盖率接近100%的无辐射环境中运行程序应该会在循环结束时给你标志的可接受值列表。如果看到偏差,请重置。

  • 监控堆栈指针。在主循环的开始,将堆栈指针与其期望值进行比较。在偏差时重置。

你所问的是一个相当复杂的话题——不容易回答。其他答案也可以,但它们只涵盖了你需要做的所有事情的一小部分。

从评论中可以看出,不可能100%修复硬件问题,但是可以使用各种技术来减少或捕获它们。

如果我是你,我会创建最高安全完整性等级级别的软件(SIL-4)。获取IEC 61513文档(用于核工业)并遵循它。

可以帮助你的是看门狗。看门狗在20世纪80年代广泛用于工业计算。当时硬件故障要常见得多——另一个答案也指的是那个时期。

看门狗是硬件/软件的组合。硬件是一个简单的计数器,从一个数字(比如1023)倒数到零。可以使用TTL或其他逻辑。

该软件被设计为一个例程监控所有基本系统的正确操作。如果该例程正确完成=发现计算机运行正常,它将计数器设置回1023。

整体设计是,在正常情况下,软件会阻止硬件计数器达到零。如果计数器达到零,计数器的硬件会执行其唯一任务并重置整个系统。从计数器的角度来看,零等于1024,计数器再次继续倒计时。

此看门狗可确保连接的计算机在许多故障情况下重新启动。我必须承认,我不熟悉能够在当今计算机上执行此类功能的硬件。与外部硬件的接口现在比以前复杂得多。

看门狗的一个固有缺点是,从它发生故障到看门狗计数器达到零+重新启动时间,系统是不可用的。虽然该时间通常比任何外部或人为干预都要短得多,但支持的设备需要能够在该时间范围内在没有计算机控制的情况下继续进行。

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

除了已经提到的,这里还有一些杂项提示:

  • 使用任何半专业嵌入式系统都应该存在的日常“面包和黄油”安全措施:内部看门狗、内部低压检测、内部时钟监视器。这些东西在2016年甚至不需要提及,它们几乎是每个现代微控制器的标准配置。

  • 如果您有一个安全和/或面向汽车的MCU,它将具有某些看门狗功能,例如给定的时间窗口,您需要在其中刷新看门狗。如果您有一个关键任务实时系统,这是首选。

  • 一般来说,使用适用于此类系统的MCU,而不是你在一包玉米片中收到的一些通用的主流绒毛。现在几乎每个MCU制造商都有专为安全应用设计的专用MCU(TI、飞思卡尔、瑞萨、ST、英飞凌等)。这些具有许多内置安全功能,包括锁步内核:这意味着有两个CPU内核执行相同的代码,并且它们必须相互一致。

  • 重要提示:您必须确保内部MCU寄存器的完整性。可写的硬件外设的所有控制和状态寄存器都可能位于RAM内存中,因此容易受到攻击。

    为了保护自己免受寄存器损坏,最好选择具有寄存器内置“一次写入”功能的微控制器。此外,您需要将所有硬件寄存器的默认值存储在NVM中,并定期将这些值复制到您的寄存器中。您可以以同样的方式确保重要变量的完整性。

    注意:始终使用防御性编程。这意味着您必须在MCU中设置所有寄存器,而不仅仅是应用程序使用的寄存器。您不希望一些随机的硬件外设突然唤醒。

  • 有各种各样的方法来检查RAM或NVM中的错误:校验和,“行走模式”,软件ECC等。现在最好的解决方案是不使用任何这些,而是使用具有内置ECC和类似检查的MCU。因为在软件中执行此操作很复杂,并且错误检查本身可能会引入错误和意外问题。

  • 使用冗余。您可以将易失性和非易失性内存存储在两个相同的“镜像”段中,这必须始终相等。每个段都可以附加一个CRC校验和。

  • 避免在MCU外部使用外部存储器。

  • 为所有可能的中断/异常实现默认中断服务例程/默认异常处理程序。即使是您没有使用的那些。默认例程除了关闭自己的中断源之外什么也不做。

  • 理解并接受防御性编程的概念。这意味着您的程序需要处理所有可能的情况,即使是理论上不可能发生的情况。示例

    高质量的关键任务固件检测尽可能多的错误,然后以安全的方式处理或忽略它们。

  • 永远不要写依赖于指定不好的行为的程序。这种行为很可能会随着辐射或EMI引起的意想不到的硬件变化而急剧变化。确保你的程序没有这种垃圾的最好方法是使用像MISRA这样的编码标准,以及静态分析工具。这也将有助于防御性编程和清除错误(为什么你不想检测任何类型的应用程序中的错误?)。

  • 重要提示:不要实现对静态存储持续时间变量默认值的任何依赖。也就是说,不要相信.data.bss的默认内容。从初始化点到实际使用变量之间可能有任何时间,RAM可能有足够的时间损坏。相反,编写程序使所有这些变量在运行时从NVM设置,就在第一次使用此类变量之前。

    在实践中,这意味着如果一个变量在文件范围或static被声明,你不应该使用=来初始化它(或者你可以,但这是毫无意义的,因为你无论如何都不能依赖这个值)。总是在运行时设置它,就在使用之前。如果有可能从NVM重复更新这些变量,那就这样做。

    与C++类似,不要依赖构造函数来获取静态存储持续时间变量。让构造函数调用公共“设置”例程,您也可以稍后在运行时直接从调用者应用程序调用该例程。

    如果可能,请完全删除初始化.data.bss的“向下复制”启动代码(并调用C++构造函数),这样如果你编写依赖于此的代码,你会得到链接器错误。许多编译器可以选择跳过这一点,通常称为“最小/快速启动”或类似的。

    这意味着必须检查任何外部库,以便它们不包含任何此类依赖。

  • 实现并定义程序的安全状态,在发生严重错误时将恢复到该状态。

  • 实现错误报告/错误日志系统总是有帮助的。

你想要3+台在辐射环境之外有一个master的从机。所有的I/O都通过包含投票和/或重试机制的master。每个从机必须有一个硬件看门狗,并且碰撞它们的调用应该被CRC等包围,以减少非自愿碰撞的可能性。碰撞应该由master控制,因此与master的丢失连接等于在几秒钟内重新启动。

这种解决方案的一个优点是,您可以对主服务器和从服务器使用相同的API,因此冗余成为一种透明的功能。

编辑:从评论中,我觉得有必要澄清“CRC思想”。如果你用CRC或摘要检查来自主服务器的随机数据,从服务器碰撞它自己的看门狗的可能性接近于零。这些随机数据只有在被审查的从服务器与其他从服务器对齐时才从主服务器发送。随机数据和CRC/摘要在每次碰撞后立即被清除。主从碰撞频率应该超过看门狗超时的双倍。每次从主服务器发送的数据都是唯一生成的。

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

在此输入图片描述

将有一个不受辐射影响的材料表面。将有多个齿轮。机械阅读器将在所有齿轮上运行,并且可以灵活地上下移动。向下意味着它是0,向上意味着它是1。从0和1您可以生成您的代码库。

既然您特别要求软件解决方案,并且您正在使用C++,为什么不使用运算符重载来创建您自己的安全数据类型呢?例如:

不要使用uint32_t(和doubleint64_t等),而是制作自己的SAFE_uint32_t,其中包含uint32_t的倍数(至少为3)。重载您希望 (* + - / &ls;<; >> = == ! = 等)执行的所有操作,并使重载的操作对每个内部值独立执行,即不要执行一次并复制结果。前后检查所有内部值是否匹配。如果值不匹配,您可以将错误的值更新为最常见的值。如果没有最常见的值,您可以安全地通知有错误。

这样,如果ALU、寄存器、RAM或总线中发生损坏并不重要,您仍然会有多次尝试并很有可能捕获错误。然而请注意,这仅适用于您可以替换的变量-例如您的堆栈指针仍然容易受到影响。

一个侧面故事:我遇到了一个类似的问题,也是在一个旧的ARM芯片上。结果是一个工具链,它使用了旧版本的GCC,连同我们使用的特定芯片,在某些边缘情况下触发了bug,这会(有时)破坏传递给函数的值。在将其归咎于无线电活动之前,确保你的设备没有任何问题,是的,有时它是一个编译器bug=)

这个答案假设你关心的是拥有一个正常工作的系统,而不仅仅是拥有一个成本最低或速度最快的系统;大多数玩放射性东西的人都看重正确性/安全性而不是速度/成本

有几个人建议你可以进行硬件更改(好吧,这里的答案已经有很多好东西了,我不打算重复所有的),还有人建议冗余(原则上很好),但我认为没有人建议冗余在实践中如何工作。你如何故障转移?你如何知道什么时候“出错了”?许多技术都是在一切正常的基础上工作的,因此失败是一件棘手的事情。然而,一些分布式计算技术专为规模期望故障而设计(毕竟有足够的规模,对于单个节点的任何MTBF,许多节点中的一个节点的故障是不可避免的);您可以为您的环境利用它。

以下是一些想法:

  • 确保整个硬件被复制n次(其中n大于2,最好是奇数),并且每个硬件元件都可以与其他硬件元件进行通信。以太网是一种显而易见的方法,但还有许多其他更简单的路由可以提供更好的保护(例如CAN)。最小化公共组件(甚至电源)。这可能意味着在多个地方采样ADC输入。

  • 确保您的应用程序状态位于单个位置,例如在有限状态机中。这可以完全基于RAM,但不排除稳定存储。因此,它将存储在多个位置。

  • 采用仲裁协议来更改状态。参见RAFT例如。当你在C++工作时,有一些众所周知的库。只有当大多数节点同意时,才会对FSM进行更改。为协议栈和仲裁协议使用一个已知的好库,而不是自己滚动一个,否则当仲裁协议挂断时,你所有关于冗余的好工作都将被浪费。

  • 确保你的FSM校验和(例如CRC/SHA),并将CRC/SHA存储在FSM本身中(以及在消息中传输,并校验消息本身)。让节点定期根据这些校验和检查它们的FSM,校验和传入消息,并检查它们的校验和是否与仲裁的校验和匹配。

  • 在系统中构建尽可能多的其他内部检查,让检测到自己失败的节点重新启动(如果你有足够的节点,这比继续工作一半要好)。在重新启动期间,尝试让它们完全从仲裁中删除,以防它们不再出现。重新启动时,让它们对软件映像(以及它们加载的任何其他内容)进行校验,并在重新将自己引入仲裁之前进行完整的RAM测试。

  • 使用硬件来支持你,但要小心。例如,你可以获得ECC RAM,并定期读取/写入它以纠正ECC错误(如果错误无法纠正,则会恐慌)。然而(从内存来看)静态RAM比DRAM首先更能容忍电离辐射,所以使用静态DRAM可能更好。也参见“我不会做的事情”下的第一点。

假设任何给定节点在一天内失败的几率为1%,假设你可以使失败完全独立。有5个节点,你需要3个节点在一天内失败,这是0.00001%的几率。更多,好吧,你明白了。

没有应该做的事情:

  • 低估了没有问题开始的价值。除非重量是一个问题,否则设备周围的一大块金属将是一个比程序员团队能想出的更便宜、更可靠的解决方案。同样,EMI输入的光耦合是一个问题,等等。无论如何,当采购您的组件时,尝试采购那些最适合抵抗电离辐射的组件。

  • 推出自己的算法。人们以前做过这些东西。使用他们的工作。容错和分布式算法很难。尽可能使用其他人的工作。

  • 使用复杂的编译器设置,天真地希望你检测到更多的故障。如果你幸运的话,你可能会检测到更多的故障。更有可能的是,你将在编译器中使用测试较少的代码路径,特别是如果你自己滚动它。

  • 使用在你的环境中未经测试的技术。大多数编写高可用性软件的人不得不模拟故障模式来检查他们的HA是否正确工作,并因此错过许多故障模式。你处于经常按需故障的幸运位置。因此测试每种技术,并确保其应用实际提高MTBF的数量超过引入它的复杂性(复杂性带来错误)。特别是将这一点应用于我的建议重新仲裁算法等。

有一点似乎没有人提到。你说你正在GCC中开发并在ARM上交叉编译。你怎么知道你没有代码来假设空闲RAM、整数大小、指针大小、执行某个操作需要多长时间、系统将连续运行多长时间或类似的各种东西?这是一个非常常见的问题。

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

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

简而言之,您最有可能的崩溃来源是代码中的错误。在您非常确定情况并非如此之前,不要担心(还)更深奥的故障模式。

考虑到Supercat的评论、现代编译器的趋势和其他因素,我很想回到古代,用汇编和到处的静态内存分配编写整个代码。对于这种完全可靠的情况,我认为汇编不再会产生很大的成本百分比差异。

有人提到使用较慢的芯片来防止离子轻松翻转位。以类似的方式,也许使用专门的cpu/ram,它实际上使用多个位来存储一个位。因此提供了硬件容错,因为所有位都被翻转的可能性很小。所以1=1111,但需要被命中4次才能真正翻转。(4可能是一个糟糕的数字,因为如果2位被翻转,它已经不明确了)。所以如果你用8,你得到的内存减少了8倍,访问时间也慢了一小部分,但数据表示更可靠。您可能可以在软件级别使用专门的编译器(为所有内容分配更多的空间)或语言实现(为以这种方式分配事物的数据结构编写包装器)来做到这一点。或者具有相同逻辑结构但在固件中执行此操作的专用硬件。

这里有大量的回复,但我会试着总结一下我的想法。

崩溃或无法正常工作可能是由于你自己的错误造成的——那么当你找到问题时,应该很容易修复。但也有硬件故障的可能性——这很难,如果不是不可能的话。

我建议首先尝试通过日志记录(堆栈、寄存器、函数调用)来捕获有问题的情况——要么将它们记录到文件的某个地方,要么以某种方式直接传输它们(“哦,不——我崩溃了”)。

从这种错误情况中恢复是重新启动(如果软件仍然活跃和踢)或硬件重置(例如hw看门狗)。从第一个开始更容易。

如果问题与硬件有关-那么日志记录应该可以帮助您确定发生在哪个函数调用问题中,并且可以让您了解哪些不起作用以及在哪里不起作用。

此外,如果代码相对复杂——“分而治之”是有意义的——这意味着你删除/禁用一些你怀疑有问题的函数调用——通常禁用一半代码并启用另一半——你可以得到“工作”/“不工作”的决定,之后你可以专注于另一半代码。

如果问题在一段时间后发生-那么可以怀疑堆栈溢出-那么最好监控堆栈点寄存器-如果它们不断增长。

如果你设法完全最小化你的代码,直到“hello world”类型的应用程序-并且它仍然随机失败-那么硬件问题是预期的-并且需要“硬件升级”-这意味着发明这样的cpu/ram/…-硬件组合可以更好地容忍辐射。

最重要的事情可能是,如果机器完全停止/重置/不起作用,你如何取回日志——可能是bootstap应该做的第一件事——如果有问题的情况出现,你应该回家。

如果在您的环境中也可以传输信号并接收响应-您可以尝试构建某种在线远程调试环境,但您必须至少有通信媒体在工作并且一些处理器/一些内存处于工作状态。通过远程调试,我的意思是GDB/gdb存根类型的方法或您自己的实现,您需要从应用程序中获得什么(例如下载日志文件、下载调用堆栈、下载ram、重新启动)

也许知道这是否意味着硬件是“为这种环境设计的”会有所帮助。它如何纠正和/或指示SEU错误的存在?

在一个与太空探索相关的项目中,我们有一个定制的MCU,它会在SEU错误时引发异常/中断,但有一些延迟,即在导致SEU异常的一个INSN之后可能会执行一些周期/指令。

特别容易受到攻击的是数据高速缓存,因此处理程序会使违规的缓存行无效并重新启动程序。只是,由于异常的不精确性质,由异常引发的insn领导的insn序列可能无法重新启动。

我们确定了危险(不可重新启动)序列(如lw $3, 0x0($2),后跟一个insn,它修改$2并且不依赖于$3),我对GCC进行了修改,因此不会发生这样的序列(例如,作为最后的手段,将两个insns分开nop)。

只是需要考虑一下…

免责声明:我不是放射性专业人士,也不是为这种应用工作。但我致力于关键数据长期存档的软错误和冗余,这在某种程度上是相互关联的(相同的问题,不同的目标)。

在我看来,放射性的主要问题是放射性可以切换位,因此放射性可以/将会篡改任何数字存储器。这些错误通常被称为软错误,位腐烂等。

问题是:当你的内存不可靠时,如何可靠地计算?

为了显著降低软错误率(以计算开销为代价,因为它主要是基于软件的解决方案),您可以:

如果您对弹性数据结构(这是算法和冗余工程中最近但令人兴奋的新领域)感兴趣,我建议您阅读以下文档:

  • 弹性算法数据结构由Giuseppe F. Italiano介绍,罗马大学"Tor Vergata"

  • 克里斯蒂亚诺,P.,德梅因,E. D.和基肖尔,S.(2011)。具有附加开销的无损容错数据结构。在算法和数据结构中(第243-254页)。斯普林格柏林海德堡。

  • Ferraro-Petrillo, U., Granoni, F.,&Italiano, G. F.(2013)。恢复内存故障的数据结构:字典的实验研究。实验算法杂志(JEA),18,1-6。

  • Italiano,G. F.(2010)。弹性算法和数据结构。在算法和复杂性中(第13-24页)。施普林格柏林海德堡。

如果您有兴趣了解有关弹性数据结构领域的更多信息,您可以查看Giuseppe F. Italiano(并通过引用进行工作)和故障RAM模型(在Finocchi et al.2005中引入;Finocchi和Italiano 2008)。

/EDIT:我主要针对RAM内存和数据存储说明了软错误的预防/恢复,但我没有谈论计算(CPU)错误。其他答案已经指出在数据库中使用原子事务,所以我将提出另一个更简单的方案:裁员和多数票

这个想法是,你只需为需要做的每个计算做x次相同的计算,并将结果存储在x个不同的变量中(x>=3)。然后你可以比较你的x变量

  • 如果他们都同意,那么根本没有计算错误。
  • 如果他们不同意,那么您可以使用多数票来获得正确的值,并且由于这意味着计算部分损坏,您还可以触发系统/程序状态扫描以检查其余部分是否正常。
  • 如果多数投票无法确定获胜者(所有x值都不同),那么这是触发故障安全程序(重新启动、向用户发出警报等)的完美信号。

与ECC相比,这种冗余方案是非常快(实际上是O(1)),当你需要故障保险时,它会为你提供明确信号。大多数投票也是(几乎)保证永远不会产生损坏的输出从小的计算错误中恢复,因为x计算给出相同输出的概率是无穷小的(因为有大量可能的输出,几乎不可能随机得到3倍相同的输出,如果x>3,机会就更少了)。

因此,使用多数票,您可以安全地避免损坏的输出,并且使用冗余x==3,您可以恢复1个错误(使用x==4,可以恢复2个错误,等等-确切的方程是nb_error_recoverable == (x-2),其中x是计算重复次数,因为您需要至少2个同意的计算才能使用多数票恢复)。

缺点是你需要计算x次而不是一次,所以你有额外的计算成本,但线性复杂性如此渐近,你不会因为获得的好处而损失太多。进行多数投票的快速方法是计算数组上的模式,但你也可以使用中值滤波器。

此外,如果您想额外确保计算正确进行,如果您可以制作自己的硬件,您可以使用x个CPU构建设备,并连接系统,以便计算在x个CPU上自动复制,并在最后机械地完成多数票(例如使用AND/OR门)。这通常在飞机和关键任务设备中实现(参见三重模块冗余)。这样,您就不会有任何计算开销(因为额外的计算将并行完成),并且您有另一层防止软错误的保护(因为计算重复和多数投票将直接由硬件而不是软件管理-这可能更容易损坏,因为程序只是存储在内存中的位…)。

首先,围绕失败设计您的应用程序。确保作为正常流程操作的一部分,它期望重置(取决于您的应用程序和软或硬故障类型)。这很难做到完美:需要一定程度事务的关键操作可能需要在程序集级别进行检查和调整,以便关键点的中断不会导致不一致的外部命令。一旦检测到任何无法恢复内存损坏或控制流偏差,就会快速失败。如果可能的话,记录失败。

其次,在可能的情况下,纠正腐败并继续。这意味着经常校验和和修复常量表(如果可以的话还有程序代码);也许在每个主要操作之前或在定时中断上,并将变量存储在自动更正的结构中(再次在每个主要操作之前或在定时中断上,从3中获得多数票,如果是单个偏差,则进行更正)。如果可能的话,进行日志更正。

第三,测试失败。设置一个可重复的测试环境,随机翻转内存中的位。这将允许您复制损坏情况并帮助围绕它们设计您的应用程序。

使用循环调度器。这使您能够添加定期维护时间来检查关键数据的正确性。最常遇到的问题是堆栈损坏。如果您的软件是周期性的,您可以在周期之间重新初始化堆栈。不要重复使用堆栈进行中断调用,请为每个重要的中断调用设置一个单独的堆栈。

与看门狗概念类似的是截止时间计时器。在调用函数之前启动硬件计时器。如果该函数在截止时间计时器中断之前没有返回,则重新加载堆栈并重试。如果在3/5次尝试后仍失败,则需要从ROM重新加载。

将您的软件拆分为多个部分并隔离这些部分以使用单独的内存区域和执行时间(尤其是在控制环境中)。示例:信号采集、预处理数据、主算法和结果实现/传输。这意味着一部分的故障不会导致程序的其余部分故障。因此,当我们修复信号采集时,其余的任务继续处理陈旧数据。

一切都需要CRC。如果你在RAM外执行,甚至你的. text也需要CRC。如果你使用循环调度程序,请定期检查CRC。一些编译器(不是GCC)可以为每个部分生成CRC,一些处理器有专用硬件来进行CRC计算,但我想这超出了你问题的范围。检查CRC还会提示内存上的ECC控制器在出现问题之前修复单位错误。

使用看门狗进行引导,而不仅仅是一次操作。如果您的引导遇到麻烦,您需要硬件帮助。

我真的读过很多很棒的答案!

这是我的2分:通过编写一个软件来检查内存或执行频繁的寄存器比较来构建内存/寄存器异常的概率模型。此外,创建一个虚拟机风格的模拟器,在那里你可以实验这个问题。我想如果你改变结大小、时钟频率、供应商、外壳等,会观察到不同的行为。

即使我们的台式电脑内存也有一定的故障率,但这并不影响日常工作。