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


1456

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

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


186
内存中的值是否正在更改,或者处理器中的值是否正在更改?如果硬件是为环境设计的,则软件应像在非放射性环境中一样运行。
汤玛斯·马修斯

3
如果可能,您应该设置一个日志记录系统,将事件存储在耐辐射的非易失性存储器中。存储足够的信息,以便您可以跟踪事件并轻松找到根本原因。
托马斯·马修斯

2
@Thomas Matthews所有内存都有FIT错误率,并且硬件制造商做出了很多承诺。大多数问题可能是由SEU在运行时修改ram引起的。
rook

9
这是硬件/软件的组合解决方案,但是我知道德州仪器(及其他公司)会为安全关键型应用制造嵌入式芯片,该芯片由两个重复的内核组成,它们以锁步方式运行,相差半个时钟周期。当硬件检测到内核之间存在差异时,会执行特殊的中断和重置操作,因此您可以从错误中恢复。我相信TI将它们称为“大力士”安全处理器。
mbrig

5
冗余坚固的电机,一些齿轮,轴和棘轮!每年更换一次,或根据剂量率要求更频繁地更换。真的,我对此类问题的第一个问题一直是,您是否真的需要那么多软件?尽可能地模拟一下。
jwdonahue

Answers:


814

我致力于软件/固件开发和小型卫星的环境测试* 大约4-5年,我想在这里分享我的经验。

