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




gcc embedded (16)

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

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


也许有必要知道“针对此环境设计”硬件的含义。 如何纠正和/或指示SEU错误的存在?

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

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

我们确定了危险的(不可重新启动的)序列(例如 lw $3, 0x0($2) ,后跟一个insn,该 序列 修改了 $2 且不依赖于数据 $3 ),并且我对GCC进行了修改,因此此类序列不会发生(例如,万不得已时,将由a的两个insns nop

只是要考虑的事情...


似乎没有人提到过这一点。 您说您正在使用GCC进行开发,并在ARM上进行交叉编译。 您怎么知道您没有代码对自由RAM,整数大小,指针大小,执行某项操作需要花费多长时间,系统将连续运行多长时间或诸如此类的假设? 这是一个非常普遍的问题。

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

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

简而言之,最有可能导致崩溃的原因是代码中的错误。 在您完全确定不是这种情况之前,请不要担心(至今)更多深奥的失败模式。


免责声明:我不是放射性专业人士,也没有从事此类应用程序的工作。 但是我为长期存档关键数据进行了软错误和冗余工作,这些工作在某种程度上是相互联系的(相同的问题,不同的目标)。

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

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

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

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

  • 或者您可以使用 弹性算法数据结构 ,这在一定程度上保证了即使存在软错误,您的程序仍将给出正确的结果。这些算法可以看作是普通算法结构与本地混入的ECC方案的混合,但是比起这种方案,弹性要强得多,因为弹性方案与该结构紧密绑定,因此您无需编码其他过程检查ECC,通常速度要快得多。这些结构提供了一种方法,可以确保您的程序在任何条件下都可以运行,直到软错误的理论极限为止。您还可以将这些弹性结构与冗余/ ECC方案混合使用,以提高安全性(或将最重要的数据结构编码为弹性数据,其余的可以从主数据结构重新计算的消耗性数据进行编码,作为具有少量ECC或奇偶校验的普通数据结构,计算速度非常快)。

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

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

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

  • U.Ferraro-Petrillo,F.Grandoni和GF Italiano(2013)。 能够抵抗内存故障的数据结构:词典的实验研究。 实验算法学报(JEA),18,1-6。

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

如果您想对弹性数据结构领域有更多的了解,可以查阅 Giuseppe F. Italiano 的作品 (并通过参考文献进行研究)和 Faulty-RAM模型 (在Finocchi等人2005年引入; Finocchi和Italiano 2008)。

/编辑:我举例说明了主要针对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个以上具有主机的从属计算机。 所有I / O都通过包含表决和/或重试机制的主机。 从站每个必须有一个硬件看门狗,并且撞到它们的调用应该被CRC或类似的东西包围,以减少非自愿撞的可能性。 碰撞应由主服务器控制,因此与主服务器的连接断开等于在几秒钟内重新启动。

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

编辑: 从评论中,我觉得有必要澄清“ CRC想法”。 如果用CRC包围凸起,或者对来自主器件的随机数据进行摘要检查,则从器件碰撞自己的看门狗的可能性接近于零。 只有在仔细检查的从机与其他主机对齐时,才从主机发送随机数据。 每次碰撞后,随机数据和CRC /摘要被立即清除。 主从碰撞频率应大于 看门狗超时的 double 。 从主机发送的数据每次都会唯一生成。


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

从评论 中可以 看出, 不可能100%修复硬件问题,但是有可能使用各种技术来减少或解决这些问题。

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


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


这是大量的答复,但是我将尝试总结一下我的想法。

崩溃或无法正常工作可能是您自己的错误造成的-因此,当您找到问题时,应该很容易解决。 但是,也有可能发生硬件故障-并且即使不是不可能整体修复也很难。

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

从这种错误情况中恢复可以是重新启动(如果软件仍在运行并且可以正常运行)或硬件复位(例如,硬件看门狗)。 从第一个开始比较容易。

如果问题与硬件有关,那么日志记录将帮助您确定发生在哪个函数调用中的问题,并且可以使您从内部了解什么不起作用以及在哪里。

同样,如果代码相对复杂-“分而治之”是有意义的-意味着您在怀疑问题的地方删除/禁用了一些函数调用-通常禁用一半代码,然后再启用另一半-您可以“正常工作” / “无效”决定之后,您可以专注于另一半代码。 (问题出在哪里)

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

并且,如果您设法完全减少代码,直到出现“ hello world”类型的应用程序-并且仍然随机失败-则可能出现硬件问题-并且需要进行“硬件升级”-意味着发明这种cpu / ram / ... -硬件组合将更好地耐受辐射。

最重要的事情可能是如果机器完全停止/重置/不工作时如何找回日志-如果发现问题情况,引导应该做的第一件事-是回到家中。

如果您的环境中也有可能发送信号并接收响应-您可以尝试构建某种在线远程调试环境,但是您必须至少使通信媒体工作并且某些处理器/内存处于工作状态。 通过远程调试,我指的是GDB / gdb存根之类的方法,还是您自己实现的从应用程序中获取的内容(例如,下载日志文件,下载调用堆栈,下载ram,重新启动)


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

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

第三, 测试失败 。 设置 可重复的 测试环境,以随机方式伪装存储器中的位。 这将使您能够复制损坏情况,并帮助您围绕这些情况设计应用程序。


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

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

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

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

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

  • 检查RAM或NVM中的错误的方法有很多种,包括校验和,“移动模式”,软件ECC等。当今最好的解决方案是不使用其中任何一种,而要使用具有内置ECC和类似的检查。 由于在软件中执行此操作很复杂,因此错误检查本身可能会引入错误和意外问题。

  • 使用冗余。 您可以将易失性和非易失性存储器都存储在两个相同的“镜像”段中,这些段必须始终相等。 每个段可以附加一个CRC校验和。
  • 避免在MCU外部使用外部存储器。
  • 为所有可能的中断/异常实现默认的中断服务例程/默认的异常处理程序。 甚至那些您不使用的。 默认例程除了关闭其自己的中断源外不执行任何操作。
  • 了解并接受防御性编程的概念。 这意味着您的程序需要处理所有可能的情况,即使是理论上不可能发生的情况。 Examples

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

  • 切勿编写依赖于行为不当的程序。 由于辐射或EMI导致硬件意外更改,此类行为可能会发生巨大变化。 确保您的程序不受此类废话的最好方法是使用像MISRA这样的编码标准,以及一个静态分析器工具。 这也将有助于防御性编程和清除错误(为什么您不希望在任何类型的应用程序中检测到错误?)。
  • 重要说明:不要依赖静态存储持续时间变量的默认值。 也就是说,不要相信 .data.bss 的默认内容。 从初始化到实际使用变量之间可能有任何时间,可能会有很多时间使RAM损坏。 而是编写程序,以便在运行时从NVM设置所有此类变量,就在首次使用此类变量之前。

    在实践中,这意味着如果在文件作用域中声明了变量或将其声明为 static ,则永远不要使用 = 进行初始化(否则可以,但是它毫无意义,因为您无论如何都不能依赖该值)。 始终在使用前在运行时进行设置。 如果可以从NVM重复更新此类变量,则可以这样做。

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

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

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

  • 为程序实现并定义一个安全状态,以防万一出现严重错误,您将还原到该状态。

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

可以帮助您的是 watchdog 。 看门狗在1980年代被广泛用于工业计算。 当时,硬件故障要普遍得多-另一个答案也就是那个时期。

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

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

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

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

看门狗的一个固有缺点是,从系统出现故障直到看门狗计数器达到零+重新启动时间,系统才可用。 尽管该时间通常比任何外部或人工干预要短得多,但在该时间范围内,受支持的设备将需要能够在没有计算机控制的情况下继续运行。


您可能也对关于算法容错的丰富文献感兴趣。 这包括旧的分配:写一个排序,当恒定数量的比较将失败时(或者,当失败的比较的渐近数量渐渐缩放为 log(n) n 比较的 log(n) 时,对输入进行正确排序)。

开始阅读的地方是Huang和Abraham在1984年发表的论文“ 矩阵运算的基于算法的容错 ”。 他们的想法大概与同态加密计算相似(但是实际上并不太一样,因为他们正在尝试在操作级别进行错误检测/纠正)。

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


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

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

这样,无论ALU,寄存器,RAM或总线上是否发生损坏,您都将有多次尝试,而且很有可能捕获错误。 但是请注意,尽管这仅适用于您可以替换的变量-例如,您的堆栈指针仍然容易受到影响。

附带说明:我在旧的ARM芯片上也遇到了类似的问题。 原来,这是一个使用旧版GCC的工具链,它与我们使用的特定芯片一起在某些极端情况下触发了一个错误,该错误会(有时)破坏值传递给函数。 在将其归咎于放射性之前,请确保您的设备没有任何问题,是的,有时它是编译器错误=)


