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

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

我们可以对代码进行修改,还是可以通过编译时的改进来识别/纠正由于单个事件造成的软错误和内存损坏? 有没有其他开发者成功地减less了软件错误对长时间运行的应用程序的有害影响?

在微型卫星软件/固件开发和环境testing方面工作了大约4-5年,我想在此分享我的经验。

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

要非常简洁直接:没有任何机制可以从软件/固件本身恢复可检测到的错误情况而不需要至less一个软件/固件的最低工作版本的一个副本用于恢复目的 – 以及硬件支持恢复 (function)。

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

  1. 恢复的目的…。 提供在实际环境中更新/重新编译/刷新软件/固件的function。 对于高度离子化的环境中的任何软件/固件来说,这几乎是必须具备的function。 没有这个,你可以多余的软件/硬件尽可能多,但是在某一点上,它们都将被炸毁。 所以,准备这个function!

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

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

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

      严格地说,没有多余的硬件,这样做的缺点是你实际上不能消除所有的单点故障。 至less,你将仍然有一个单一的故障点,这是交换机本身 (或者通常是代码的开始)。 尽pipe如此,对于在高度离子化环境下(如微微/毫微微卫星)尺寸受限的设备而言,将单点故障减less到一点而无需额外的硬件仍然值得考虑。 更重要的是,切换的代码肯定会比整个程序的代码less得多 – 大大降低了获取单一事件的风险。

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

    3. 您也可以在您的设备中的永久性存储器中存储该副本,以便恢复正在运行的系统的软件/固件
  4. …可检测到的错误情况。错误必须是可检测的 ,通常由硬件错误校正/检测电路或由用于错误校正/检测的一小段代码来检测。 最好把这些代码放在小的,多个的, 独立于主软件/固件的地方。 它的主要任务只是检查/纠正。 如果硬件电路/固件是可靠的 (例如它比其他部件辐射更强硬 – 或者有多个电路/逻辑),那么你可以考虑对其进行纠错。 但是如果不是的话,最好把它作为错误检测。 更正可以通过外部系统/设备。 对于纠错,可以考虑使用像Hamming / Golay23这样的基本纠错algorithm,因为它们可以在电路/软件中更容易地实现。 但最终取决于你的团队的能力。 对于错误检测,通常使用CRC。

  5. …支持恢复的硬件现在,在这个问题上遇到了最困难的方面。 最终,恢复要求负责恢复的硬件至less起作用。 如果硬件永久断开(通常在其总电离剂量达到一定水平后发生),则(可惜)没有办法使软件帮助恢复。 因此,对于暴露于高辐射水平的设备(如卫星)而言,硬件正是最重要的考虑因素。

除了上面提到的由于单个事件不正常而预测固件错误的build议之外,我还想build议你有:

  1. 子系统间通信协议中的错误检测和/或纠错algorithm。 这是另一个几乎必须有的,以避免从其他系统收到的不完整/错误的信号

  2. 过滤ADC读数。 不要直接使用ADC读数。 通过中值filter,均值filter或任何其他filter进行过滤 – 不要相信单个读数值。 多采样,不能less采样 – 合理。

美国航空航天局有一份关于抗辐射软件的论文。 它描述了三个主要任务:

  1. 定期监视内存的错误,然后清除这些错误,
  2. 强大的错误恢复机制,以及
  3. 如果事情不再有效,重新configuration的能力。

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

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

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

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

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

这里有一些想法和想法:

更有创意地使用ROM。

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

使用你的最好的RAM的堆栈。

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

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

你可以运行一个“健全检查”例程每个计时器滴答,以及一个看门狗例程来处理系统locking。 您的主代码也可以定期增加一个计数器来指示进度,并且完整性检查程序可以确保这一点已经发生。

用软件实现纠错码 。

您可以添加冗余到您的数据,以检测和/或纠正错误。 这会增加处理时间,可能使处理器暴露于辐射下更长时间,从而增加出错的机会,所以您必须考虑权衡。

记住caching。