*(小型卫星由于其电子元件的尺寸相对较小且尺寸有限,因此比大型卫星更容易发生单事件干扰

非常简洁和直接:没有机制歇着检测,错误的情况由软件/固件本身没有,至少,一个 复制最低工作版本的软件/固件的某处恢复目的-和与硬件配套恢复(功能性)。

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

  1. ...恢复目的...。提供在真实环境中更新/重新编译/重新刷新软件/固件的功能。对于高度电离的环境,这是几乎所有软件/固件都必须具备的功能。否则,您可能会拥有任意数量的冗余软件/硬件,但有一点,它们都将崩溃。因此,准备此功能!

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

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

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

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

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

    3. 您还可以将副本保存在设备的永久存储器中,以触发该副本以还原正在运行的系统的软件/固件
  4. ...可检测的错误情况。错误必须是可检测的,通常是通过硬件错误纠正/检测电路或一小段代码来进行错误纠正/检测。最好将此类代码缩小,多个并独立于主软件/固件。其主要任务用于检查/更正。如果硬件电路/固件可靠(例如,其辐射强度比其余辐射更强-或具有多个电路/逻辑),那么您可以考虑对其进行纠错。但是,如果不是这样,最好将其作为错误检测。可以通过外部系统/设备进行更正。对于纠错,您可以考虑使用诸如Hamming / Golay23之类的基本纠错算法,因为它们可以在电路/软件中更轻松地实现。但这最终取决于您团队的能力。对于错误检测,通常使用CRC。

  5. ...支持恢复的硬件现在,这是这个问题上最困难的方面。最终,恢复需要负责恢复的硬件至少能够正常运行。如果硬件永久损坏(通常在其总电离剂量达到一定水平后发生),则该软件将(很难)无法帮助恢复。因此,对于暴露于高辐射水平的设备(例如卫星),硬件无疑是最重要的问题。

除了上述可预见的由于单事件失败而导致的固件错误的建议之外,我还建议您具有:

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

  2. 过滤ADC读数。千万不能使用ADC直接读取。用中值过滤器,均值过滤器或任何其他过滤器过滤它- 永远不要相信单个读数值。采样更多而不是更少-合理。


401

美国国家航空航天局(NASA)撰写了一篇有关防辐射软件的论文。它描述了三个主要任务:

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

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

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

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

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

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

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

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

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

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

28
实际上,这听起来像是语言会擅长的事情。因为值永远不会改变,所以如果它们遭到破坏,您可以返回到原始定义(这就是应该的定义),并且您不会无意中重复两次相同的事情(因为没有副作用)。
PyRulez '16

20
RAII是一个坏主意,因为您不能依靠它来正确执行甚至根本不执行。它可能会随机破坏您的数据等。您确实希望获得尽可能多的不变性,并在此之上具有纠错机制。丢掉破碎的东西比尝试以某种方式修复它们要容易得多(您确切地知道如何回到正确的旧状态?)。不过,您可能想要使用一种相当愚蠢的语言-优化可能会带来伤害,而不是帮助。
a安

67
@PyRulez:纯粹的语言是一种抽象,硬件不是纯粹的。编译器非常擅长隐藏差异。如果您的程序具有逻辑上不应在步骤X之后使用的值,则编译器可能会使用在步骤X + 1中计算出的值覆盖它。但这意味着您无法返回。更正式地讲,纯语言中程序的可能状态形成一个非循环图,这意味着两个状态是等效的,并且当从两个状态均可到达的状态是等效时,可以将它们合并。这次合并破坏了通往这些州的道路差异。
MSalters '16

2
@Vorac-根据演示,与C ++模板有关的问题是代码膨胀。
jww

3
@DeerSpotter确切的问题远不止于此。电离会损坏正在运行的观察程序的某些位。然后,您将需要一个观察者的观察者,然后-一个观察者的观察者等等……
Agnius Vasiliauskas

116

这里有一些想法和想法:

更创造性地使用ROM。

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

将最佳RAM用于堆栈。

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

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

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

实施错误纠正代码在软件中。

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

记住缓存。

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

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

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

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

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

采用 objdump实际查看生成的汇编语言,制定出了多少代码每个程序的占用。

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

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

评论者说

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

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

使用冗余硬件。

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


14
如今,通过硬件可以使用ECC,从而节省了处理时间。第一步将是选择具有内置ECC的微控制器。
伦丁

23
我脑海中的某个地方提到了航空电子(也许是航天飞机?)飞行硬件,其中冗余架构被明确设计为不完全相同(并且由不同的团队)。这样做减少了硬件/软件设计中系统错误的可能性,从而降低了所有投票系统在面对相同输入时同时崩溃的可能性。
彼得M

8
@PeterM:AFAIK也被称为波音777的飞行软件:三支团队的三种版本采用三种编程语言。
恢复莫妮卡-M.Schröder'16

7
@DanEsparza RAM通常具有电容器(DRAM)或反馈中的一些晶体管(SRAM),用于存储数据。辐射事件可能会虚假地对电容器进行充电/放电,或更改反馈回路中的信号。ROM通常不需要写入的能力(至少在没有特殊情况和/或更高电压的情况下),因此在物理级别上可能固有地更稳定。
nanofarad

7
@DanEsparza:ROM存储器有多种类型。如果“ eeprom”或“ eepromableatat-at-v”可以通过eeprom或flash来模拟“ ROM”,则实际上“ ROM”仍然易于电离。也许只是比别人少。但是,有一些不错的硬核东西,例如Mask ROM基于保险丝的PROM,我认为它们真的需要大量的辐射才能开始失效。我不知道是否还有制造。
quetzalcoatl

105

您可能也对关于算法容错的丰富文献感兴趣。这包括旧的作业:写那种正确排序其输入时比较恒定的数量将失败(或者稍微更邪恶的版本,当失败的比较级表的渐近数log(n)n比较)。

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

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


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

40

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

处理布尔值被破坏的一种方法(如您的示例链接中所示)可以TRUE等于,0xffffffff然后POPCNT与阈值一起使用。
wizzwizz4

@ wizzwizz4鉴于值0xff是未编程闪存单元的默认值,这听起来是个坏主意。
伦丁

%01010101010101010101010101010101,然后进行XOR或POPCNT?
wizzwizz4

1
@ wizzwizz4还是C标准所要求的值0x1。
伦丁

1
@ wizzwizz4为什么使用部分或全部上述方法(ECC,CRC等)。否则,宇宙射线也可能会在您的.text部分中翻转一点,从而更改操作码或类似内容。
伦丁

34

仅在大多数形式的编译器优化被禁用的情况下,才可以使用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编译器中则不是。这样的编译器在那种环境下可能非常危险,因为激进的编译器会努力只包含与某些情况相关的代码,这些情况可能通过某种定义良好的机制发生,并且其后果也将得到很好的定义。在某些情况下,目的是要在故障后进行检测和清除的代码可能最终使情况变得更糟。如果编译器确定尝试进行的恢复在某些情况下将调用未定义的行为,则它可以推断出在这种情况下可能需要进行这种恢复的条件不可能发生,从而消除了要检查它们的代码。


6
实际上,有多少现代编译器不提供-O0或提供等效转换?如果您授予 GCC 许可,GCC会做很多奇怪的事情,但是如果您要求它不这样做,通常也可以做到字面上的意思。
Leushenko '16

24
抱歉,但是这个想法从根本上是危险的。禁用优化会降低程序速度。或者,换句话说,您需要更快的CPU。碰巧的是,更快的CPU更快,因为它们的晶体管门上的电荷较小。这使得它们更容易受到辐射的影响。更好的策略是使用速度较慢的大芯片,在该芯片中单个光子几乎不可能发生碰撞,并以降低速度-O2
MSalters '16

27
-O0不好的主意的第二个原因是因为它发出了更多无用的指令。示例:非内联调用包含保存寄存器,进行调用,恢复寄存器的指令。所有这些都会失败。不存在的指令不会失败。
MSalters '16

15
另一个-O0不好的主意是:它倾向于将变量存储在内存中而不是寄存器中。现在还不能确定内存更容易受到SEU的影响,但是传输中的数据比静止数据更容易受到影响。应避免无用的数据移动,并在此方面提供-O2帮助。
MSalters '16

9
@MSalters:重要的不是数据不受干扰,而是系统能够以符合要求的方式处理中断。在许多编译器上,禁用所有优化都会导致代码执行过多的寄存器到寄存器移动,这很糟糕,但是从恢复的角度来看,将变量存储在内存中比将变量保存在寄存器中更安全。如果一个内存中有两个应该服从某种条件v1=v2+0xCAFEBABE的变量(例如,对两个变量的所有更新都已完成……
supercat

28

这是一个极其广泛的主题。基本上,您无法真正从内存损坏中恢复,但是至少可以尝试立即失败。您可以使用以下几种技巧:

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

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

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

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


27

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

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

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

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

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

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


9
具有TTL标准IC的二进制计数器看门狗确实是1980年代的解决方案。不要那样做 如今,市场上没有没有内置看门狗电路的MCU。您需要检查的是内置看门狗是否具有单独的时钟源(好的,很可能是这种情况),或者它是否从系统时钟继承了时钟(坏的)。
伦丁

1

2
顺便说一句,仍广泛用于嵌入式处理器中。
格雷厄姆

5
@Peter Mortensen请停止对这个问题的每个答案进行编辑。这不是Wikipedia,而且这些链接也无济于事(而且我相信每个人都知道如何找到Wikipedia ...)。您的许多修改都不正确,因为您不知道该主题。遇到不正确的修改时,我正在回滚。您没有使这个线程更好,但是更糟。停止编辑。
伦丁

杰克·甘斯勒(Jack Ganssle)上有一篇有关看门狗的好文章:ganssle.com/watchdogs.htm
Igor Skochinsky,2016年

23

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

有几个人建议您可以进行硬件更改(很好-答案中已经有很多不错的东西,我不打算重复所有这些内容),还有其他人建议了冗余(原则上很好),但是我不认为任何人都建议过这种冗余在实践中将如何工作。您如何进行故障转移?您怎么知道什么时候“出了错”?许多技术都是在一切正常的基础上工作的,因此失败是一件棘手的事情。但是,一些为规模化而设计的分布式计算技术可能会出现故障(毕竟,如果规模足够大,那么对于单个节点而言,任何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的提高幅度超过了引入它的复杂性(复杂性会带来bug)。尤其将其应用于我的建议定额算法等。


2
在关键任务应用中使用以太网可能不是一个好主意。I2C也不在PCB本身之外。像CAN这样坚固的东西会更合适。
伦丁

1
@Lundin公平点,尽管任何光连接(包括以太网)都应该可以。
abligh

1
物理介质并不是以太网不合适的原因,而是缺乏确定的实时行为。尽管我想现在也有提供可靠的以太网的方法,但是我只是出于老习惯将其与商业/玩具电子产品组合在一起。
隆丁

1
@Lundin是一个公平的观点,但是正如我建议使用它运行RAFT一样,算法中仍然会存在(理论上)不确定的实时行为(例如,同时进行的领导人选举导致重新选举的结果类似于CSMA /光盘)。如果需要严格的实时行为,可以说我的答案比以太网有更多的问题(请注意,在我答复的开头,我说“正确”可能经常以“快速”为代价)。我把你的观点并入了。
abligh 2016年

1
@Lundin:任何涉及异步方面的系统都不能完全不确定。我认为,如果以适当的方式设置软件协议并且设备具有唯一的ID,并且设备数量存在已知限制(设备越多,则设备越大最坏情况下的重试次数)。
超级猫

23

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

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

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

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


1
其中一些建议具有类似的用于检测损坏的“多位完整性检查”心态,但我真的很喜欢这种建议,其中最关键的是对安全至关重要的自定义数据类型的建议
WearyWanderer

2
世界上有一些系统,每个冗余节点都是由不同的团队设计和开发的,由仲裁员来确保它们不会偶然采用相同的解决方案。这样一来,您就不会因为相同的错误而让它们全都崩溃,并且类似的瞬变不会表现出相似的故障模式。
jwdonahue

16

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

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

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

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

  • 依靠良好的旧冗余方案,尤其是更有效的纠错码(目的相同,但算法更聪明,因此您可以用更少的冗余恢复更多的位)。有时(错误地)也称为校验和。使用这种解决方案,您必须随时将程序的完整状态存储在主变量/类(或结构?)中,计算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门)。这通常在飞机和关键任务设备中实现(请参阅三重模块冗余)。这样,您将没有任何计算开销(因为额外的计算将并行进行),并且您拥有另一层保护免受软错误的影响(因为计算重复和多数表决将直接由硬件而不是由硬件管理)软件-由于程序只是存储在内存中的位而容易被破坏...)。


9

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

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

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

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


1
确实,在问题的测试中,没有任何人提到该应用程序在放射性环境外运行良好。
Marc.2377 '16

9

您希望辐射环境外有3个以上具有主机的从属计算机。所有I / O都通过包含表决和/或重试机制的主机。从站每个必须有一个硬件看门狗,并且撞到它们的调用应该被CRC或类似的东西包围,以减少非自愿撞的可能性。碰撞应由主服务器控制,因此与主服务器的连接断开等于在几秒钟内重新启动。

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

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


7
我正在尝试一种方案,您可以在辐射环境之外拥有一个主机,并且能够与辐射环境内部的从属可靠地通信,而您不能仅仅将这些从属设备置于辐射环境之外。
fostandy '16

1
@fostandy:从站正在使用需要控制器的设备进行测量或控制。说一个盖革计数器。主站由于从站冗余而不需要可靠的通信。
乔纳斯·比斯特罗姆'16

4
引入主服务器不会自动意味着提高安全性。如果从站x由于内存损坏而发疯,因此它反复告诉自己“主服务器在这里,主服务器很高兴”,那么主服务器不会保存任何数量的CRC或禁止的命令。您将不得不给主设备切断该从设备电源的可能性。而且,如果您遇到常见原因错误,则添加更多从站将不会增加安全性。还请记住,软件错误的数量和可能破坏的事物的数量会随着复杂性的增加而增加。
伦丁

5
话虽这么说,当然,最好将程序的大部分“外包”到暴露较少的地方,同时如果可以的话,使放射环境中的电子设备尽可能简单。
伦丁

7

如何运行应用程序的许多实例。如果崩溃是由于随机的内存位更改引起的,则您的某些应用程序实例很可能会成功通过并产生准确的结果。(对于具有统计背景的人而言),很可能很容易地计算出给定的翻转概率,您需要多少个实例,以实现所需的微小总体误差。


2
当然,在一个健壮的应用程序实例中,嵌入式系统会比在多个实例上触发安全关键捕获要好得多,从而提高硬件要求,并在某种程度上希望至少有一个实例能够顺利通过?我知道了这个主意,它是有效的,但更倾向于不依赖蛮力的建议
WearyWanderer 16/04/29

7

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

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

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


11
或者,通读技术要求并实施有意义的要求。SIL标准中的大部分都是胡说八道,如果一味地遵循它们,您将最终得到不安全和危险的产品。今天的SIL认证主要是要生产大量文档,然后贿赂测试机构。SIL级别没有说明系统的实际安全性。相反,您将需要关注实际的技术安全措施。SIL文档中有一些很好的文档,还有一些完整的废话。
伦丁

7

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


7

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

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

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

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

只是要考虑的事情...


7

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

在此处输入图片说明

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


2
诸如CD-ROM之类的光学介质可能会满足此定义。它将具有大容量的额外好处。
Wossname

2
是的,它将是相似的,但是CD-ROM将使用较少,但这将是完全机械的系统。
Hitul '16

7
我想知道他们是否没有在太空中不使用打孔卡读取器的原因。
索伦

3
@Soren速度和物理空间可能是一个原因。
Hitul '16

5

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

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

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

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


4

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

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

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


3

鉴于supercat的评论,现代编译器的发展趋势以及其他因素,我很想回到远古时代,将整个代码编写到汇编和静态内存分配中的任何地方。对于这种绝对的可靠性,我认为组装不再引起很大的成本差异。


我是汇编语言的忠实拥护者(从我对其他问题的回答中可以看出),但是我认为这不是一个很好的答案。很有可能知道大多数C代码对编译器有什么期望(就寄存器与内存中的值而言),并且您始终可以检查它是否符合您的期望。即使您拥有非常熟悉编写ARM asm的开发人员,使用asm 手写大型项目也只是大量的额外工作。也许如果您想做类似计算3次相同结果的工作,那么在asm中编写一些函数就很有意义。(编译器会对其进行CSE处理)
Peter Cordes

否则,必须平衡较高的风险,即升级编译器可能会给您带来意想不到的更改。
约书亚

1

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

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

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

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

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

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

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

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

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

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


抱歉,但问题是放射性环境会发生硬件故障。您的答案是关于常规软件优化以及如何查找错误的。但是在这种情况下,错误不是由错误引起的
jeb

是的,您也可以责怪地球重力,编译器优化,第三方库,放射性环境等。但是您确定不是您自己的错误吗?:-)除非经过证实-我不相信。我曾经跑过一些固件更新和测试电源关闭情况-我的软件只有在修复了所有错误后才在所有电源关闭情况下都可以幸存。(夜间关闭电源超过4000次)但是很难相信在某些情况下存在错误。尤其是当我们谈论内存损坏时。
TarmoPikaro '16

0

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

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

甚至我们的台式机PC内存都有一定的故障率,但这并不影响日常工作。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.