我们正在编译一个嵌入式C / C ++应用程序,该应用程序部署在受到电离辐射轰击的环境中的屏蔽设备中。我们正在使用GCC并为ARM进行交叉编译。部署后,我们的应用程序会生成一些错误数据,并且崩溃的次数比我们想要的要多。硬件是为此环境设计的,我们的应用程序已在该平台上运行了几年。
我们是否可以对代码进行更改,或者可以进行编译时改进以识别/纠正由单个事件引发的软错误和内存损坏?其他开发人员是否在减少软错误对长期运行的应用程序的有害影响方面取得了成功?
我们正在编译一个嵌入式C / C ++应用程序,该应用程序部署在受到电离辐射轰击的环境中的屏蔽设备中。我们正在使用GCC并为ARM进行交叉编译。部署后,我们的应用程序会生成一些错误数据,并且崩溃的次数比我们想要的要多。硬件是为此环境设计的,我们的应用程序已在该平台上运行了几年。
我们是否可以对代码进行更改,或者可以进行编译时改进以识别/纠正由单个事件引发的软错误和内存损坏?其他开发人员是否在减少软错误对长期运行的应用程序的有害影响方面取得了成功?
Answers:
我致力于软件/固件开发和小型卫星的环境测试* 大约4-5年,我想在这里分享我的经验。
*(小型卫星由于其电子元件的尺寸相对较小且尺寸有限,因此比大型卫星更容易发生单事件干扰)
非常简洁和直接:没有机制歇着检测,错误的情况由软件/固件本身没有,至少,一个 复制的最低工作版本的软件/固件的某处为恢复目的-和与硬件配套恢复(功能性)。
现在,通常在硬件和软件级别都可以处理这种情况。在这里,根据您的要求,我将分享我们在软件级别上可以做的事情。
...恢复目的...。提供在真实环境中更新/重新编译/重新刷新软件/固件的功能。对于高度电离的环境,这是几乎所有软件/固件都必须具备的功能。否则,您可能会拥有任意数量的冗余软件/硬件,但有一点,它们都将崩溃。因此,准备此功能!
...最低工作版本...在代码中具有响应性的多个副本,最低版本的软件/固件。这就像Windows中的安全模式。拥有一个最低功能版本的软件/固件,而不是仅拥有一个功能完整的软件版本。最小副本的大小通常比完整副本小得多,并且几乎总是只有以下两个或三个功能:
...在某处复制...在某处有冗余软件/固件。
无论有没有冗余硬件,您都可以尝试在ARM uC中拥有冗余软件/固件。通常,这是通过在单独的地址中使用两个或多个相同的软件/固件来相互发送心跳来完成的,但是一次只能激活一个。如果已知一个或多个软件/固件无响应,请切换到其他软件/固件。使用这种方法的好处是,发生错误后,我们可以立即进行功能替换-无需与负责检测和修复错误的任何外部系统/当事方进行任何联系(在卫星情况下,通常是任务控制中心( MCC))。
严格来说,没有冗余硬件,这样做的缺点是您实际上无法消除所有单点故障。至少,您仍然会有一个单点故障,这就是开关本身(或者通常是代码的开头)。但是,对于在高度电离的环境中受尺寸限制的设备(例如,微微/毫微微卫星),仍然需要考虑将单点故障减少到一个点而无需其他硬件。此外,用于切换的代码段肯定会比整个程序的代码段少得多-大大降低了获得单个事件的风险。
但是,如果您不这样做,则您的外部系统中应该至少有一个副本,可以与该设备联系并更新软件/固件(在卫星情况下,它又是任务控制中心)。
...可检测的错误情况。错误必须是可检测的,通常是通过硬件错误纠正/检测电路或一小段代码来进行错误纠正/检测。最好将此类代码缩小,多个并独立于主软件/固件。其主要任务仅用于检查/更正。如果硬件电路/固件可靠(例如,其辐射强度比其余辐射更强-或具有多个电路/逻辑),那么您可以考虑对其进行纠错。但是,如果不是这样,最好将其作为错误检测。可以通过外部系统/设备进行更正。对于纠错,您可以考虑使用诸如Hamming / Golay23之类的基本纠错算法,因为它们可以在电路/软件中更轻松地实现。但这最终取决于您团队的能力。对于错误检测,通常使用CRC。
...支持恢复的硬件现在,这是这个问题上最困难的方面。最终,恢复需要负责恢复的硬件至少能够正常运行。如果硬件永久损坏(通常在其总电离剂量达到一定水平后发生),则该软件将(很难)无法帮助恢复。因此,对于暴露于高辐射水平的设备(例如卫星),硬件无疑是最重要的问题。
除了上述可预见的由于单事件失败而导致的固件错误的建议之外,我还建议您具有:
子系统间通信协议中的错误检测和/或错误校正算法。为了避免从其他系统接收到不完整/错误的信号,这是另一个几乎必须具备的条件
过滤ADC读数。千万不能使用ADC直接读取。用中值过滤器,均值过滤器或任何其他过滤器过滤它- 永远不要相信单个读数值。采样更多而不是更少-合理。
美国国家航空航天局(NASA)撰写了一篇有关防辐射软件的论文。它描述了三个主要任务:
请注意,内存扫描速率应足够频繁,以至于很少会发生多位错误,因为大多数ECC内存可以从单位错误而非多位错误中恢复。
强大的错误恢复包括控制流传输(通常在错误发生之前的某个时刻重新启动进程),资源释放和数据恢复。
他们对数据恢复的主要建议是,通过将中间数据视为临时数据,从而避免了对数据的需求,以便在错误之前重新启动也会将数据回滚到可靠状态。这听起来类似于数据库中“事务”的概念。
他们讨论了特别适用于面向对象语言(例如C ++)的技术。例如
而且,正是这种情况,NASA已将C ++用于诸如Mars Rover之类的大型项目。
C ++类抽象和封装可在多个项目和开发人员之间进行快速开发和测试。
他们避免使用某些可能导致问题的C ++功能:
new
和delete
除外)new
以避免系统堆损坏的可能性)。这里有一些想法和想法:
更创造性地使用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台以上的设备,您可以使用“投票”系统来尝试确定哪些设备已受到威胁。
您可能也对关于算法容错的丰富文献感兴趣。这包括旧的作业:写那种正确排序其输入时比较恒定的数量将失败(或者稍微更邪恶的版本,当失败的比较级表的渐近数log(n)
为n
比较)。
Huang和Abraham在1984年发表的论文“ 矩阵运算的基于算法的容错能力 ”是一个开始阅读的地方。 ”。他们的想法大概与同态加密计算相似(但是实际上并不太一样,因为他们正在尝试在操作级别进行错误检测/纠正)。
该论文的最新版本是Bosilca,Delmas,Dongarra和Langou的“ 应用于高性能计算的基于算法的容错能力 ”。
为放射性环境编写代码与为任何关键任务应用程序编写代码实际上没有什么不同。
除了已经提到的内容以外,这里还有一些其他提示:
重要信息:您必须确保内部MCU寄存器的完整性。所有可写的硬件外设的控制和状态寄存器都可能位于RAM内存中,因此容易受到攻击。
为了防止寄存器损坏,最好选择具有内置“一次性写入”功能的微控制器。此外,您需要将所有硬件寄存器的默认值存储在NVM中,并定期将这些值复制到您的寄存器中。您可以用相同的方式确保重要变量的完整性。
注意:始终使用防御性编程。这意味着您必须设置MCU中的所有寄存器,而不仅仅是应用程序使用的寄存器。您不希望某些随机的硬件外设突然唤醒。
检查RAM或NVM中的错误的方法有很多种,包括校验和,“移动模式”,软件ECC等。当今最好的解决方案是不使用其中任何一种,而是使用具有内置ECC和类似的检查。由于在软件中执行此操作很复杂,因此错误检查本身可能会引入错误和意外问题。
了解并接受防御性编程的概念。这意味着您的程序需要处理所有可能的情况,即使是理论上不可能发生的情况。例子。
高质量的关键任务固件会检测到尽可能多的错误,然后以安全的方式将其忽略。
重要信息:不要依赖静态存储持续时间变量的默认值。也就是说,不要相信.data
或的默认内容.bss
。从初始化到实际使用变量之间可能有任何时间,可能会有很多时间使RAM损坏。而是编写程序,以便在运行时从NVM设置所有此类变量,就在首次使用此类变量之前。
实际上,这意味着如果在文件作用域或as处声明了变量,则static
永远不要使用=
它进行初始化(或者可以,但是它是没有意义的,因为无论如何您都不能依赖该值)。始终在使用前在运行时进行设置。如果可以从NVM重复更新此类变量,则可以这样做。
同样在C ++中,不要依赖于构造函数来获取静态存储持续时间变量。让构造函数调用公共的“设置”例程,您也可以稍后在运行时直接从调用者应用程序中调用该例程。
如果可能,请删除“ copy-down”启动代码以完全初始化.data
并.bss
(并调用C ++构造函数),以便在编写依赖此代码的代码时出现链接器错误。许多编译器可以跳过此选项,通常称为“最小/快速启动”或类似的选项。
这意味着必须检查所有外部库,以便它们不包含任何此类依赖。
为程序实现并定义一个安全状态,以防万一出现严重错误,您将还原到该状态。
%01010101010101010101010101010101
,然后进行XOR或POPCNT?
.text
部分中翻转一点,从而更改操作码或类似内容。
仅在大多数形式的编译器优化被禁用的情况下,才可以使用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编译器中则不是。这样的编译器在那种环境下可能非常危险,因为激进的编译器会努力只包含与某些情况相关的代码,这些情况可能通过某种定义良好的机制发生,并且其后果也将得到很好的定义。在某些情况下,目的是要在故障后进行检测和清除的代码可能最终使情况变得更糟。如果编译器确定尝试进行的恢复在某些情况下将调用未定义的行为,则它可以推断出在这种情况下可能需要进行这种恢复的条件不可能发生,从而消除了要检查它们的代码。
-O0
或提供等效转换?如果您授予 GCC 许可,GCC会做很多奇怪的事情,但是如果您要求它不这样做,通常也可以做到字面上的意思。
-O2
。
-O0
不好的主意的第二个原因是因为它发出了更多无用的指令。示例:非内联调用包含保存寄存器,进行调用,恢复寄存器的指令。所有这些都会失败。不存在的指令不会失败。
-O0
不好的主意是:它倾向于将变量存储在内存中而不是寄存器中。现在还不能确定内存更容易受到SEU的影响,但是传输中的数据比静止数据更容易受到影响。应避免无用的数据移动,并在此方面提供-O2
帮助。
v1=v2+0xCAFEBABE
的变量(例如,对两个变量的所有更新都已完成……
这是一个极其广泛的主题。基本上,您无法真正从内存损坏中恢复,但是至少可以尝试立即失败。您可以使用以下几种技巧:
校验和常量数据。如果您有任何长时间保持不变的配置数据(包括已配置的硬件寄存器),请在初始化时计算其校验和并定期进行验证。当看到不匹配时,该重新初始化或重置了。
冗余存储变量。如果你有一个重要的变量x
,写出它的价值x1
,x2
并且x3
和它读成(x1 == x2) ? x2 : x3
。
实行 程序流监控。在主循环调用的重要函数/分支中,对具有唯一值的全局标志进行XOR。在测试覆盖率接近100%的无辐射环境中运行程序,应在周期结束时为您提供标志的可接受值列表。如果发现偏差,请重设。
监视堆栈指针。在主循环的开始,将堆栈指针与其预期值进行比较。偏差重设。
可以帮助您的是看门狗。看门狗在1980年代被广泛用于工业计算。当时,硬件故障要普遍得多-另一个答案也就是那个时期。
看门狗是硬件/软件的组合功能。硬件是一个简单的计数器,它从一个数字(例如1023)递减到零。可以使用TTL或其他逻辑。
该软件的设计使得一个例程可以监视所有基本系统的正确运行。如果此例程正确完成=发现计算机运行正常,则它将计数器设置回1023。
总体设计是,在正常情况下,软件可防止硬件计数器达到零。如果计数器达到零,则计数器的硬件将执行其唯一任务并重置整个系统。从计数器的角度来看,零等于1024,并且计数器再次继续递减计数。
该监视程序可确保在许多很多情况下都可以重新启动连接的计算机。我必须承认,我对能够在当今计算机上执行此类功能的硬件不熟悉。与外部硬件的接口现在比以前复杂得多。
看门狗的一个固有缺点是,从系统出现故障直到看门狗计数器达到零+重新启动时间,系统才可用。尽管该时间通常比任何外部或人工干预要短得多,但在该时间范围内,受支持的设备将需要能够在没有计算机控制的情况下继续运行。
该答案假定您关心的是拥有一个正常运行的系统,而不是拥有成本最低或速度最快的系统。大多数玩放射性物质的人都重视正确性/安全性,而不是速度/成本
有几个人建议您可以进行硬件更改(很好-答案中已经有很多不错的东西,我不打算重复所有这些内容),还有其他人建议了冗余(原则上很好),但是我不认为任何人都建议过这种冗余在实践中将如何工作。您如何进行故障转移?您怎么知道什么时候“出了错”?许多技术都是在一切正常的基础上工作的,因此失败是一件棘手的事情。但是,一些为规模化而设计的分布式计算技术可能会出现故障(毕竟,如果规模足够大,那么对于单个节点而言,任何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)。尤其将其应用于我的建议定额算法等。
由于您专门要求软件解决方案,并且您正在使用C ++,为什么不使用运算符重载来创建自己的安全数据类型?例如:
而不是使用的uint32_t
(和double
,int64_t
等等),让你自己SAFE_uint32_t
包含uint32_t的倍数(3最小值)。重载要执行的所有操作(* +-/ << >> = ==!=等),并使重载的操作对每个内部值独立执行,即,不要一次执行并复制结果。在之前和之后,请检查所有内部值是否匹配。如果值不匹配,则可以将错误的一个更新为最常见的一个。如果没有最常用的值,则可以安全地通知您有错误。
这样,无论ALU,寄存器,RAM或总线上是否发生损坏,您都将有多次尝试,而且很有可能捕获错误。但是请注意,尽管这仅适用于您可以替换的变量-例如,您的堆栈指针仍然容易受到影响。
附带说明:我在旧的ARM芯片上也遇到了类似的问题。原来,这是一个使用旧版GCC的工具链,它与我们使用的特定芯片一起在某些极端情况下触发了一个错误,该错误会(有时)破坏值传递给函数。在将其归咎于放射性之前,请确保您的设备没有任何问题,是的,有时它是编译器错误=)
免责声明:我不是放射性专业人士,也没有从事此类应用程序的工作。但是我为长期存档关键数据进行了软错误和冗余工作,这些工作在某种程度上是相互联系的(相同的问题,不同的目标)。
我认为放射性的主要问题是放射性可以切换位,因此放射性可以/将篡改任何数字存储器。这些错误通常称为软错误,位腐烂等。
然后的问题是:当内存不可靠时如何可靠地计算?
要显着降低软错误率(由于主要是基于软件的解决方案,而以计算开销为代价),您可以:
依靠良好的旧冗余方案,尤其是更有效的纠错码(目的相同,但算法更聪明,因此您可以用更少的冗余恢复更多的位)。有时(错误地)也称为校验和。使用这种解决方案,您必须随时将程序的完整状态存储在主变量/类(或结构?)中,计算ECC,并在执行任何操作之前检查ECC是否正确,以及是否不,修理田野。但是,此解决方案不能保证您的软件可以正常工作(简单地,它可以在可以正常工作时正常运行,否则不能正常运行,因为ECC可以告诉您是否出了问题,在这种情况下,您可以停止软件以便于不要得到假的结果)。
或者你可以使用 弹性算法数据结构,这在一定程度上保证了即使存在软错误,您的程序仍将给出正确的结果。这些算法可以看作是普通算法结构与本地混入的ECC方案的混合,但是它具有更大的弹性,因为弹性方案与该结构紧密绑定,因此您无需编码其他过程检查ECC,通常速度要快得多。这些结构提供了一种方法,可确保您的程序在任何条件下都可以运行,直到软错误的理论极限。您也可以将这些弹性结构与冗余/ ECC方案混合使用,以提高安全性(或将最重要的数据结构编码为弹性数据,其余的可以从主数据结构重新计算的消耗性数据进行编码,
如果您对弹性数据结构(这是算法学和冗余工程学中的一个新近但令人兴奋的新领域)感兴趣,建议您阅读以下文档:
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变量:
与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门)。这通常在飞机和关键任务设备中实现(请参阅三重模块冗余)。这样,您将没有任何计算开销(因为额外的计算将并行进行),并且您拥有另一层保护免受软错误的影响(因为计算重复和多数表决将直接由硬件而不是由硬件管理)软件-由于程序只是存储在内存中的位而容易被破坏...)。
似乎没有人提到过这一点。您说您正在使用GCC进行开发并交叉编译到ARM。您怎么知道您没有代码对自由RAM,整数大小,指针大小,执行某项操作需要花费多长时间,系统将连续运行多长时间或诸如此类的假设?这是一个非常普遍的问题。
答案通常是自动化的单元测试。编写测试工具,在开发系统上执行代码,然后在目标系统上运行相同的测试工具。寻找差异!
还要检查嵌入式设备上的勘误。您可能会发现“不要这样做,因为它会崩溃,因此启用该编译器选项,然后编译器就可以解决”。
简而言之,最有可能导致崩溃的原因是代码中的错误。在您完全确定不是这种情况之前,请不要担心(至今)更多深奥的失败模式。
您希望辐射环境外有3个以上具有主机的从属计算机。所有I / O都通过包含表决和/或重试机制的主机。从站每个必须有一个硬件看门狗,并且撞到它们的调用应该被CRC或类似的东西包围,以减少非自愿撞的可能性。碰撞应由主服务器控制,因此与主服务器的连接断开等于在几秒钟内重新启动。
该解决方案的优点之一是您可以对主服务器使用与从服务器相同的API,因此冗余成为透明的功能。
编辑:从评论中,我觉得有必要澄清“ CRC想法”。如果您用CRC包围凸起,或者对来自主器件的随机数据进行摘要检查,则从器件碰撞自己的看门狗的可能性接近于零。只有在仔细检查的从机与其他主机对齐时,才从主机发送随机数据。每次碰撞后,随机数据和CRC /摘要被立即清除。主从碰撞频率应为看门狗超时的两倍以上。从主机发送的数据每次都会唯一生成。
如何运行应用程序的许多实例。如果崩溃是由于随机的内存位更改引起的,则您的某些应用程序实例很可能会成功通过并产生准确的结果。(对于具有统计背景的人而言),很可能很容易地计算出给定的翻转概率,您需要多少个实例,以实现所需的微小总体误差。
您要问的话题很复杂-不容易回答。其他答案也可以,但是它们只涵盖了您需要做的所有事情的一小部分。
从评论中可以看出,不可能100%修复硬件问题,但是有可能使用各种技术来减少或解决这些问题。
如果您是我,则将创建最高安全完整性级别(SIL-4)的软件。获取并遵循IEC 61513(用于核工业)文档。
也许有必要知道“针对此环境设计”硬件的含义。如何纠正和/或指示SEU错误的存在?
在一个与太空探索相关的项目中,我们有一个自定义的MCU,它将引发SEU错误的异常/中断,但是会有所延迟,即,在导致SEU异常的一个insn之后,某些周期可能会通过/执行指令。
数据缓存特别容易受到攻击,因此处理程序会使无效的缓存行无效并重新启动程序。只是由于异常的不精确性,以异常引发insn为首的insns序列可能无法重新启动。
我们确定了危险的(不可重新启动的)序列(例如lw $3, 0x0($2)
,后跟一个insn,该序列修改了$2
且不依赖于数据$3
),并且我对GCC进行了修改,因此此类序列不会发生(例如,万不得已时,将由a的两个insns nop
。
只是要考虑的事情...
如果您的硬件出现故障,则可以使用机械存储来恢复它。如果您的代码库很小并且具有一定的物理空间,则可以使用机械数据存储。
材料表面将不会受到辐射的影响。多个齿轮将在那里。机械读取器将在所有齿轮上运行,并且可以灵活地上下移动。向下表示它是0,向上表示它是1。从0和1您可以生成代码库。
使用循环调度程序。这使您能够添加定期维护时间以检查关键数据的正确性。最经常遇到的问题是堆栈损坏。如果您的软件是周期性的,则可以在周期之间重新初始化堆栈。不要将堆栈重用于中断调用,请为每个重要的中断调用设置单独的堆栈。
与看门狗概念类似的是截止期限计时器。在调用函数之前,请启动硬件计时器。如果在截止期限定时器中断之前函数未返回,请重新加载堆栈,然后重试。如果3/5次尝试后仍然失败,则需要从ROM重新加载。
将您的软件分成几个部分,并隔离这些部分,以使用单独的存储区和执行时间(尤其是在控制环境中)。示例:信号采集,预设数据,主要算法和结果实现/传输。这意味着一部分故障不会导致程序其余部分的故障。因此,当我们修复信号采集时,其余任务将继续使用过时的数据。
一切都需要CRC。如果您在RAM之外执行,甚至您的.text也需要CRC。如果使用循环调度程序,请定期检查CRC。一些编译器(不是GCC)可以为每个部分生成CRC,而某些处理器具有专用的硬件来进行CRC计算,但是我想这不在您的问题范围之内。检查CRC还会提示内存中的ECC控制器在出现问题之前修复单个位错误。
首先,围绕failure设计您的应用程序。确保作为正常流程操作的一部分,它会期望重置(取决于您的应用程序以及软性或硬性故障的类型)。这很难做到完美:需要某种程度的事务性的关键操作可能需要在组装级别进行检查和调整,以使关键点处的中断不会导致不一致的外部命令。 一旦检测到任何不可恢复的内存损坏或控制流偏差,就会快速失败。尽可能记录失败。
其次,在可能的情况下,纠正腐败并继续。这意味着经常校验和固定常量表(如果可能的话,还有程序代码);可能在每个主要操作之前或在定时中断之前,并将变量存储在自动更正的结构中(再次在每个主要操作之前或在定时中断之前,从3中获得多数表决,如果是单个偏差,则进行校正)。如果可能,请记录更正。
第三,测试失败。设置可重复的测试环境,以随机方式伪装存储器中的位。这将使您能够复制损坏情况,并帮助您围绕这些情况设计应用程序。
鉴于supercat的评论,现代编译器的发展趋势以及其他因素,我很想回到远古时代,将整个代码编写到汇编和静态内存分配中的任何地方。对于这种绝对的可靠性,我认为组装不再引起很大的成本差异。
这是大量的答复,但是我将尝试总结一下我的想法。
崩溃或无法正常工作可能是您自己的错误造成的-因此,当您找到问题时,应该很容易解决。但是,也有可能发生硬件故障-并且即使不是不可能整体修复也很难。
我建议首先尝试通过记录(堆栈,寄存器,函数调用)来捕获问题情况-通过将它们记录到文件中的某个位置,或者以某种方式直接传输它们(“哦,不,我崩溃了”)。
从这种错误情况中恢复可以是重新启动(如果软件仍在运行并且可以正常运行)或硬件复位(例如,硬件看门狗)。从第一个开始比较容易。
如果问题与硬件有关,那么日志记录将帮助您确定发生在哪个函数调用中的问题,并且可以使您从内部了解什么不起作用以及在哪里。
同样,如果代码相对复杂-“分而治之”是有意义的-意味着您在怀疑问题的地方删除/禁用了一些函数调用-通常禁用一半代码,然后再启用另一半-您可以“正常工作” / “无效”决定之后,您可以专注于另一半代码。(问题出在哪里)
如果一段时间后出现问题-可以怀疑是堆栈溢出-那么最好监视堆栈点寄存器-如果它们不断增长。
并且,如果您设法完全减少代码,直到出现“ hello world”类型的应用程序-并且仍然随机失败-则可能出现硬件问题-并且需要进行“硬件升级”-意味着发明这种cpu / ram / ... -硬件组合将更好地耐受辐射。
最重要的事情可能是如果机器完全停止/重置/不工作时如何找回日志-如果发现问题情况,引导应该做的第一件事-是回到家中。
如果您的环境中也有可能发送信号并接收响应-您可以尝试构建某种在线远程调试环境,但是您必须至少使通信媒体工作,并使某些处理器/内存处于工作状态。通过远程调试,我指的是GDB / gdb存根之类的方法,还是您自己实现的从应用程序中获取的内容(例如,下载日志文件,下载调用堆栈,下载ram,重新启动)
我真的读了很多很棒的答案!
这是我的2分:通过编写软件来检查内存或执行频繁的寄存器比较,建立内存/寄存器异常的统计模型。此外,以虚拟机样式创建一个仿真器,您可以在其中试验该问题。我想如果您更改结点大小,时钟频率,供应商,机壳等,将会观察到不同的行为。
甚至我们的台式机PC内存都有一定的故障率,但这并不影响日常工作。