检查CPU高速caching的大小。 您最近访问或修改的数据可能在caching中。 我相信你可以禁用至less一些caching(性能很高); 你应该试试看看这些caching对SEU是多么的敏感。 如果caching比RAM更硬,那么您可以定期读取和重新写入关键数据,以确保它保持在caching中,并使RAM恢复正常。

巧妙地使用页面error handling程序。

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

使用汇编语言来处理关键的事情(这可能是一切)。

使用汇编语言,您可以知道寄存器中的内容和RAM中的内容; 你知道 CPU使用的是什么特殊的RAM表,你可以用一个迂回的方式来devise事物,以降低风险。

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

如果你使用的是像Linux这样的大型操作系统,那么你是在寻求麻烦; 有太多的复杂性和很多事情要出错。

记住它是一个概率的游戏。

一位评论者说

你为避免错误而编写的每一个例程都会受到同样原因的影响。

在这种情况下,检查程序正常工作所需的100字节的代码和数据出错的机会远远小于其他地方出现错误的机会。 如果你的ROM非常可靠,而且几乎所有的代码/数据都在ROM中,那么你的可能性就更大了。

使用冗余硬件。

使用两个或多个相同的代码完全相同的硬件设置。 如果结果不同,应该触发复位。 使用3台或更多设备,您可以使用“投票”系统来确定哪个设备已被入侵。

您也可能对algorithm容错这一主题的丰富文献感兴趣。 这包括旧的赋值:当一个常量的比较失败时,编写一个能够正确sortinginput的sorting(或者,当n比较失败的比较的渐近数比例为log(n)时,稍微恶化的版本)。

开始阅读的地方是Huang和Abraham的1984年论文“ 基于algorithm的matrix运算容错 ”。 他们的想法与同态encryption计算有些相似(但是它并不是真的一样,因为他们试图在操作级别进行错误检测/纠正)。

该论文的最新后裔是Bosilca,Delmas,Dongarra和Langou的“ 基于algorithm的容错应用于高性能计算 ”。

编写用于放射性环境的代码与为任何关键任务应用程序编写代码没有任何区别。

除了已经提到的以外,这里还有一些其他的提示:

  • 内部看门狗,内部低电压检测,内部时钟监视器,使用每日“面包和黄油”安全措施,应该在任何半专业embedded式系统上出现。 这些东西甚至不需要在2016年提及,而且几乎每一个现代微控制器都是标准的。
  • 如果您有一个安全和/或面向汽车的MCU,它将具有某些看门狗function,例如给定的时间窗口,在其中您需要刷新看门狗。 如果你有一个关键任务的实时系统,这是首选。
  • 一般来说,使用适合这些系统的MCU,而不是一些普通的主stream绒毛,你收到一包玉米片。 现在几乎所有的MCU制造商都有专门为安全应用而devise的MCU(TI,Freescale,Renesas,ST,Infineon等)。 这些内置的安全function很多,包括锁步内核:这意味着有两个CPU内核执行相同的代码,并且必须相互协调。
  • 重要:您必须确保内部MCU寄存器的完整性。 可写的硬件外设的所有控制和状态寄存器可能位于RAM存储器中,因此易受攻击。

    为了防止寄存器损坏,最好select一个带有内置寄存器“一次写入”function的微控制器。 另外,您需要将所有硬件寄存器的默认值存储在NVM中,并定期将这些值复制到寄存器中。 您可以用同样的方式确保重要variables的完整性。

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

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

  • 使用冗余。 您可以将易失性和非易失性存储器存储在两个相同的“镜像”段中,这些段必须始终等效。 每个段可以附加一个CRC校验和。
  • 避免在MCU外部使用外部存储器。
  • 为所有可能的中断/exception实现默认中断服务例程/默认exception处理程序。 即使是你没有使用的。 除了closures自己的中断源以外,默认的例程应该什么也不做。
  • 理解并接受防御性编程的概念。 这意味着你的程序需要处理所有可能的情况,即使那些理论上不可能发生的情况。 例子 。

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

  • 永远不要编写依赖于不明确行为的程序。 由于辐射或EMI导致的意外的硬件变化,这种行为可能会发生急剧的变化。 确保你的程序没有这样的垃圾的最好方法是使用MISRA等编码标准和静态分析工具。 这也将有助于防御性编程和剔除错误(为什么你不想在任何types的应用程序中检测错误?)。
  • 重要提示:不要依赖静态存储持续时间variables的默认值。 也就是说,不要相信.data.bss的默认内容。 从初始化点到variables被实际使用的时间点之间可能有任何时间量,RAM可能已经被浪费了很多时间。 而应该编写程序,以便所有这些variables都是在运行时从NVM中设置的,就在这个variables第一次使用的时候。

    在实践中,这意味着如果一个variables是在文件范围内声明的或者是static ,你不应该使用=来初始化它(或者你可以,但是没有意义,因为你不能依赖这个值)。 在使用之前,始终将其设置为运行时间。 如果可以从NVM反复更新这些variables,那就这样做。

    同样在C ++中,不要依赖静态存储持续时间variables的构造函数。 让构造函数调用一个公共的“设置”例程,您也可以在运行时直接调用应用程序。

    如果可能的话,删除“复制”启动代码,用于初始化.data.bss (并调用C ++构造函数),以便在编写依赖于代码的代码时获得链接器错误。 许多编译器可以select跳过这个,通常称为“最小/快速启动”或类似的。

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

  • 为程序实现并定义一个安全状态,以防万一发生严重错误时将恢复到哪里。

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

