我们正在编译一个嵌入式C++应用程序,它部署在电离辐射轰击的环境中的屏蔽设备中。我们正在为ARM使用GCC和交叉编译。在部署时,我们的应用程序会生成一些错误的数据,并且比我们希望的更频繁地崩溃。硬件是为这个环境而设计的,我们的应用程序已经在这个平台上运行了几年。
我们是否可以对代码进行更改,或对编译时进行改进,以识别/纠正由单个事件扰乱导致的软错误和内存损坏?是否有其他开发人员成功地减少了软错误对长时间运行的应用程序的有害影响?
我们正在编译一个嵌入式C++应用程序,它部署在电离辐射轰击的环境中的屏蔽设备中。我们正在为ARM使用GCC和交叉编译。在部署时,我们的应用程序会生成一些错误的数据,并且比我们希望的更频繁地崩溃。硬件是为这个环境而设计的,我们的应用程序已经在这个平台上运行了几年。
我们是否可以对代码进行更改,或对编译时进行改进,以识别/纠正由单个事件扰乱导致的软错误和内存损坏?是否有其他开发人员成功地减少了软错误对长时间运行的应用程序的有害影响?
当前回答
如果你的硬件出现故障,你可以使用机械存储来恢复它。如果你的代码库很小,并且有一些物理空间,那么你可以使用一个机械数据存储。
材料表面不会受到辐射的影响。将有多个档位。机械读卡器将在所有齿轮上运行,并且可以灵活地上下移动。向下表示为0,向上表示为1。从0和1可以生成代码库。
其他回答
以下是一些想法和想法:
更创造性地使用ROM。
在ROM中存储任何可以存储的东西。不要计算东西,而是将查找表存储在ROM中。(确保编译器将查找表输出到只读部分!在运行时打印内存地址以进行检查!)将中断向量表存储在RAM中。当然,运行一些测试以查看ROM与RAM相比的可靠性。
为堆栈使用最好的RAM。
堆栈中的SEU可能是最可能的崩溃源,因为它是索引变量、状态变量、返回地址和各种类型的指针通常存在的地方。
执行计时器滴答声和看门狗计时器例程。
您可以在每一次计时器计时时运行一个“健全性检查”例程,以及一个看门狗例程来处理系统锁定。您的主代码还可以周期性地增加一个计数器以指示进度,而健全性检查例程可以确保发生了这种情况。
在软件中执行纠错代码。
您可以为数据添加冗余,以便能够检测和/或纠正错误。这将增加处理时间,可能会使处理器长时间暴露在辐射中,从而增加出错的机会,因此您必须考虑权衡。
记住缓存。
检查CPU缓存的大小。您最近访问或修改的数据可能位于缓存中。我相信您可以禁用至少一些缓存(以较大的性能代价);你应该试试看缓存对SEU的敏感性。如果缓存比RAM更硬,那么您可以定期读取和重新写入关键数据,以确保它保留在缓存中并使RAM恢复正常。
巧妙地使用页面错误处理程序。
如果将内存页标记为不存在,CPU将在您尝试访问它时发出页面错误。您可以创建一个页面错误处理程序,在处理读取请求之前进行一些检查。(PC操作系统使用此功能透明地加载已交换到磁盘的页面。)
对关键的事情使用汇编语言(可能是所有事情)。
使用汇编语言,您知道寄存器中的内容和RAM中的内容;你知道CPU使用的是什么特殊的RAM表,你可以用迂回的方式来设计,以降低风险。
使用objdump实际查看生成的汇编语言,并计算每个例程占用的代码量。
如果你使用的是像Linux这样的大型操作系统,那么你就是在自找麻烦;有太多的复杂性和太多的事情要出错。
记住这是一场概率游戏。
一位评论者说
你为捕捉错误而编写的每一个例程都会因同样的原因而失败。
虽然这是真的,但检查例程正确运行所需的(例如)100字节代码和数据中发生错误的机会要比其他地方发生错误的几率小得多。如果你的ROM非常可靠,并且几乎所有的代码/数据都在ROM中,那么你的可能性就更大了。
使用冗余硬件。
使用具有相同代码的两个或更多相同硬件设置。如果结果不同,应触发重置。对于3个或更多设备,您可以使用“投票”系统来尝试确定哪一个已被破坏。
这个答案假设你关心的是一个工作正常的系统,而不是一个成本最低或速度快的系统;大多数玩放射性物品的人都看重正确性/安全性而不是速度/成本
有几个人建议您可以进行硬件更改(很好,答案中已经有很多好东西,我不打算重复所有内容),还有一些人建议冗余(原则上很好),但我认为没有人建议冗余在实践中如何工作。你怎么会失败?你怎么知道什么时候出了问题?许多技术都是在一切都会成功的基础上工作的,因此失败是一件棘手的事情。然而,一些为规模而设计的分布式计算技术预计会出现故障(毕竟,规模足够大,多个节点中的一个节点的故障是不可避免的,单个节点的平均无故障时间为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)。特别是将此应用于我的建议重新仲裁算法等。
首先,围绕失败设计应用程序。确保作为正常流程操作的一部分,它需要重置(取决于您的应用程序和软或硬故障类型)。这很难做到完美:需要某种程度的事务性的关键操作可能需要在组装级别进行检查和调整,以便关键点的中断不会导致不一致的外部命令。一旦检测到任何不可恢复的内存损坏或控制流偏差,就立即失败。如果可能,记录故障。
第二,如果可能,纠正腐败并继续下去。这意味着经常检查和修复常量表(如果可以的话,还包括程序代码);可能在每个主要操作之前或在定时中断上,并将变量存储在自动校正的结构中(同样在每个主要运算之前或在计时中断上,从3中获得多数票,如果是单个偏差,则进行校正)。如果可能,记录更正。
第三,测试失败。设置一个可重复的测试环境,随机翻转内存中的位。这将允许您复制损坏情况,并帮助围绕它们设计应用程序。
您可能还对有关算法容错的丰富文献感兴趣。这包括旧的赋值:编写一个排序,当恒定数量的比较将失败时(或者,更糟糕的版本,当失败的比较的渐近数量为n次比较的log(n)时),正确地对其输入进行排序。
开始阅读黄和亚伯拉罕1984年的论文《矩阵运算的基于算法的容错》。他们的想法隐约类似于同态加密计算(但实际上并不相同,因为他们正在尝试在操作级别进行错误检测/纠正)。
该论文的一个较新的后代是Bosilca、Delmas、Dongarra和Langou的“基于算法的容错应用于高性能计算”。
考虑到超级跑车的评论、现代编译器的趋势以及其他因素,我很想回到古代,用汇编和静态内存分配的方式到处编写整个代码。对于这种完全的可靠性,我认为组装不再会带来很大的成本差异。