提问



我们正在编译嵌入式C/C ++应用程序,该应用程序部署在受电离辐射轰击的环境中的屏蔽设备中。我们正在使用GCC和ARM进行交叉编译。部署后,我们的应用程序会生成一些错误的数据,并且比我们想要的更频繁地崩溃。硬件专为此环境而设计,我们的应用程序已在此平台上运行了数年。[41]


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

最佳参考


通过软件/固件开发和微型卫星环境测试工作约4 - 5年*,我想在此分享我的经验。[44]


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



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



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



  1. ......恢复目的...... 。提供在真实环境中更新/重新编译/刷新软件/固件的功能。对于高电离环境中的任何软件/固件,这是几乎必须的功能。如果没有这个,你可以拥有你想要的冗余软件/硬件,但是在某一点上,它们都会爆炸。所以,准备这个功能!

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



    1. 能够听取外部系统的命令,

    2. 能够更新当前的软件/固件,

    3. 能够监控基本操作的管家数据。


  3. ...复制......某处...... 在某处拥有冗余软件/固件。



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


      严格地说,没有冗余硬件,这样做的缺点是你实际上无法消除所有单点故障。至少,您仍然会有一个单点故障,即交换机本身(或通常是代码的开头)。然而,对于在高度电离环境(例如微微/毫微微卫星)中受尺寸限制的设备,单点故障减少到一点而没有额外硬件仍然值得考虑。此外,切换的代码片段肯定会比整个程序的代码少得多 - 大大降低了获取单个事件的风险。

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

    3. 您也可以将副本放在设备的永久性内存存储器中,可以触发该副本以恢复正在运行的系统的软件/固件


  4. ...可检测到的错误情况.. 错误必须可检测,通常由硬件纠错/检测电路或通过用于纠错/检测的一小段代码。最好将这些代码从主软件/固件中放入小,多个和独立的。它的主要任务是仅用于检查/纠正。如果硬件电路/固件是可靠的(例如它比放置更多的辐射硬化 - 或具有多个电路/逻辑),那么您可以考虑使用它进行纠错。但如果不是,最好将其作为错误检测。可以通过外部系统/设备进行校正。对于纠错,您可以考虑使用像Hamming/Golay23这样的基本纠错算法,因为它们可以在电路/软件中更容易实现。但它最终取决于您团队的能力。对于错误检测,通常使用CRC。

  5. ...支持恢复的硬件现在,在这个问题上遇到了最困难的方面。最终,恢复需要负责恢复的硬件至少功能。如果硬件永久性损坏(通常在总电离剂量达到一定水平后发生),那么(遗憾的是)软件无法帮助恢复。因此,对于暴露于高辐射水平的设备(例如卫星)而言,硬件是最重要的考虑因素。



除了上述因单一事件扰乱而预期固件错误的建议外,我还建议您:



  1. 子系统间通信协议中的错误检测和/或错误校正算法。这是另一个几乎必须具备的,以避免从其他系统接收的不完整/错误信号

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


其它参考1


美国宇航局有一篇关于辐射强化软件的论文。它描述了三个主要任务:[45]



  1. 定期监视内存中的错误,然后清除这些错误,

  2. 强大的错误恢复机制,

  3. 如果某些内容不再有效,则能够重新配置。



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


强大的错误恢复包括控制流传输(通常在错误之前的某个点重新启动进程),资源释放和数据恢复。


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


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



  1. 用于连续内存对象的基于软件的ECC

  2. 按合同编程:验证前提条件和后置条件,然后检查对象以验证它仍处于有效状态。


其它参考2


以下是一些想法和想法:[47]


更有创意地使用ROM。


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


将最好的RAM用于堆栈。


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


实现timer-tick和看门狗定时器例程。


您可以在每个计时器时间点运行健全性检查例程,以及用于处理系统锁定的监视程序例程。您的主代码也可以定期递增计数器以指示进度,并且完整性检查例程可以确保已经发生这种情况。


在软件中实施纠错码。 [48]


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