有可能使用C来编写在这样的环境中强健行为的程序,但只有在大多数forms的编译器优化被禁用的情况下。 优化编译器被devise成用“更高效”的代码replace许多看似冗余的编码模式,并且可能不知道编译器知道没有办法x可能持有其他东西的原因程序员正在testingx==42程序员希望阻止某些代码的执行,即使在某些情况下,只有当系统收到某种电子故障时,才能保持该值。

声明variables是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() ,但是没有后续的毛刺事件,那么在没有通过validation检查或者首先调用cancel_preparations的情况下,代码将无法到达trigger_activation() [如果堆栈发生故障,执行可能会在调用prepare_for_activation()返回的上下文之后继续cancel_preparations() trigger_activation()之前的一个位置,但在prepare_for_activation()trigger_activation()调用之间会发生对cancel_preparations()的调用,从而使后者无害。

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

这是一个非常广泛的主题。 基本上,你不能真正从内存腐败中恢复,但你至less可以尝试失败 。 以下是您可以使用的一些技巧:

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

  • 存储冗余的variables 。 如果你有一个重要的variablesx ,把它的值写在x1x2x3并把它读作(x1 == x2) ? x2 : x3 (x1 == x2) ? x2 : x3

  • 实现程序stream程监控 。 在主循环中调用的重要函数/分支中使用唯一值XOR一个全局标志。 在无辐射的环境下以接近100%的testing覆盖率运行该程序应该给出在该周期结束时标志的可接受值的列表。 如果你看到偏差,重置。

  • 监视堆栈指针 。 在主循环的开始,比较堆栈指针和期望的值。 重置偏差。

什么可以帮助你是一个监督 。 上世纪80年代,看门狗被广泛用于工业计算。 硬件故障比较常见 – 另一个答案也是指那个时期。

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

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

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

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

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

这个答案假定你关心的是一个能够正常工作的系统,除此之外还有一个最小成本或者最快的系统。 大多数玩放射性物质的人都认为正确性/安全性超过速度/成本

有几个人提出了你可以做出的硬件修改(好的 – 这里已经有很多好的东西了,我不打算重复这一切),还有人提出了冗余(原则上很好),但我不认为任何人都已经提出了这种冗余在实践中如何运作。 你怎么过错? 你怎么知道什么时候出了问题? 许多技术在一切工作的基础上工作,失败是一个棘手的事情来处理。 但是,一些针对规模devise的分布式计算技术预计会出现故障(毕竟具有足够的规模,单个节点的任何MTBF都无法避免许多节点的故障); 你可以利用这个为你的环境。

这里有一些想法:

  • 确保你的整个硬件被复制了n次(其中n大于2,最好是奇数),并且每个硬件元素都可以与其他硬件元素进行通信。 以太网是这样做的一个显而易见的方法,但是还有许多其他更简单的路线可以提供更好的保护(例如CAN)。 最大限度地减less常见的组件(甚至电源)。 这可能意味着在多个地方采样ADCinput。

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

  • 采用仲裁协议来更改状态。 以RAFT为例。 当你在C ++中工作的时候,有一些众所周知的库。 FSM的变化只有在大多数节点同意时才能做出。 为协议栈和仲裁协议使用一个已知的好库,而不是自己来滚动一个,或者在仲裁协议挂起时浪费所有冗余的冗余工作。

  • 确保你的FSM有校验和(例如CRC / SHA),并将CRC / SHA存储在FSM本身(以及在消息中传输和校验消息本身)。 获取节点检查他们的FSM定期对这些校验和,校验和传入的消息,并检查其校验符合法定的校验和。

  • 将尽可能多的内部检查build立到您的系统中,使节点检测到自己的故障重新启动(如果您有足够的节点,这比继续进行一半的工作要好)。 试图让他们在重启过程中彻底摆脱法定人数,以免再次出现。 在重新启动时,他们会校验软件映像(以及其他任何其他内容),并在重新引入法定人数之前执行完整的RAMtesting。

  • 使用硬件来支持你,但要小心。 例如,您可以获得ECC RAM,并定期通过读写来纠正ECC错误(如果错误不可纠正,则会出现紧急情况)。 然而(从内存来看)静态RAM远比DRAM更加容忍电离辐射,所以最好使用静态DRAM。 看到“我不会做的事情”下面的第一点。

假设您在一天之内有1%的机率出现任何给定节点的故障,让我们假装您可以让故障完全独立。 有5个节点,你需要三个在一天内失败,这是一个0.00001%的机会。 随着更多,那么你明白了。

不会做的事情:

  • 低估没有问题的价值开始。 除非重量是一个问题,否则设备周围的一大块金属将是一个比程序员能想出的更便宜,更可靠的解决scheme。 EMIinput的同上光耦合是一个问题,等等。无论如何,尝试采购你的元件来获得最好的抗电离辐射。

  • 滚动你自己的algorithm 。 人们以前做过这个东西。 使用他们的工作。 容错和分布式algorithm很难。 尽可能使用其他人的工作。

  • 天真地使用复杂的编译器设置,希望能够检测到更多的故障。 如果你幸运的话,你可能会发现更多的失败。 更有可能的是,你会在编译器中使用一个代码path,这个代码path经过了很less的testing,特别是如果你自己编译的话。

  • 使用在您的环境中未经testing的技术。 大多数编写高可用性软件的人必须模拟故障模式来检查HA的工作是否正确,从而错过许多故障模式。 你处于经常需求失败的“幸运”地位。 因此,testing每一项技术,并确保其应用实际上改善了MTBF的超过复杂度的介绍(复杂性来自于bug)。 特别是将这个应用于我的build议re-quorumalgorithm等。

既然你特别要求软件解决scheme,并且你正在使用C ++,为什么不使用运算符重载来创build自己的安全数​​据types呢? 例如:

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

这样,如果在ALU,寄存器,RAM或总线上发生损坏并不重要,那么仍然会有多次尝试,并且很有可能发生错误。 但请注意,这只适用于你可以replace的variables – 例如你的堆栈指针仍然是易受影响的。

一个小故事:我遇到了一个类似的问题,也是在一个旧的ARM芯片上。 事实certificate,这是一个使用旧版本GCC的工具链,与我们使用的特定芯片一起,在某些边缘情况下触发了一个错误,有时这些错误会将错误的值传递到函数中。 确保你的设备在放射性活动之前没有任何问题,是的,有时它是一个编译器错误=)