美国国家航空航天局(NASA)发表 了一篇关于辐射增强 软件的论文。 它描述了三个主要任务:

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

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

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

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

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

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

而且,正是这种情况,NASA已将C ++用于诸如 Mars Rover之 类的大型项目。

C ++类抽象和封装可在多个项目和开发人员之间进行快速开发和测试。

他们避免使用某些可能导致问题的C ++功能:

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

这里有一些想法和想法:

更创造性地使用ROM。

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

将最佳RAM用于堆栈。

堆栈中的SEU可能是最有可能导致崩溃的原因,因为索引变量,状态变量,返回地址和各种指针通常都存在于此。

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

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

在软件中实施 error-correcting-codes

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

记住缓存。

检查您的CPU缓存的大小。 您最近访问或修改的数据可能会在缓存中。 我相信您可以禁用至少某些缓存(以较高的性能代价); 您应该尝试这样做以查看缓存对SEU的敏感程度。 如果缓存比RAM硬,那么您可以定期读取和重写关键数据,以确保它们保留在缓存中并使RAM恢复正常。

聪明地使用页面错误处理程序。

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

使用汇编语言处理关键的事情(可能是所有事情)。

使用汇编语言,您 知道 寄存器中的内容和RAM中的内容。 您 知道 CPU使用的是什么特殊的RAM表,并且可以通过回旋方式进行设计以降低风险。

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

