我们正在编译一个嵌入式C++应用程序,它部署在电离辐射轰击的环境中的屏蔽设备中。我们正在为ARM使用GCC和交叉编译。在部署时,我们的应用程序会生成一些错误的数据,并且比我们希望的更频繁地崩溃。硬件是为这个环境而设计的,我们的应用程序已经在这个平台上运行了几年。
我们是否可以对代码进行更改,或对编译时进行改进,以识别/纠正由单个事件扰乱导致的软错误和内存损坏?是否有其他开发人员成功地减少了软错误对长时间运行的应用程序的有害影响?
我们正在编译一个嵌入式C++应用程序,它部署在电离辐射轰击的环境中的屏蔽设备中。我们正在为ARM使用GCC和交叉编译。在部署时,我们的应用程序会生成一些错误的数据,并且比我们希望的更频繁地崩溃。硬件是为这个环境而设计的,我们的应用程序已经在这个平台上运行了几年。
我们是否可以对代码进行更改,或对编译时进行改进,以识别/纠正由单个事件扰乱导致的软错误和内存损坏?是否有其他开发人员成功地减少了软错误对长时间运行的应用程序的有害影响?
当前回答
免责声明:我不是放射性专业人员,也不是这类应用的工作人员。但我致力于关键数据的长期归档的软错误和冗余,这有点联系(相同的问题,不同的目标)。
在我看来,放射性的主要问题是放射性可以切换位,因此放射性可以/将篡改任何数字存储器。这些错误通常被称为软错误、比特腐烂等。
问题是:当你的内存不可靠时,如何可靠地计算?
要显著降低软错误率(以计算开销为代价,因为大多数情况下都是基于软件的解决方案),您可以:
依靠好的旧冗余方案,更具体地说,是更有效的纠错码(目的相同,但算法更聪明,这样可以用更少的冗余恢复更多的比特)。这有时(错误地)也称为校验和。使用这种解决方案,您必须随时将程序的完整状态存储在主变量/类(或结构?)中,计算ECC,并在执行任何操作之前检查ECC是否正确,如果不正确,则修复字段。然而,此解决方案不能保证您的软件能够正常工作(简单地说,它可以正常工作,否则停止工作,因为ECC可以告诉您是否有问题,在这种情况下,您可以停止软件,这样您就不会得到假结果)。或者,您可以使用弹性算法数据结构,这在一定程度上保证您的程序即使在存在软错误的情况下仍能给出正确的结果。这些算法可以看作是普通算法结构与ECC方案的混合,但这比这更具弹性,因为弹性方案与结构紧密结合,因此不需要编码额外的过程来检查ECC,而且通常速度更快。这些结构提供了一种方法,可以确保您的程序在任何条件下都能工作,直到软错误的理论范围。您还可以将这些弹性结构与冗余/ECC方案混合使用,以提高安全性(或将最重要的数据结构编码为弹性数据结构,其余的是可从主数据结构重新计算的消耗性数据,作为具有ECC或奇偶校验的正常数据结构,计算速度非常快)。
如果您对弹性数据结构感兴趣(这是一个最近但令人兴奋的算法和冗余工程领域),我建议您阅读以下文档:
罗马大学Giuseppe F.Italiano“Tor Vergata”介绍的弹性算法数据结构Christiano,P.、Demaine,E.D.和Kishore,S.(2011)。具有附加开销的无损容错数据结构。《算法和数据结构》(第243-254页)。施普林格柏林海德堡。Ferraro Petrillo,U.、Grandoni,F.和Italiano,G.F.(2013)。数据结构对记忆故障的恢复能力:词典的实验研究。实验算法杂志(JEA),18,1-6。意大利,G.F.(2010)。弹性算法和数据结构。《算法与复杂性》(第13-24页)。施普林格柏林海德堡。
如果您有兴趣了解弹性数据结构领域的更多信息,您可以查看Giuseppe F.Italiano的作品(并通过参考文献)和Fault 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之间自动复制计算,并在最后以机械方式进行多数表决(例如,使用“与/或”门)。这通常在飞机和任务关键设备中实现(参见三模块冗余)。这样,你就不会有任何计算开销(因为额外的计算将并行进行),并且你有另一层防止软错误的保护(因为计算重复和多数表决将由硬件直接管理,而不是由软件管理——因为程序只是存储在内存中的位……)。
其他回答
有一点似乎没有人提到。你说你在GCC中开发,并在ARM上交叉编译。你怎么知道你的代码中没有关于空闲RAM、整数大小、指针大小、执行某个操作需要多长时间、系统将持续运行多长时间等的假设?这是一个非常普遍的问题。
答案通常是自动单元测试。编写在开发系统上执行代码的测试线束,然后在目标系统上运行相同的测试线束。寻找差异!
还要检查嵌入式设备上的勘误表。您可能会发现“不要这样做,因为它会崩溃,所以启用编译器选项,编译器会解决它”。
简而言之,崩溃的最可能来源是代码中的错误。在你确定这不是事实之前,不要担心更深奥的故障模式。
在小型卫星的软件/固件开发和环境测试方面工作了大约4-5年,我想在这里分享我的经验。
*(小型卫星比大型卫星更容易发生单次事件干扰,因为其电子部件的尺寸相对较小且有限)
非常简洁和直接:没有机制可以从可检测到的错误中恢复过来软件/固件本身的情况,至少没有用于恢复目的的软件/固件的最低工作版本副本,以及支持恢复的硬件(功能)。
现在,这种情况通常在硬件和软件两级处理。在这里,根据您的要求,我将分享我们在软件级别可以做的事情。
…恢复目的。。。。提供在真实环境中更新/重新编译/刷新软件/固件的能力。这几乎是高度电离环境中任何软件/固件的必备功能。如果没有这一点,您可以拥有任意数量的冗余软件/硬件,但在某一点上,它们都会崩溃。所以,准备好这个功能!…最低工作版本。。。在您的代码中具有响应性、多个副本、最低版本的软件/固件。这类似于Windows中的安全模式。不要只拥有一个功能完整的软件版本,而是拥有软件/固件的最低版本的多个副本。最小副本通常比完整副本小得多,并且几乎总是只有以下两个或三个功能:能够监听来自外部系统的命令,能够更新当前软件/固件,能够监控基本操作的内务数据。…复制…某处。。。在某处安装冗余软件/固件。无论有无冗余硬件,您都可以尝试在ARM uC中使用冗余软件/固件。这通常是通过在单独的地址中有两个或多个相同的软件/固件来实现的,这些软件/固件将向彼此发送心跳信号,但一次只有一个处于活动状态。如果已知一个或多个软件/固件没有响应,请切换到其他软件/固件。使用这种方法的好处是,我们可以在发生错误后立即进行功能更换,而无需与负责检测和修复错误的任何外部系统/方进行任何联系(在卫星情况下,通常是任务控制中心(MCC))。严格来说,如果没有冗余硬件,这样做的缺点是实际上无法消除所有单点故障。至少,您仍然会有一个单一的故障点,那就是交换机本身(或者通常是代码的开头)。然而,对于高度电离环境中受尺寸限制的设备(如微微/毫微微卫星),在没有额外硬件的情况下将单点故障减少到一点仍然值得考虑。更重要的是,用于切换的代码肯定会比整个程序的代码少得多,从而显著降低了在其中出现单一事件的风险。但是,如果您没有这样做,您的外部系统中应该至少有一个副本,该副本可以与设备接触并更新软件/固件(在卫星情况下,它也是任务控制中心)。您还可以在设备的永久内存存储中保存副本,该副本可以被触发以恢复正在运行的系统的软件/固件…可检测到的错误情况。。该错误必须是可检测的,通常通过硬件纠错/检测电路或通过一小段纠错/检测代码来检测。最好将这些代码放得小、多,并且独立于主软件/固件。其主要任务仅用于检查/纠正。如果硬件电路/固件是可靠的(例如,它比其余的更抗辐射-或具有多个电路/逻辑),那么您可以考虑使用它进行错误校正。但如果不是,最好将其作为错误检测。可通过外部系统/设备进行校正。对于纠错,您可以考虑使用像Hamming/Golay23这样的基本纠错算法,因为它们可以更容易地在电路/软件中实现。但这最终取决于团队的能力。对于错误检测,通常使用CRC。…支持恢复的硬件现在是这个问题上最困难的方面。最终,恢复需要负责恢复的硬件至少能够正常工作。如果硬件永久损坏(通常发生在其总电离剂量达到一定水平后),则软件无法帮助恢复。因此,对于暴露在高辐射水平下的设备(如卫星)来说,硬件无疑是最重要的关注点。
除了上述预测固件错误的建议外,我还建议您:
子系统间通信协议中的错误检测和/或错误校正算法。这是另一个几乎必须具备的功能,以避免从其他系统接收到不完整/错误的信号过滤ADC读数。请勿直接使用ADC读数。通过中值过滤器、均值过滤器或任何其他过滤器对其进行过滤-切勿相信单个读数。多采样,而不是少采样-合理。
如果你的硬件出现故障,你可以使用机械存储来恢复它。如果你的代码库很小,并且有一些物理空间,那么你可以使用一个机械数据存储。
材料表面不会受到辐射的影响。将有多个档位。机械读卡器将在所有齿轮上运行,并且可以灵活地上下移动。向下表示为0,向上表示为1。从0和1可以生成代码库。
这里有大量的回复,但我将尝试总结我对此的想法。
某些东西崩溃或不正常工作可能是您自己的错误造成的,那么当您找到问题时,应该很容易解决。但也有可能出现硬件故障,如果不是不可能,整体上很难解决。
我建议首先尝试通过日志记录(堆栈、寄存器、函数调用)来捕捉问题情况——要么将它们记录到文件中的某个位置,要么以某种方式直接发送(“哦,不,我崩溃了”)。
从这种错误情况中恢复可以是重新启动(如果软件仍然处于活动状态)或硬件重置(例如硬件看门狗)。从第一个开始更容易。
若问题是硬件相关的,那个么日志记录应该可以帮助您确定在哪个函数调用中发生了问题,这可以让您了解什么是不工作的以及在哪里。
此外,如果代码相对复杂-“分割并征服”它是有意义的-这意味着你在怀疑问题所在的地方删除/禁用一些函数调用-通常禁用一半代码并启用另一半代码-你可以得到“确实有效”/“不有效”的决定,然后你可以专注于另一半代码。(问题所在)
若问题在一段时间后发生,那个么可以怀疑堆栈溢出,那个么最好监视堆栈点寄存器,若它们不断增长。
如果你设法完全最小化代码,直到“hello world”类型的应用程序出现故障,那么硬件问题是意料之中的,需要进行“硬件升级”,这意味着发明这样的cpu/ram/-能够更好地耐受辐射的硬件组合。
最重要的事情可能是,如果机器完全停止/重新设置/不工作,您如何取回日志-这可能是bootstap应该做的第一件事-如果有问题的情况被解决,您应该回家。
如果在您的环境中也可以发送信号和接收响应,那么您可以尝试构建某种在线远程调试环境,但您必须至少有通信媒体工作,并且某些处理器/某些ram处于工作状态。通过远程调试,我的意思是GDB/GDB存根类型的方法,或者您自己实现从应用程序中获取所需的内容(例如,下载日志文件、下载调用堆栈、下载ram、重新启动)
既然您专门要求软件解决方案,而且您使用的是C++,为什么不使用运算符重载来创建自己的安全数据类型呢?例如:
不要使用uint32_t(以及double、int64_t等),而是制作自己的SAFE_uint32-t,其中包含uint32/t的倍数(最小值为3)。重载您想要执行的所有操作(*+-/<<>>==!=等),并使重载的操作对每个内部值独立执行,即不要执行一次并复制结果。在之前和之后,检查所有内部值是否匹配。如果值不匹配,可以将错误的值更新为最常见的值。如果没有最常见的值,您可以安全地通知存在错误。
这样,即使ALU、寄存器、RAM或总线上发生损坏也无所谓,您仍然可以多次尝试并很好地捕获错误。然而,请注意,这只适用于您可以替换的变量-例如,堆栈指针仍然是易受影响的。
附带故事:我遇到了一个类似的问题,也是在一个旧的ARM芯片上。结果发现,这是一个使用旧版本GCC的工具链,与我们使用的特定芯片一起,在某些边缘情况下触发了一个错误,这会(有时)破坏传递到函数中的值。在将设备归咎于无线电活动之前,确保设备没有任何问题,是的,有时是编译器错误=)