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

roo*_*ook 1414 c c++ embedded gcc fault-tolerance

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

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

Ian*_*Ian 794

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

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

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

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

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

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

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

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

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

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

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

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

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

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

  2. 过滤ADC读数.千万不能使用ADC直接读取.通过中值滤波器,均值滤波器或任何其他滤波器对其进行滤波 - 绝不相信单个读数值.样品更多,而不是更少 - 合理.


rsj*_*ffe 394

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

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

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

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

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

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

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

而且,恰巧也是如此,NASA已经将C++用于Mars Rover等重大项目.

C++类抽象和封装支持多个项目和开发人员之间的快速开发和测试.

他们避免了可能产生问题的某些C++功能:

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

  • @PyRulez:纯语言是一种抽象,硬件并不纯粹.编译器非常善于隐藏差异.如果你的程序有一个值,逻辑上不应该在步骤X之后使用它,编译器可能会用在步骤X + 1中计算的值覆盖它.但这意味着你不能回去.更正式地说,纯语言中程序的可能状态形成非循环图,这意味着两个状态是等价的,并且当两者可达的状态相等时可以合并.这种合并破坏了导致这些状态的路径的差异. (63认同)
  • 这实际上听起来像[纯](https://en.wikipedia.org/wiki/Purely_functional)语言擅长的东西.由于值永远不会改变,如果它们被损坏,你可以回到原来的定义(这应该是它应该是什么),并且你不会意外地做同样的事情两次(因为没有副作用). (28认同)
  • RAII是一个坏主意,因为你无法依赖它正常运行甚至根本不依赖它.它可能会随机损坏您的数据等.您真的希望获得尽可能多的不变性,以及纠错机制.抛弃破碎的东西要比以某种方式尝试修复它们要容易得多(你究竟知道怎么回到正确的旧状态?).你可能想要使用一种相当愚蠢的语言 - 优化可能比他们帮助更多. (20认同)
  • @DeerSpotter确切的问题远不止于此。电离会损坏正在运行的观察程序的某些位。然后,您将需要一个观察者的观察者,然后-一个观察者的观察者等等。 (3认同)
  • @Vorac - 根据演示文稿,对 C++ 模板的关注是代码膨胀。 (2认同)

Art*_*ius 115

以下是一些想法和想法:

更有创意地使用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个或更多设备,您可以使用"投票"系统来尝试识别哪个设备已被入侵.

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

Eri*_*ers 102

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

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

该论文的最新后代是Bosilca,Delmas,Dongarra和Langou的" 基于算法的容错应用于高性能计算 ".

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

Lun*_*din 40

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

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

  • 使用日常"面包和黄油"安全措施,应该出现在任何半专业嵌入式系统上:内部看门狗,内部低压检测,内部时钟监视器.这些东西甚至不需要在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"启动代码,以便在编写依赖于此类的代码时出现链接器错误.许多编译器可以选择跳过这个,通常称为"最小/快速启动"或类似.

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

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

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


sup*_*cat 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();
Run Code Online (Sandbox Code Playgroud)

如果编译器以相对字面的方式转换代码,并且如果系统状态的所有检查都在之后重复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编译器中则不然.这种编译器在这种环境中可能非常危险,因为他们努力只使用代码,这些代码在通过某种明确定义的机制可能产生的情况下是相关的,并且其结果后果也将被很好地定义.在某些情况下,其目的是在故障后检测和清理的代码最终会使事情变得更糟.如果编译器确定尝试恢复在某些情况下会调用未定义的行为,则可能会推断出在这种情况下不太可能发生需要进行此类恢复的条件,从而消除了将检查它们的代码.

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

Dmi*_*yev 28

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

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

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

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

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


Old*_*ank 27

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

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

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

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

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

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

  • 采用TTL标准IC的二进制计数器看门狗确实是20世纪80年代的解决方案.不要那样做.今天,市场上没有一个没有内置看门狗电路的MCU.您需要检查的是内置看门狗是否具有单独的时钟源(很好,很可能是这种情况),或者它是否从系统时钟(坏)继承其时钟. (9认同)
  • @Peter Mortensen请在这个问题的每个答案上停止编辑狂欢.这不是维基百科,这些链接没有帮助(我相信每个人都知道如何找到维基百科......).您的许多编辑都不正确,因为您不知道该主题.当我遇到它们时,我正在对你的错误编辑进行回滚.你没有把这个线程变得更好,但更糟.停止编辑. (5认同)
  • 顺便提一下,它仍广泛用于嵌入式处理器. (2认同)

abl*_*igh 23

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

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

以下是一些想法:

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

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

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

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

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

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

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

不会做的事情:

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

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

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

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

  • 在关键任务应用程序中使用以太网可能不是一个好主意.在PCB本身之外,I2C也不是.像CAN这样坚固的东西会更合适. (2认同)

jkf*_*ing 22

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

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

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

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

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

gab*_*ous 15

免责声明:我不是放射性专业人员,也不是这种应用程序的工作人员.但我致力于软错误和冗余,以便对关键数据进行长期存档,这在某种程度上是相互关联的(同样的问题,不同的目标).

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

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

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

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

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

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

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

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

  • Ferraro-Petrillo,U.,Grandoni,F.,&Italiano,GF(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计算给出相同输出的概率是无穷小的(因为有大量可能的输出,所以几乎不可能如果x> 3,则随机获得相同的3倍,甚至更少的机会.

因此,通过多数投票,您可以安全地避免输出损坏,并且冗余x == 3,您可以恢复1个错误(x == 4,它将是2个可恢复的错误,等等 - 确切的等式是nb_error_recoverable == (x-2)x是数字重复计算,因为你需要至少2个同意的计算才能使用多数投票来恢复).

缺点是你需要计算x次而不是一次,所以你有额外的计算成本,但是线性复杂性,所以渐渐地你不会因为你获得的好处而损失太多.进行多数投票的快速方法是计算阵列上的模式,但您也可以使用中值滤波器.

此外,如果您想进一步确保计算正确进行,如果您可以制作自己的硬件,则可以使用x CPU构建设备,并连接系统,以便在x CPU中自动复制计算,并进行多数投票机械地结束(例如使用AND/OR门).这通常在飞机和关键任务设备中实现(参见三重模块冗余).这样,您就不会有任何计算开销(因为额外的计算将并行完成),并且您还有另一层软错误保护(因为计算重复和多数投票将由硬件直接管理,而不是由软件 - 由于程序只是存储在内存中的位,因此更容易被破坏...).


Jon*_*röm 9

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

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

编辑:从评论中我觉得有必要澄清"CRC的想法".如果您使用CRC或对来自主设备的随机数据进行摘要检查来包围凸起,那么奴隶碰撞它自己的看门狗的可能性接近于零.当受到严格审查的奴隶与其他人对齐时,该随机数据仅从主人发送.每次碰撞后立即清除随机数据和CRC /摘要.主从凸点频率应该是看门狗超时的两倍以上.从主站发送的数据每次都是唯一生成的.

  • 我试图了解一个场景,你可以在辐射环境之外拥有一个主人,能够与辐射环境中的奴隶可靠地通信,在那里你不能只把奴隶放在辐射环境之外. (7认同)
  • 话虽如此,如果您有这样的选择,那么尽可能简单地将程序"外包"到一个较少暴露的地方,同时保持放射性环境中的电子设备尽可能简单,这当然是很好的. (5认同)
  • 引入主人不会自动意味着增加安全性.如果奴隶x由于内存损坏而变得疯狂,那么它反复告诉自己"主人在这里,主人很高兴",那么主人没有数量的CRC或咆哮的命令会保存它.你必须让主人有可能削减那个奴隶的力量.如果您有共同原因错误,添加更多从站将不会增加安全性.还要记住,软件错误的数量和可能破坏的事物数量会随着复杂性而增加. (4认同)

Gra*_*ham 8

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

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

还要检查嵌入式设备上的勘误表.您可能会发现有一些关于"不要这样做因为它会崩溃,所以启用该编译器选项和编译器将解决它".

简而言之,您最可能的崩溃源是代码中的错误.直到你完全确定情况并非如此,不要担心(还)更多深奥的失败模式.


ren*_*ren 7

如何运行应用程序的许多实例.如果崩溃是由于随机内存位更改造成的,那么您的某些应用实例可能会通过并生成准确的结果.对于具有统计背景的人来说,计算你需要多少个实例可能很容易,只需要根据你的意愿实现微小的整体误差.

  • 当然,嵌入式系统在强大的应用程序的一个实例中更喜欢安全关键捕获,而不仅仅是解雇几个实例,提高硬件要求,并且在某种程度上希望至少有一个实例能够通过好运而失败?我明白这个想法并且它是有效的,但更倾向于不依赖蛮力的建议 (2认同)

BЈо*_*вић 7

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

正如评论中所见,不可能100%地修复硬件问题,但是可能有可能通过各种技术来减少或捕获它们.

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

  • 或者更确切地说,阅读技术要求并实施那些有意义的要求.SIL标准的很大一部分是无稽之谈,如果你教条地遵循它们,你将会得到不安全和危险的产品.今天的SIL认证主要是关于生产大量文件,然后贿赂测试机构.SIL级别没有说明系统的实际安全性.相反,您需要关注实际的技术安全措施.SIL文档中有一些非常好的文档,并且有一些完全无意义的文档. (11认同)

Hit*_*tul 7

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

在此输入图像描述

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

  • 我想知道他们为什么不在太空中使用穿卡读卡器是有原因的. (7认同)
  • @Soren速度和物理空间可能是一个原因. (3认同)
  • 也许诸如CD-ROM之类的光学介质将满足该定义.它会增加大容量的额外奖励. (2认同)
  • 是的,它会是类似的,但光盘将使用较小但这将是完全机械系统. (2认同)

Ale*_*x C 7

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


chi*_*ill 7

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

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

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

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

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


MrB*_*rth 5

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

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

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


Ger*_*ard 5

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

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

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

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