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


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

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


1281
2018-04-24 19:09


起源


内存中的值是变化还是处理器中的值发生变化?如果硬件是 设计 对于环境,软件应该像在非放射性环境中运行一样运行。 - Thomas Matthews
如果可能,您应该设置一个日志系统,将事件存储在抗辐射的非易失性存储器中。存储足够的信息,以便您可以跟踪事件并轻松找到根本原因。 - Thomas Matthews
@Thomas Matthews所有内存都有FIT错误率,硬件制造商做出了很多承诺。大多数问题可能是由SEU在运行时修改ram引起的。 - rook
其他相关讨论 这里 (黑客新闻) - Noam Hacker
这是一个硬件/软件组合解决方案,但我知道德州仪器(可能还有其他人)为安全关键应用制作嵌入式芯片,这些应用包括两个重复的内核,以锁步方式运行,半个时钟周期不同相。当硬件检测到内核之间存在不同的情况时,会执行特殊的中断和重置操作,以便您可以从错误中恢复。我相信TI将它们称为“Hercules”安全处理器。 - mbrig


答案:


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

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

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

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

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

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

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

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

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

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

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

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

除了上面因为单个事件扰乱而预测固件错误的建议之外,我还建议您:

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

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


731
2018-04-25 02:58





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

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

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

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

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

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

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

370
2018-04-24 19:32



这实际上听起来像是一件事 纯 语言会擅长。由于值永远不会改变,如果它们被损坏,你可以回到原来的定义(这应该是它应该是什么),并且你不会意外地做同样的事情两次(因为没有副作用)。 - PyRulez
RAII是一个坏主意,因为你无法依赖它正常运行甚至根本不依赖它。它可能会随机损坏您的数据等。您真的希望获得尽可能多的不变性,以及纠错机制。扔掉破碎的东西要比以某种方式尝试修理它们要容易得多(你究竟知道怎么回到正确的旧状态?)。你可能想要使用一种相当愚蠢的语言 - 优化可能比他们帮助更多。 - Luaan
@PyRulez:纯语言是一种抽象,硬件并不纯粹。编译器非常善于隐藏差异。如果你的程序有一个值,它在逻辑上不应该在步骤X之后使用,编译器可能会用在步骤X + 1中计算的值覆盖它。但这意味着你不能回去。更正式地说,纯语言中程序的可能状态形成非循环图,这意味着两个状态是等价的,并且当两者可达的状态相等时可以合并。这种合并破坏了导致这些状态的路径的差异。 - MSalters
不知怎的,我觉得这一切都导致回到原始的位代码。一旦有了一组数字,你所要做的就是确保它们不会在特定分区中发生变化。然后,如果数字移动或消失,您所做的就是重新模拟数字。其他分区用于数据存储等。 - DeerSpotter


以下是一些想法和想法:

更有创意地使用ROM。

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

使用最好的RAM作为堆栈。

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

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

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

实行 错误校正码 在软件中。

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

记住缓存。

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

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

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

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

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

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

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

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

一位评论者说

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

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

使用冗余硬件。

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


107
2018-04-24 23:11



如今,通过硬件可以获得ECC,从而节省了处理时间。第一步是选择内置ECC的微控制器。 - Lundin
在我脑海中的某个地方是对航空电子设备(可能是航天飞机?)飞行硬件的参考,其中冗余架构明确地设计为不相同(并且由不同的团队)。这样做可以减少硬件/软件设计中出现系统错误的可能性,从而降低所有投票系统在面对相同输入时同时崩溃的可能性。 - Peter M
@PeterM:AFAIK也声称用于波音777的飞行软件:由三个团队以三种编程语言提供三个版本。 - Martin Schröder
@DanEsparza RAM通常具有电容器(DRAM)或存储数据的少量反馈(SRAM)晶体管。辐射事件可以对电容器进行虚假充电/放电,或者改变反馈回路​​中的信号。 ROM通常不需要被写入的能力(至少在没有特殊情况和/或更高电压的情况下),因此在物理层面上可能固有地更稳定。 - Andrey Akhmetov
@DanEsparza:有多种类型的ROM存储器。如果“ROM”是由eeprom或flash readvely-at-5v模拟但可编程为10v,则实际上“ROM”仍然易于电离。也许只比其他人少。但是,有很多很好的东西 掩模ROM 要么 基于熔丝的PROM 我认为需要非常严重的辐射才能开始失败。但我不知道是否还有制造。 - quetzalcoatl


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

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

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


