我们正在编译一个嵌入式C++应用程序,它部署在电离辐射轰击的环境中的屏蔽设备中。我们正在为ARM使用GCC和交叉编译。在部署时,我们的应用程序会生成一些错误的数据,并且比我们希望的更频繁地崩溃。硬件是为这个环境而设计的,我们的应用程序已经在这个平台上运行了几年。
我们是否可以对代码进行更改,或对编译时进行改进,以识别/纠正由单个事件扰乱导致的软错误和内存损坏?是否有其他开发人员成功地减少了软错误对长时间运行的应用程序的有害影响?
我们正在编译一个嵌入式C++应用程序,它部署在电离辐射轰击的环境中的屏蔽设备中。我们正在为ARM使用GCC和交叉编译。在部署时,我们的应用程序会生成一些错误的数据,并且比我们希望的更频繁地崩溃。硬件是为这个环境而设计的,我们的应用程序已经在这个平台上运行了几年。
我们是否可以对代码进行更改,或对编译时进行改进,以识别/纠正由单个事件扰乱导致的软错误和内存损坏?是否有其他开发人员成功地减少了软错误对长时间运行的应用程序的有害影响?
当前回答
使用C语言编写在这种环境中表现稳健的程序是可能的,但前提是大多数形式的编译器优化都被禁用。优化编译器旨在用“更高效”的编码模式替换许多看似冗余的编码模式,并且可能不知道当编译器知道x不可能保持任何其他值时,程序员测试x==42的原因是因为程序员想要阻止执行某些代码,而x保持某个其他值——即使在这样的情况下,它保持该值的唯一方法是系统接收到某种电气故障。
将变量声明为易失性通常很有用,但可能不是万能药。特别重要的是,注意安全编码通常需要操作具有需要多个步骤来激活的硬件联锁,并且使用以下模式编写代码:
... 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(),但如果没有后续故障事件,则代码将无法在未通过验证检查或先调用cancel_preparies的情况下到达trigger_activation()[如果堆栈出现问题,则在调用prepare_for_activation()的上下文返回后,执行可能会继续到trigger_active()之前的某个位置,但调用cancel_preparations(从而使后者的调用无害。
这样的代码在传统的C语言中可能是安全的,但在现代的C编译器中却不安全。这种编译器在这种环境中可能非常危险,因为它们努力只包含通过某种定义良好的机制可能出现的情况下相关的代码,并且其结果也将得到很好的定义。在某些情况下,旨在检测和清理故障的代码可能会使情况变得更糟。如果编译器确定尝试的恢复在某些情况下会调用未定义的行为,则可能推断在这种情况下不可能出现需要恢复的条件,从而消除了检查这些条件的代码。
其他回答
也许了解一下硬件“为这种环境而设计”意味着什么会有所帮助。它如何纠正和/或指示SEU错误的存在?
在一个与空间探索相关的项目中,我们有一个自定义MCU,它会在SEU错误时引发异常/中断,但会有一些延迟,即在导致SEU异常的insn之后可能会通过一些循环/执行一些指令。
数据缓存尤其容易受到攻击,因此处理程序会使有问题的缓存行无效并重新启动程序。只是,由于异常的不精确性,以引发异常的insn为首的insn序列可能无法重新启动。
我们确定了危险的(不可重启的)序列(如lw$3,0x0($2),然后是insn,它修改了$2,数据不依赖于$3),我对GCC进行了修改,所以这样的序列不会发生(例如,作为最后的手段,用nop分隔两个insn)。
只是需要考虑的事情。。。
考虑到超级跑车的评论、现代编译器的趋势以及其他因素,我很想回到古代,用汇编和静态内存分配的方式到处编写整个代码。对于这种完全的可靠性,我认为组装不再会带来很大的成本差异。
这个答案假设你关心的是一个工作正常的系统,而不是一个成本最低或速度快的系统;大多数玩放射性物品的人都看重正确性/安全性而不是速度/成本
有几个人建议您可以进行硬件更改(很好,答案中已经有很多好东西,我不打算重复所有内容),还有一些人建议冗余(原则上很好),但我认为没有人建议冗余在实践中如何工作。你怎么会失败?你怎么知道什么时候出了问题?许多技术都是在一切都会成功的基础上工作的,因此失败是一件棘手的事情。然而,一些为规模而设计的分布式计算技术预计会出现故障(毕竟,规模足够大,多个节点中的一个节点的故障是不可避免的,单个节点的平均无故障时间为MTBF);你可以利用它来保护你的环境。
以下是一些想法:
确保整个硬件复制n次(其中n大于2,最好是奇数),并且每个硬件元素可以与其他硬件元素通信。以太网是实现这一点的一种明显方式,但还有许多其他更简单的路由可以提供更好的保护(例如CAN)。尽量减少常见组件(甚至电源)。例如,这可能意味着在多个地方对ADC输入进行采样。确保应用程序状态在一个地方,例如在有限状态机中。这可以完全基于RAM,但并不排除稳定的存储。因此,它将存储在几个地方。对状态变化采用仲裁协议。例如,请参见RAFT。当您在C++中工作时,有一些众所周知的库可以实现这一点。只有当大多数节点同意时,才能对FSM进行更改。为协议堆栈和仲裁协议使用一个已知的好库,而不是自己滚动一个,否则当仲裁协议挂断时,您在冗余方面的所有好工作都将被浪费。确保您对FSM进行校验和(例如,CRC/SHA),并将CRC/CHA存储在FSM本身中(以及在消息中传输,并对消息本身进行校验和)。让节点定期对照这些校验和、传入消息的校验和检查其FSM,并检查其校验和是否与仲裁的校验和匹配。在系统中构建尽可能多的其他内部检查,使检测到自身故障的节点重新启动(这比在有足够节点的情况下继续半工作要好)。尝试让他们在重新启动过程中彻底退出仲裁,以防他们再次出现。在重新启动时,让他们检查软件映像(以及他们加载的任何其他内容),并在重新引入仲裁之前进行完整的RAM测试。使用硬件支持您,但要小心操作。例如,您可以获取ECC RAM,并定期对其进行读/写,以纠正ECC错误(如果错误无法纠正,则会死机)。然而(从内存来看)静态RAM比DRAM更能耐受电离辐射,因此最好使用静态DRAM。请参见“我不会做的事情”下的第一点。
假设您在一天内任何给定节点都有1%的失败机会,假设您可以使失败完全独立。如果有5个节点,一天内需要3个节点失败,这是0.00001%的概率。有了更多,你就明白了。
我不会做的事情:
低估了一开始没有问题的价值。除非重量是一个问题,否则你的设备周围的一大块金属将是一个比程序员团队所能想到的更便宜、更可靠的解决方案。同样,EMI输入的光学耦合也是一个问题,等等。无论怎样,在采购部件时,都要尽量选择那些抗电离辐射性能最好的部件。使用自己的算法。人们以前也做过这种事。利用他们的工作。容错和分布式算法很难。尽可能利用他人的工作。使用复杂的编译器设置,天真地希望您检测到更多失败。如果你运气好,你可能会发现更多的失败。更有可能的是,您将在编译器中使用一个测试较少的代码路径,特别是如果您自己滚动的话。使用在您的环境中未经测试的技术。大多数编写高可用性软件的人必须模拟故障模式,以检查其HA是否正常工作,并因此错过了许多故障模式。你处于“幸运”的境地,经常按需出现故障。因此,测试每种技术,并确保其应用程序实际提高MTBF的数量超过引入它的复杂性(复杂性带来了bug)。特别是将此应用于我的建议重新仲裁算法等。
您可能还对有关算法容错的丰富文献感兴趣。这包括旧的赋值:编写一个排序,当恒定数量的比较将失败时(或者,更糟糕的版本,当失败的比较的渐近数量为n次比较的log(n)时),正确地对其输入进行排序。
开始阅读黄和亚伯拉罕1984年的论文《矩阵运算的基于算法的容错》。他们的想法隐约类似于同态加密计算(但实际上并不相同,因为他们正在尝试在操作级别进行错误检测/纠正)。
该论文的一个较新的后代是Bosilca、Delmas、Dongarra和Langou的“基于算法的容错应用于高性能计算”。
你需要3台以上的从机,在辐射环境外有一台主机。所有I/O都通过包含表决和/或重试机制的主机。每个从设备必须有一个硬件监视器,并且撞击它们的调用应该被CRC等包围,以降低非自愿撞击的概率。转发应该由主机控制,因此与主机的连接丢失等于几秒钟内重新启动。
此解决方案的一个优点是,您可以对主机和从机使用相同的API,因此冗余成为一种透明的特性。
编辑:从评论中,我觉得有必要澄清“CRC的想法”。如果你用CRC来围绕碰撞,或者对来自主设备的随机数据进行摘要检查,那么从设备碰撞它自己的看门狗的可能性接近于零。只有当受监视的从设备与其他设备对齐时,才从主设备发送随机数据。随机数据和CRC/摘要在每次碰撞后立即清除。主从缓冲频率应超过看门狗超时的两倍。每次从主机发送的数据都是唯一生成的。