如果您使用的是像Linux这样的大型操作系统,那您就麻烦了; 有这么多的复杂性和很多错误要解决。

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

评论者说

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

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

使用冗余硬件。

使用2个或更多具有相同代码的相同硬件设置。 如果结果不同,则应触发重置。 对于3台或3台以上的设备,您可以使用“投票”系统来尝试确定哪些设备已受到威胁。


该答案假定您关心的是拥有一个正常运行的系统,而不是拥有成本最低或速度最快的系统。 大多数玩放射性物质的人都重视正确性/安全性,而不是速度/成本

有几个人建议您可以进行硬件更改(很好-答案中已经有很多不错的东西,我不打算重复全部),还有其他人建议了冗余(原则上很好),但是我不认为任何人都建议过这种冗余在实践中将如何工作。 您如何进行故障转移? 您怎么知道什么时候“出了错”? 许多技术都是在一切正常的基础上工作的,因此失败是一件棘手的事情。 但是,一些为规模化而设计的分布式计算技术 会出现 故障(毕竟具有足够的规模,对于单个节点而言,任何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个节点,则一天之内将需要三个节点发生故障,这是0.00001%的机会。 有了更多,那么,您就知道了。

不会 做的事情:

  • 低估了开始时没有问题的价值。 除非担心重量问题,否则设备周围的大量金属将是比一组程序员所能想到的便宜得多,更可靠的解决方案。 EMI输入的同向光耦合是一个问题,等等。无论如何,在采购组件时要尝试采购最能抵抗电离辐射的组件。

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

  • 天真的使用复杂的编译器设置,希望您能发现更多的故障。 如果幸运的话,您可能会发现更多的故障。 更有可能的是,您将在编译器中使用未经测试的代码路径,特别是如果您自己滚动代码路径。

  • 使用未经您环境测试的技术。 大多数编写高可用性软件的人都必须模拟故障模式以检查其HA是否正常工作,从而错过许多故障模式。 您处于经常出现按需故障的“幸运”位置。 因此,请测试每种技术,并确保其应用实际将MTBF的提高幅度超过了引入它的复杂性(复杂性会带来bug)。 尤其将其应用于我的建议定额算法等。





fault-tolerance