免责声明:我不是放射性专业人员,也不是为这种应用工作。 但是,我对软件错误和冗余进行了长期的关键数据存档工作,这些存在一定的联系(同样的问题,不同的目标)。

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

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

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

  • 依靠良好的旧冗余scheme ,更具体地说是更高效的纠错码 (相同的目的,但更聪明的algorithm,以便您可以恢复更less的冗余位)。 这有时(错误地)也称为校验和。 使用这种解决scheme,您必须随时在主variables/类(或结构体?)中stored procedures的完整状态,计算ECC,并在执行任何操作之前检查ECC是否正确不,修理田地。 然而,这个解决scheme并不能保证你的软件能够工作(只要它能够正常工作,或者不能正常工作,因为ECC可以告诉你是否有什么问题,在这种情况下,你可以停止你的软件,让你不要得到假的结果)。

  • 或者你可以使用弹性的algorithm数据结构 ,这保证了一定的限制,即使存在软错误,你的程序仍然会给出正确的结果。 这些algorithm可以被看作是混合了ECCscheme的通用algorithm结构,但是这种方法比以前更有弹性,因为弹性scheme与结构紧密相关,所以你不需要编码额外的程序检查ECC,通常它们要快得多。 这些结构提供了一种方法来确保您的程序可以在任何情况下工作,达到软错误的理论界限。 您还可以将这些弹性结构与冗余/ ECCscheme混合使用以获得更高的安全性(或将最重要的数据结构编码为弹性,其余为可从主数据结构重新计算的可消耗数据, ECC或奇偶校验,计算速度非常快)。

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

  • 弹性algorithm数据结构介绍Giuseppe F.Italiano,Universita di Roma“Tor Vergata”

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

  • Ferraro-Petrillo,U.,Grandoni,F.,&Italiano,GF(2013)。 数据结构对内存故障具有适应性:词典的实验研究。 Journal of Experimental Algorithmics(JEA),18,1-6。

  • 意大利语,GF(2010)。 弹性algorithm和数据结构。 algorithm和复杂性(13-24页)。 斯普林格柏林海德堡。