记住缓存。


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


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


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


使用汇编语言处理关键事物(可能是一切)。


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


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


如果您使用像Linux这样的大型操作系统,那么您就会遇到麻烦;有太多的复杂性和许多事情要出错。


请记住,这是一场概率游戏。


一位评论者说



  为了捕获错误而编写的每个例程都会因同一原因而失败。



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


使用冗余硬件。


使用2个或更多相同硬件设置和相同代码。如果结果不同,则应触发重置。使用3个或更多设备,您可以使用投票系统来尝试识别哪个设备已被入侵。

其它参考3


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


一个开始阅读的地方是Huang和Abraham的1984年论文基于算法的矩阵运算的容错。他们的想法与同态加密计算模糊地相似(但实际上并不相同,因为他们正在尝试错误检测/纠正在运作层面。)[49]


该论文的最新后裔是Bosilca,Delmas,Dongarra和Langou的基于算法的容错应用于高性能计算。[50]

其它参考4


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


除了已经提到的内容之外,还有一些其他的提示:



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

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

  • 一般来说,使用适合这类系统的MCU,而不是玉米片包装中的一般主流绒毛。现在几乎每个MCU制造商都有专门为安全应用设计的MCU(TI,Freescale,Renesas,ST,Infineon等)。它们具有许多内置的安全功能,包括锁步核心:意味着有2个CPU核心执行相同的代码,并且它们必须彼此一致。

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


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


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

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

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

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

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

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


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

  • 永远不要编写依赖于指定不当行为的程序。由于辐射或EMI引起的意外硬件变化,这种行为可能会发生巨大变化。确保您的程序没有这种废话的最佳方法是使用像MISRA这样的编码标准,以及静态分析器工具。这也有助于防御性编程和清除错误(为什么你不想在任何类型的应用程序中检测错误?)。

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


    在实践中,这意味着如果变量在文件范围或static声明,则不应使用=来初始化它(或者你可以,但它没有意义,因为你不能依赖于它无论如何)。始终在使用前将其设置为运行时。如果可以从NVM重复更新此类变量,则执行此操作。


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


    如果可能,删除完全初始化.data.bss(并调用C ++构造函数)的copy-down启动代码,这样如果编写依赖于此类的代码,就会出现链接器错误。许多编译器可以选择跳过这个,通常称为最小/快速启动或类似。


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

  • 实施并定义程序的安全状态,以便在出现严重错误时将恢复。

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


其它参考5


可以使用C编写在这种环境中表现稳健的程序,但前提是禁用大多数形式的编译器优化。优化编译器的目的是用更有效的代码来替换许多看似冗余的编码模式,并且可能不知道程序员在编译器知道无法x时测试x==42的原因。可能持有其他任何东西是因为程序员想要阻止某些代码的执行,x保留一些其他值 - 即使在它可以保持该值的唯一方法是系统收到某种类型的情况下电气故障。


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


... code that checks system state
if (system_state_favors_activation)
{
  prepare_for_activation();
  ... code that checks system state again
  if (system_state_is_valid)
  {
    if (system_state_favors_activation)
      trigger_activation();
  }
  else
    perform_safety_shutdown_and_restart();
}
cancel_preparations();