93
2018-04-24 21:13



我真的很喜欢你的回应。这是一种更通用的数据完整性软件方法,我们的最终产品将使用基于算法的容错解决方案。谢谢! - rook


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

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

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

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

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

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

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

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

  • 永远不要编写依赖于指定不当行为的程序。由于辐射或EMI引起的意外硬件变化,这种行为可能会发生巨大变化。确保您的程序没有这种废话的最佳方法是使用像MISRA这样的编码标准,以及静态分析器工具。这也有助于防御性编程和清除错误(为什么你不想在任何类型的应用程序中检测错误?)。
  • 重要信息:不要依赖静态存储持续时间变量的默认值。也就是说,不要相信默认内容 .data 要么 .bss。从初始化点到实际使用变量的点之间可能有任何时间,RAM可能有足够的时间被破坏。相反,编写程序以便在运行时从NVM设置所有这些变量,就在第一次使用这样的变量之前。

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

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

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

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

  • 为程序实现并定义安全状态,以便在发生严重错误时恢复。

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

36
2018-04-27 14:11



处理布尔值被破坏的一种方法(如在你的示例链接中)可以是 TRUE 等于 0xffffffff 然后用 POPCNT 有一个门槛。 - wizzwizz4
@ wizzwizz4鉴于值0xff是非编程闪存单元的默认值,这听起来不错。 - Lundin
%01010101010101010101010101010101,XOR然后POPCNT? - wizzwizz4
@ wizzwizz4或者只是值为0x1,符合C标准的要求。 - Lundin
@ wizzwizz4为什么使用上述部分或全部方法(ECC,CRC等)。否则,宇宙射线也可以翻转你的一点 .text 部分,更改操作代码或类似的。 - Lundin