如果您有兴趣了解有关弹性数据结构的更多信息,您可以查看Giuseppe F. Italiano的工作(并通过参考文献)和Faulty-RAM模型 (Finocchi等人,2005; Finocchi和2008年)。

/编辑:我说明了主要用于RAM存储器和数据存储器的软错误的预防/恢复,但是我没有谈到计算(CPU)错误 。 其他答案已经指出使用像数据库中的primefaces事务,所以我会提出另一个更简单的scheme: 冗余和多数票

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

  • 如果他们都同意,那么根本没有计算错误。
  • if they disagree, then you can use a majority vote to get the correct value, and since this means the computation was partially corrupted, you can also trigger a system/program state scan to check that the rest is ok.
  • if the majority vote cannot determine a winner (all x values are different), then it's a perfect signal for you to trigger the failsafe procedure (reboot, raise an alert to user, etc.).

This redundancy scheme is very fast compared to ECC (practically O(1)) and it provides you with a clear signal when you need to failsafe . The majority vote is also (almost) guaranteed to never produce corrupted output and also to recover from minor computation errors , because the probability that x computations give the same output is infinitesimal (because there is a huge amount of possible outputs, it's almost impossible to randomly get 3 times the same, even less chances if x > 3).

So with majority vote you are safe from corrupted output, and with redundancy x == 3, you can recover 1 error (with x == 4 it will be 2 errors recoverable, etc. — the exact equation is nb_error_recoverable == (x-2) where x is the number of calculation repetitions because you need at least 2 agreeing calculations to recover using the majority vote).

The drawback is that you need to compute x times instead of once, so you have an additional computation cost, but's linear complexity so asymptotically you don't lose much for the benefits you gain. A fast way to do a majority vote is to compute the mode on an array, but you can also use a median filter.

Also, if you want to make extra sure the calculations are conducted correctly, if you can make your own hardware you can construct your device with x CPUs, and wire the system so that calculations are automatically duplicated across the x CPUs with a majority vote done mechanically at the end (using AND/OR gates for example). This is often implemented in airplanes and mission-critical devices (see triple modular redundancy ). This way, you would not have any computational overhead (since the additional calculations will be done in parallel), and you have another layer of protection from soft errors (since the calculation duplication and majority vote will be managed directly by the hardware and not by software — which can more easily get corrupted since a program is simply bits stored in memory…).

One point no-one seems to have mentioned. You say you're developing in GCC and cross-compiling onto ARM. How do you know that you don't have code which makes assumptions about free RAM, integer size, pointer size, how long it takes to do a certain operation, how long the system will run for continuously, or various stuff like that? This is a very common problem.

The answer is usually automated unit testing. Write test harnesses which exercise the code on the development system, then run the same test harnesses on the target system. Look for differences!

Also check for errata on your embedded device. You may find there's something about "don't do this because it'll crash, so enable that compiler option and the compiler will work around it".

In short, your most likely source of crashes is bugs in your code. Until you've made pretty damn sure this isn't the case, don't worry (yet) about more esoteric failure modes.

You want 3+ slave machines with a master outside the radiation environment. All I/O passes through the master which contains a vote and/or retry mechanism. The slaves must have a hardware watchdog each and the call to bump them should be surrounded by CRCs or the like to reduce the probability of involuntary bumping. Bumping should be controlled by the master, so lost connection with master equals reboot within a few seconds.

One advantage of this solution is that you can use the same API to the master as to the slaves, so redundancy becomes a transparent feature.

Edit: From the comments I feel the need to clarify the "CRC idea." The possibilty of the slave bumping it's own watchdog is close to zero if you surround the bump with CRC or digest checks on random data from the master. That random data is only sent from master when the slave under scrutiny is aligned with the others. The random data and CRC/digest are immediately cleared after each bump. The master-slave bump frequency should be more than double the watchdog timeout. The data sent from the master is uniquely generated every time.

How about running many instances of your application. If crashes are due to random memory bit changes, chances are some of your app instances will make it through and produce accurate results. It's probably quite easy (for someone with statistical background) to calculate how many instances do you need given bit flop probability to achieve as tiny overall error as you wish.

What you ask is quite complex topic – not easily answerable. Other answers are ok, but they covered just a small part of all the things you need to do.

As seen in comments , it is not possible to fix hardware problems 100%, however it is possible with high probabily to reduce or catch them using various techniques.

If I was you, I would create the software of the highest Safety integrity level level (SIL-4). Get the IEC 61513 document (for the nuclear industry) and follow it.

Someone mentioned using slower chips to prevent ions from flipping bits as easily. In a similar fashion perhaps use a specialized cpu/ram that actually uses multiple bits to store a single bit. Thus providing a hardware fault tolerance because it would be very unlikely that all of the bits would get flipped. So 1 = 1111 but would need to get hit 4 times to actually flipped. (4 might be a bad number since if 2 bits get flipped its already ambiguous). So if you go with 8, you get 8 times less ram and some fraction slower access time but a much more reliable data representation. You could probably do this both on the software level with a specialized compiler(allocate x amount more space for everything) or language implementation (write wrappers for data structures that allocate things this way). Or specialized hardware that has the same logical structure but does this in the firmware.

Perhaps it would help to know does it mean for the hardware to be "designed for this environment". How does it correct and/or indicates the presence of SEU errors ?

At one space exploration related project, we had a custom MCU, which would raise an exception/interrupt on SEU errors, but with some delay, ie some cycles may pass/instructions be executed after the one insn which caused the SEU exception.

Particularly vulnerable was the data cache, so a handler would invalidate the offending cache line and restart the program. Only that, due to the imprecise nature of the exception, the sequence of insns headed by the exception raising insn may not be restartable.

We identified the hazardous (not restartable) sequences (like lw $3, 0x0($2) , followed by an insn, which modifies $2 and is not data-dependent on $3 ), and I made modifications to GCC, so such sequences do not occur (eg as a last resort, separating the two insns by a nop ).

Just something to consider …

If your hardware fails then you can use mechanical storage to recover it. If your code base is small and have some physical space then you can use a mechanical data store.

Enter image description here

There will be a surface of material which will not be affected by radiation. Multiple gears will be there. A mechanical reader will run on all the gears and will be flexible to move up and down. Down means it is 0 and up means it is 1. From 0 and 1 you can generate your code base.

Given supercat's comments, the tendencies of modern compilers, and other things, I'd be tempted to go back to the ancient days and write the whole code in assembly and static memory allocations everywhere. For this kind of utter reliability I think assembly no longer incurs a large percentage difference of the cost.

Firstly, design your application around failure . Ensure that as part of normal flow operation, it expects to reset (depending on your application and the type of failure either soft or hard). This is hard to get perfect: critical operations that require some degree of transactionality may need to be checked and tweaked at an assembly level so that an interruption at a key point cannot result in inconsistent external commands. Fail fast as soon as any unrecoverable memory corruption or control flow deviation is detected. Log failures if possible.

Secondly, where possible, correct corruption and continue . This means checksumming and fixing constant tables (and program code if you can) often; perhaps before each major operation or on a timed interrupt, and storing variables in structures that autocorrect (again before each major op or on a timed interrupt take a majority vote from 3 and correct if is a single deviation). Log corrections if possible.

Thirdly, test failure . Set up a repeatable test environment that flips bits in memory psuedo-randomly. This will allow you to replicate corruption situations and help design your application around them.

Use a cyclic scheduler . This gives you the ability to add regular maintenance times to check the correctness of critical data. The biggest problem often is corruption of the stack. If your software is cyclical you can reinitialise the stack between cycles. Do not reuse the stacks for interrupt calls, setup a separate stack of each important interrupt call.

Similar to the Watchdog concept is deadline timers.Start a hardware timer before calling a function. If the function does not return before the deadline reload the stack and try again. If it still fails after 3/5 tries you need reload from ROM.

Slit your software into parts and isolate these parts (Especially in a control environment). Example: signal acquisition, prepossessing data, main algorithm and result implementation/transmission. This means a failure in one part will not cause failures through the rest of the program.

Everything needs CRCs. If you execute out of RAM even your .text needs a CRC. Check the CRCs regularly if you using a cyclical scheduler. Some compilers (not GCC) can generate CRCs for each section and some processors have dedicated hardware to do CRC calculations, but I guess that would fall out side of the scope of your question.

Here are huge amount of replies, but I'll try to sum up my ideas about this.

Something crashes or does not work correctly could be result of your own mistakes – then it should be easily to fix when you locate the problem. But there is also possibility of hardware failures – and that's difficult if not impossible to fix in overall.

I would recommend first to try to catch the problematic situation by logging (stack, registers, function calls) – either by logging them somewhere into file, or transmitting them somehow directly ("oh no – I'm crashing").

Recovery from such error situation is either reboot (if software is still alive and kicking) or hardware reset (eg hw watchdogs). Easier to start from first one.

If problem is hardware related – then logging should help you to identify in which function call problem occurs and that can give you inside knowledge of what is not working and where.

Also if code is relatively complex – it makes sense to "divide and conquer" it – meaning you remove / disable some function calls where you suspect problem is – typically disabling half of code and enabling another half – you can get "does work" / "does not work" kind of decision after which you can focus into another half of code. (Where problem is)

If problem occurs after some time – then stack overflow can be suspected – then it's better to monitor stack point registers – if they constantly grows.

And if you manage to fully minimize your code until "hello world" kind of application – and it's still failing randomly – then hardware problems are expected – and there needs to be "hardware upgrade" – meaning invent such cpu / ram / … -hardware combination which would tolerate radiation better.

Most important thing is probably how you get your logs back if machine fully stopped / resetted / does not work – probably first thing bootstap should do – is a head back home if problematic situation is entcovered.

If it's possible in your environment also to transmit a signal and receive response – you could try out to construct some sort of online remote debugging environment, but then you must have at least of communication media working and some processor/ some ram in working state. And by remote debugging I mean either GDB / gdb stub kind of approach or your own implementation of what you need to get back from your application (eg download log files, download call stack, download ram, restart)