如果编译器以相对字面的方式翻译代码,如果全部翻译
prepare_for_activation()之后重复检查系统状态,
该系统可以抵抗几乎任何似乎合理的单故障事件,
甚至那些会随意破坏程序计数器和堆栈的程序。如果
在调用prepare_for_activation()之后发生了一个小故障,这意味着
激活是合适的(因为没有其他原因
prepare_for_activation()会在故障之前调用。如果
故障导致代码不恰当地达到prepare_for_activation(),但那里
没有后续的故障事件,随后的代码就没有办法了
到达trigger_activation()而没有通过验证检查或首先调用cancel_preparations [[如果堆栈出现故障,执行可能会在调用prepare_for_activation()的上下文返回后继续trigger_activation()之前的某个位置,但是在prepare_for_activation()trigger_activation()的调用之间会发生cancel_preparations()调用,从而使后者调用无害。


这样的代码在传统C中可能是安全的,但在现代C编译器中则不然。这种编译器在这种环境中可能非常危险,因为他们努力只包含与通过这种情况可能出现的情况相关的代码。我明确定义的机制,其后果也将得到明确界定。在某些情况下,其目的是在故障后检测和清理的代码最终会使事情变得更糟。如果编译器确定尝试恢复在某些情况下会调用未定义的行为,则可能会推断出在这种情况下不太可能发生需要此类恢复的条件,从而消除了将检查它们的代码。

其它参考6


这是一个非常广泛的主题。基本上,你不能真正从内存损坏中恢复,但你至少可以尝试及时失败。这里有一些你可以使用的技巧:



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

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

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

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


其它参考7


什么可以帮助你是一个看门狗。看门狗在20世纪80年代被广泛用于工业计算领域。硬件故障比较常见 - 另一个答案也指那个时期。[52]


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


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


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


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


看门狗的一个固有缺点是,在看门狗计数器达到零+重启时间之前,系统从失效时就不可用。虽然该时间通常比任何外部或人为干预短得多,但支持的设备需要能够在该时间范围内无需计算机控制。

其它参考8


这个答案假设您关注的是系统运行正常,而且系统成本最低或速度最快;大多数玩放射性物体的人都非常重视速度/成本的正确性/安全性


有几个人建议您可以进行硬件更改(很好 - 答案中已经有很多好东西,而且我不打算重复所有这些),其他人建议冗余(原则上很棒),但我不喜欢我想有人已经提出了这种冗余在实践中如何发挥作用。你如何进行故障转移?你怎么知道什么时候出了什么问题?许多技术在所有工作的基础上工作,因此失败是一件很棘手的事情。然而,一些分布式计算技术专为扩展期望失败而设计(毕竟具有足够的规模,对于单个节点,任何MTBF都不可避免地会出现多个节点的故障);您可以利用它来实现环境。


以下是一些想法:



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

  • 确保您的申请状态在一个地方,例如在有限状态机中。这可以完全基于RAM,但不排除稳定存储。因此它将存储在几个地方。

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

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

  • 尽可能多地对系统进行内部检查,使节点检测到自己的故障重启(如果你有足够的节点,这比继续工作一半要好)。尝试让他们干净的情况下,重新启动他们穿上时从仲裁自行拆除的T再次出现。在重新启动时有他们的校验软件映像(和其他任何他们加载),并重新引入自己仲裁之前做了充分的RAM测试。

  • 使用硬件为您提供支持,但要小心谨慎。例如,您可以获取ECC RAM,并定期读取/写入ECC错误以纠正ECC错误(如果错误无法纠正,则会出现紧急情况)。然而(从存储器中)静态RAM比DRAM首先更容忍电离辐射,因此可能更好地使用静态DRAM。请参阅我不会做的事情下的第一点。



假设你在一天之内有1%的机会失败,让我们假装你可以让失败完全独立。有了5个节点,你将需要在一天内失败三个,这是一个.00001%的机会。更多,好吧,你明白了。


我不做的事情:



  • 低估了没有问题的价值。除非重量是一个问题,否则设备周围的大块金属将是一个比它更便宜,更可靠的解决方案。程序员团队可以提出。 EMI的输入的同上光学耦合是一个问题,等等。无论如何,尝试采购组件以获得最佳的电离辐射。

  • 滚动您自己的算法。人们以前做过这些事。使用他们的工作。容错和分布式算法很难。在可能的情况下使用其他人的工作。

  • 在天真的情况下使用复杂的编译器设置希望您检测到更多故障。如果幸运的话,您可能会发现更多故障。更有可能的是,您将在编译器中使用经过较少测试的代码路径,特别是如果您自己进行了编译。

  • 使用在您的环境中未经测试的技术。大多数编写高可用性软件的人必须模拟故障模式以检查其HA是否正常工作,并因此错过许多故障模式。您处于需求频繁失败的幸运位置。因此,测试每种技术,并确保其应用实际上将MTBF提高了超过引入它的复杂性(复杂性带来了错误)。特别是应用于我的建议re quorum算法等。


其它参考9


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


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


这样,如果在ALU,寄存器,RAM或总线上发生损坏并不重要,您仍然会有多次尝试并且很有可能发现错误。请注意,尽管这只适用于您可以使用的变量替换 - 您的堆栈指针仍然容易受到影响。


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

其它参考10


免责声明:我不是放射性专业人员,也不是这种应用程序。但我致力于软错误和冗余,以便长期存档关键数据,这有点关联(同样的问题,不同的目标)。


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


那么问题是:当你的记忆不可靠时如何可靠地计算?


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



  • 依赖于良好的旧冗余方案,更具体地说是更有效的纠错码(同样的目的,但更聪明的算法,以便您可以用更少的资源恢复更多的位冗余)。这有时(错误地)也称为校验和。使用这种解决方案,您必须随时在主变量/类(或结构?)中存储程序的完整状态,计算ECC,并在执行任何操作之前检查ECC是否正确,如果不,修理田地。但是,此解决方案并不能保证您的软件可以正常工作(只是它可以正常工作,或者如果不能正常工作,因为ECC可以告诉您是否有问题),在这种情况下,您可以停止您的软件以便您不要得到假结果。[56] [57]

  • 或者您可以使用弹性算法数据结构,即使存在软错误,也可以保证您的程序仍能提供正确的结果。这些算法可以看作是常见的算法结构与原生混合的ECC方案的混合,但这比那更有弹性,因为弹性方案与结构紧密相关,因此您不需要编码其他程序检查ECC,通常它们更快。这些结构提供了一种方法来确保您的程序在任何条件下工作,直到软错误的理论范围。您还可以将这些弹性结构与冗余/ECC混合使用附加安全性的方案(或将最重要的数据结构编码为弹性,其余的,可以从主数据结构中重新计算的可消耗数据,作为具有一点ECC或奇偶校验的常规数据结构,非常快计算)。



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



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

  • Christiano,P.,Demaine,E。D.,& Kishore,S。(2011)。具有附加开销的无损容错数据结构。在算法和数据结构中(第243-254页)。施普林格柏林海德堡。

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

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



如果您有兴趣了解有关弹性数据结构领域的更多信息,可以查看Giuseppe F. Italiano的作品(并通过参考资料完成工作)和故障RAM模型(在Finocchi等人,2005年; Finocchi和Italiano,2008年。[59]


/编辑:我主要针对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门)。这通常在飞机和任务关键型设备中实现(参见三重模块冗余)。这样,您就不会有任何计算开销(因为额外的计算将并行完成),并且您还有另一层软错误保护(因为计算重复和多数投票将由硬件直接管理,而不是由软件 - 由于程序只是存储在内存中的位,因此更容易被破坏...)。[60]

其它参考11


你想要3个以上的奴隶机器在辐射环境之外有一个主人。所有I/O都通过包含投票和/或重试机制的主服务器。从属设备必须各有一个硬件监视器,并且应该用CRC等包围它们的呼叫,以减少无意识碰撞的可能性。 Bumping应该由master控制,因此与master失去的连接等于几秒内重启。


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


 从评论中我觉得有必要澄清CRC的想法。如果用CRC或对来自主设备的随机数据进行摘要检查来覆盖凸起,那么奴隶碰撞它自己的监视器的可能性接近于零。当被检查的从设备与其他设备对齐时,该随机数据仅从主设备发送每次碰撞后立即清除随机数据和CRC/摘要。主 - 从碰撞频率应该是看门狗超时的两倍以上。每次从主机发送的数据是唯一生成的。[61]

其它参考12


似乎没有人提到过一点。你说你正在开发GCC并交叉编译到ARM上。你怎么知道你没有代码可以假设有关空闲RAM,整数大小,指针大小,执行某个操作需要多长时间,如何系统会持续运行,还是各种类似的东西?这是一个非常普遍的问题。


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


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


简而言之,您最可能的崩溃源是代码中的错误。直到你已经非常确定这不是这种情况,不要担心(还)关于更深奥的失败模式。

其它参考13


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

其它参考14


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


正如评论中所见,不可能100%修复硬件问题,但是有可能用高概率来减少或捕获它们使用各种技术。


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

其它参考15


有人提到使用较慢的芯片来防止离子轻易地翻转位。以类似的方式,可能使用专门的cpu/ram,它实际上使用多个位来存储单个位。因此提供硬件容错,因为所有位都不太可能被翻转。所以1=1111但需要被击中4次才能实际翻转。 (4可能是一个糟糕的数字ince如果2位被翻转它已经模糊不清)。因此,如果你选择8,你可以减少8倍的ram和一些较慢的访问时间,但更可靠的数据表示。您可以在软件级别使用专门的编译器(为所有内容分配x更多空间)或语言实现(为这样分配事物的数据结构的写包装器)执行此操作。或者具有相同逻辑结构但在固件中执行此操作的专用硬件。

其它参考16


或许有助于了解硬件是否为此环境设计意味着什么。它如何纠正和/或表明存在SEU错误?


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


特别容易受到数据缓存的影响,因此处理程序会使违规缓存行无效并重新启动程序。只有这一点,由于异常的不精确性,由异常提升insn领导的insn序列可能无法重新启动。


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


需要考虑的事情......

其它参考17


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


[64]


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

其它参考18


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


类似于Watchdog概念的是截止日期计时器。在调用函数之前启动硬件计时器。如果在截止时间计时器中断之前函数未返回,则重新加载堆栈并再次尝试。如果在3/5尝试后仍然失败,则需要从ROM重新加载。


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


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

其它参考19


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


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


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

其它参考20


鉴于supercat的评论,现代编译器的倾向以及其他事情,我很想回到古代,并在整个程序集和静态内存分配中编写整个代码。对于这种完全可靠性,我认为装配不再导致成本的很大百分比差异。

其它参考21


这里有大量的回复,但我会尝试总结我对此的看法。


崩溃或无法正常工作可能是您自己的错误造成的 - 然后在找到问题时应该很容易解决。但也存在硬件故障的可能性 - 即使不是不可能整体修复也很困难。


我建议首先尝试通过记录(堆栈,寄存器,函数调用)来捕获有问题的情况 - 通过将它们记录到文件中,或者以某种方式直接传输它们(哦不 - 我崩溃)。


从这种错误情况中恢复是重启(如果软件仍处于Activity状态并且正在启动)或硬件重置(例如,hw看门狗)。从第一个开始更容易。


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


此外,如果代码相对复杂 - 划分并征服它是有意义的 - 这意味着你删除/禁用一些你怀疑问题的函数调用 - 通常禁用一半代码并启用另一半 - 你可以得到工作/不起作用的决定,之后你可以专注于另一半的代码。 (问题出在哪里)


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


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


最重要的是,如果机器完全停止/重置/不起作用,你可能会如何获得你的日志 - 这可能是第一件事就是bootstap应该做的事情 - 如果遇到问题的情况,那就是回家。


如果在您的环境中也可以传输信号并接收响应 - 您可以尝试构建某种在线远程调试环境,但是您必须至少使用通信介质并使用某些处理器/某些ram工作通过远程调试我的意思是GDB/gdb存根方法或你自己实现你需要从你的应用程序返回的东西(例如下载日志文件,下载调用栈,下载ram,重启)

其它参考22


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


这是我的2分:通过编写软件来检查内存或执行频繁的寄存器比较,建立内存/寄存器异常的统计模型。此外,创建一个虚拟机风格的模拟器,您可以在其中试验该问题。我想如果你改变结点大小,时钟频率,供应商,外壳等会发现不同的行为。


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