可以使用C编写在这种环境中表现稳健的程序,但前提是禁用大多数形式的编译器优化。优化编译器旨在用“更有效”的编码模式替换许多看似冗余的编码模式,并且可能不知道程序员正在测试的原因 x==42 当编译器知道没有办法 x 可能持有其他任何东西是因为程序员想要阻止执行某些代码 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 [如果堆栈出现故障,执行可能会在之前的某个位置进行 trigger_activation() 在调用的上下文之后 prepare_for_activation() 返回,但调用 cancel_preparations() 会在调用之间发生 prepare_for_activation() 和 trigger_activation()因此使后者的呼叫无害。

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


30
2018-04-25 16:14



实际上,有多少现代编译器没有提供 -O0 还是一个等效的开关? GCC会做很多奇怪的事情 如果你给它许可,但如果你要求它不要这样做,它通常也可以是相当文字的。 - Leushenko
对不起,但这个想法基本上是危险的。禁用优化会产生较慢的程序。或者,换句话说,您需要更快的CPU。实际上,更快的CPU速度更快,因为晶体管栅极上的电荷更小。这使他们更容易受到辐射的影响。更好的策略是使用一个缓慢的大芯片,其中单个光子不太可能敲击一点,并且恢复速度 -O2。 - MSalters
次要原因 -O0 是一个坏主意是因为它发出了更多无用的指令。示例:非内联调用包含保存寄存器,进行调用,恢复寄存器的指令。所有这些都可能失败。不存在的指令不会失败。 - MSalters
还有另一个原因 -O0 这是一个坏主意:它倾向于将变量存储在内存中而不是寄存器中。现在不确定内存是否更容易受到SEU的影响,但是飞行中的数据比静止时的数据更容易受到影响。应避免无用的数据移动,并且 -O2 帮助那里。 - MSalters
@MSalters:重要的不是数据不受破坏的影响,而是系统能够以满足要求的方式处理中断。在许多编译器中,禁用所有优化会产生执行过多寄存器到寄存器移动的代码,这很糟糕,但是从恢复的角度来看,将变量存储在内存中比将它们保存在寄存器中更安全。如果一个人在内存中有两个应该遵守某些条件的变量(例如 v1=v2+0xCAFEBABE 并完成了对这两个变量的所有更新...... - supercat


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

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

  • 存储冗余的变量。如果你有一个重要的变量 x写下它的价值 x1x2 和 x3 并将其读作 (x1 == x2) ? x2 : x3

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

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


27
2018-04-25 17:05





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

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

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

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

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

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


26
2018-04-26 22:41



采用TTL标准IC的二进制计数器看门狗确实是20世纪80年代的解决方案。不要那样做。今天,市场上没有一个没有内置看门狗电路的MCU。您需要检查的是内置看门狗是否具有单独的时钟源(很好,很可能是这种情况),或者它是否从系统时钟(坏)继承其时钟。 - Lundin
或者在FPGA中实现看门狗: ntrs.nasa.gov/archive/nasa/casi.ntrs.nasa.gov/20130013486.pdf - nos
顺便提一下,它仍广泛用于嵌入式处理器。 - Graham
@Peter Mortensen请在这个问题的每个答案上停止编辑狂欢。这不是维基百科,这些链接没有帮助(我相信每个人都知道如何找到维基百科......)。您的许多编辑都不正确,因为您不知道该主题。当我遇到它们时,我正在对你的错误编辑进行回滚。你没有把这个线程变得更好,但更糟。停止编辑。 - Lundin
Jack Ganssle有一篇关于监管机构的好文章: ganssle.com/watchdogs.htm - Igor Skochinsky


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

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

以下是一些想法:

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

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

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

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

  • 尽可能多地对系统进行内部检查,使得检测自身故障的节点重新启动(如果你有足够的节点,这比继续工作一半要好)。尝试让他们在重新启动期间干净地从法定人数中移除,以防他们再次出现。在重新启动时,让他们对软件映像(以及它们加载的任何其他内容)进行校验和,并在重新引入仲裁之前执行完整的RAM测试。

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

假设您在一天内有任何给定节点失败的可能性为1%,让我们假装您可以完全独立完成失败。有5个节点,你需要在一天内失败三个,这是一个.00001%的机会。有了更多,嗯,你明白了。

我想做的事情  做:

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

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

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

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


22
2018-04-27 15:41



以太网在任务关键型应用程序中使用可能不是一个好主意。在PCB本身之外,I2C也不是。像CAN这样坚固耐用的东西会更合适。 - Lundin
@Lundin Fair点,虽然任何光学连接(包括以太网)应该没问题。 - abligh
物理媒体不是以太网不适合的原因,而是缺乏确定性的实时行为。虽然我认为现在有一些方法可以提供一些可靠的以太网,但我只是将它与商业/玩具电子产品结合在一起。 - Lundin
@Lundin这是一个公平的观点,但正如我建议用它来运行RAFT一样,算法中也会存在(理论上)非确定性的实时行为(例如,同时进行的领导选举导致重新选举类似于CSMA /光盘)。如果需要严格的实时行为,可以说我的答案比以太网有更多的问题(并且在我的回复中我注意到'正确'可能是以'快'经常为代价)。我把你的观点纳入了CAN。 - abligh
@Lundin:任何涉及异步方面的系统都不是完全不确定的。我认为,如果软件协议以合适的方式设置并且设备具有唯一的ID并且设备数量已知有限(设备越多,则设备越多),在没有硬件中断的情况下,以太网的最坏情况行为就会受到限制。最坏情况下的重试次数)。 - supercat


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

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

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

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


21
2018-04-27 15:32



这些建议中的一些有类似的“多位理智检查”思维方式,用于检测损坏,我真的很喜欢这一点,尽管安全关键自定义数据类型的建议最多但是 - WearyWanderer
世界上有一些系统,每个冗余节点由不同的团队设计和开发,并有一个仲裁器,以确保他们不会意外地解决相同的解决方案。这样你就不会因为同样的错误而将它们全部关闭,而类似的瞬态并没有表现出类似的失败模式。 - jwdonahue