我们正在编译一个嵌入式C / C ++应用程序,该应用程序部署在受到电离辐射轰击的环境中的屏蔽设备中。我们正在使用GCC并为ARM进行交叉编译。部署后,我们的应用程序会生成一些错误数据,并且崩溃的次数比我们想要的要多。硬件是为此环境设计的,我们的应用程序已在该平台上运行了几年。
我们可以对代码进行更改吗,还是可以在编译时进行改进,以识别/纠正由单个事件引发的软错误和内存损坏?是否有其他开发人员在减少软错误对长期运行的应用程序的有害影响方面取得了成功?
内存中的值是否正在更改,或者处理器中的值是否正在更改?如果硬件是为环境设计的,则软件应像在非放射性环境中一样运行。 –托马斯·马修斯
如果可能,您应该设置一个日志记录系统,将事件存储在耐辐射的非易失性存储器中。存储足够的信息,以便您可以跟踪事件并轻松找到根本原因。 –托马斯·马修斯
@Thomas Matthews所有内存都有FIT错误率,并且硬件制造商做出了很多承诺。大多数问题可能是由SEU在运行时修改ram引起的。 –车
这是硬件/软件的组合解决方案,但我知道德州仪器(及其他公司)会为安全关键型应用制造嵌入式芯片,该芯片由两个重复的内核组成,它们以锁步方式运行,相差半个时钟周期。当硬件检测到内核之间存在差异时,会执行特殊的中断和重置操作,因此您可以从错误中恢复。我相信TI将它们称为“大力士”安全处理器。 – mbrig
冗余坚固的电机,一些齿轮,轴和棘轮!每年更换一次,或根据剂量率要求更频繁地更换。真的,我对这类问题的第一个问题一直是,您是否真的需要那么多软件?尽可能地模拟一下。 – jwdonahue
我想在小型卫星的软件/固件开发和环境测试中工作约4-5年*,我想在这里分享我的经验。
*(由于小型卫星的电子组件尺寸相对较小且尺寸有限,因此小型卫星比大型卫星更容易发生单事件失败)
简明扼要,直接:如果没有软件/固件的最低工作版本的至少一个副本用于恢复目的,并且没有硬件支持,则没有任何机制可以从软件/固件本身从可检测的错误情况中进行恢复。恢复(功能性)。
现在,通常在硬件和软件级别都可以处理这种情况。在这里,根据您的要求,我将分享我们在软件方面可以做的事情。
...恢复目的。...提供在真实环境中更新/重新编译/刷新软件/固件的功能。对于高度电离的环境,这是几乎所有软件/固件都必须具备的功能。否则,您可能会拥有任意数量的冗余软件/硬件,但在某一时刻,它们都会崩溃。因此,准备此功能!
...最低工作版本...在代码中包含响应式,多个副本,最低版本的软件/固件。这就像Windows中的安全模式。拥有一个最低功能版本的软件/固件,而不是只有一个功能完整的软件版本,而要拥有多个副本。最小副本的大小通常比完整副本小得多,并且几乎总是只有以下两个或三个功能:
无论有没有冗余硬件,您都可以尝试在ARM uC中拥有冗余软件/固件。通常可以通过在单独的地址中使用两个或多个相同的软件/固件来相互发送心跳来完成此操作,但是一次只能激活一个。如果已知一个或多个软件/固件无响应,请切换到其他软件/固件。使用这种方法的好处是,发生错误后,我们可以立即进行功能替换-无需与负责检测和修复错误的任何外部系统/部门进行任何联系(在卫星情况下,通常是任务控制中心( MCC))。
严格来说,没有冗余硬件,这样做的缺点是您实际上无法消除所有单点故障。至少,您仍然会有一个单点故障,这就是开关本身(或者通常是代码的开头)。但是,对于在高度电离的环境中受尺寸限制的设备(例如微微/毫微微卫星),将单点故障减少到一个点而无需额外的硬件仍然值得考虑。此外,用于切换的代码段肯定比整个程序的代码段少得多-大大降低了获得单个事件的风险。
但是,如果您不这样做,则您的外部系统中应该至少有一个副本,可以与该设备联系并更新软件/固件(在卫星情况下,它又是任务控制中心)。
您还可以将副本保存在设备的永久存储器中,以触发该副本以还原正在运行的系统的软件/固件
...可检测的错误情况。通常必须通过硬件错误校正/检测电路或一小段用于错误校正/检测的代码来检测错误。最好将此类代码缩小,多个并独立于主软件/固件。其主要任务仅用于检查/更正。如果硬件电路/固件可靠(例如,其辐射辐射比其余部分更坚固-或具有多个电路/逻辑),那么您可以考虑对其进行纠错。但是,如果不是这样,最好将其作为错误检测。可以通过外部系统/设备进行更正。对于纠错,您可以考虑使用诸如Hamming / Golay23之类的基本纠错算法,因为它们可以在电路/软件中更轻松地实现。但这最终取决于您团队的能力。对于错误检测,通常使用CRC。
...支持恢复的硬件现在,这是这个问题上最困难的方面。最终,恢复需要负责恢复的硬件至少能够正常运行。如果硬件永久损坏(通常在其总电离剂量达到一定水平后发生),则该软件将(很难)无法帮助恢复。因此,对于暴露于高辐射水平的设备(例如卫星),硬件无疑是最重要的问题。
除了上述可预见的由于单事件失败而导致的固件错误的建议之外,我还建议您具有:
子系统间通信协议中的错误检测和/或错误校正算法。为了避免从其他系统接收到不完整/错误的信号,这是另一个几乎必须具备的条件
过滤ADC读数。不要直接使用ADC读数。用中值过滤器,均值过滤器或任何其他过滤器过滤它-永远不要相信单个读数值。采样更多而不是更少-合理。
请注意,内存扫描速率应足够频繁,以至于很少会发生多位错误,因为大多数ECC内存可以从单位错误而非多位错误中恢复。
强大的错误恢复包括控制流传输(通常在错误发生之前的某个时刻重新启动进程),资源释放和数据恢复。
他们对数据恢复的主要建议是通过将中间数据视为临时数据来避免对数据的需求,以便在错误之前重新启动也会将数据回滚到可靠状态。这听起来类似于数据库中“事务”的概念。
按合同编程:验证前提条件和后置条件,然后检查对象以确认其仍处于有效状态。
而且,正是这种情况,NASA已将C ++用于诸如火星漫游者之类的大型项目。
C ++类的抽象和封装可在多个项目和开发人员之间进行快速开发和测试。
动态分配(使用了专用的内存池和新的放置以避免系统堆损坏的可能性)。
实际上,这听起来像是纯语言会擅长的事情。由于值永远不会改变,因此如果它们遭到破坏,则可以返回到原始定义(这就是应该的定义),并且您不会无意中重复两次相同的事情(因为没有副作用)。 – PyRulez
RAII是一个坏主意,因为您不能依靠它来正确执行甚至根本不执行。它可能会随机破坏您的数据等。您确实希望获得尽可能多的不变性,并且在此之上还要有纠错机制。丢掉破碎的东西比尝试以某种方式修复它们要容易得多(您确切地知道如何回到正确的旧状态?)。不过,您可能想要使用一种相当愚蠢的语言-优化可能会带来更大的伤害。 – Lu安
@PyRulez:纯粹的语言是一种抽象,硬件不是纯粹的。编译器非常擅长隐藏差异。如果您的程序具有逻辑上不应在步骤X之后使用的值,则编译器可能会使用在步骤X + 1中计算出的值覆盖它。但这意味着您无法返回。更正式地讲,纯语言中程序的可能状态形成一个非循环图,这意味着两个状态是等效的,并且当从两个状态都可以到达的状态相等时,可以将它们合并。这次合并破坏了通往这些州的道路差异。 – MSalters
@Vorac-根据演示文稿,与C ++模板有关的问题是代码膨胀。 – jww
@DeerSpotter确切的问题远不止于此。电离会损坏正在运行的观察程序的位。然后,您将需要一个观察者的观察者,然后-一个观察者的观察者,依此类推... –阿尼乌斯·瓦西利亚斯卡斯(Agnius Vasiliauskas)
将任何可以存储的内容存储在ROM中。无需计算内容,而是将查找表存储在ROM中。 (确保您的编译器将查找表输出到只读部分!在运行时打印出内存地址以进行检查!)将中断向量表存储在ROM中。当然,请运行一些测试以查看ROM与RAM相比有多可靠。
堆栈中的SEU可能是最有可能导致崩溃的原因,因为索引变量,状态变量,返回地址和各种类型的指针通常位于该处。
您可以在每个计时器滴答声中运行“健全性检查”例程,以及用于处理系统锁定的看门狗例程。您的主代码还可以定期增加一个计数器来指示进度,并且完整性检查例程可以确保这种情况已经发生。
您可以为数据添加冗余,以便能够检测和/或纠正错误。这将增加处理时间,可能使处理器长时间暴露在辐射下,从而增加了出错的机会,因此您必须考虑取舍。
检查您的CPU缓存的大小。您最近访问或修改的数据可能会在缓存中。我相信您可以禁用至少某些缓存(以较高的性能成本);您应该尝试这样做以查看缓存对SEU的敏感程度。如果缓存比RAM硬,那么您可以定期读取和重写关键数据,以确保它们保留在缓存中并使RAM恢复正常。
如果将内存页面标记为不存在,则当您尝试访问该页面时,CPU将发出页面错误。您可以创建一个页面错误处理程序,在处理读取请求之前进行一些检查。 (PC操作系统使用它来透明地加载已交换到磁盘的页面。)
使用汇编语言,您知道寄存器中的内容和RAM中的内容。您知道CPU使用的是什么特殊的RAM表,并且可以通过回旋方式进行设计以降低风险。
使用objdump实际查看生成的汇编语言,并计算出每个例程占用多少代码。
如果您使用的是像Linux这样的大型操作系统,那您就麻烦了。有这么多的复杂性和太多的事情要出错。
您编写的每个捕获错误的例程都将因相同的原因而失败。
虽然这是事实,但检查例程正常运行所需的(例如)100字节代码和数据中的错误几率比其他地方的错误几率小得多。如果您的ROM非常可靠,并且几乎所有代码/数据实际上都在ROM中,那么您的几率甚至更高。
使用2个或更多具有相同代码的相同硬件设置。如果结果不同,则应触发复位。对于3台或3台以上的设备,您可以使用“投票”系统来尝试确定哪些设备已受到威胁。
如今,通过硬件可以使用ECC,从而节省了处理时间。第一步将是选择具有内置ECC的微控制器。 –伦丁
我脑海中的某个地方提到了航空电子(也许是航天飞机?)飞行硬件,其中冗余架构被明确设计为不完全相同(以及由不同的团队)。这样做减少了硬件/软件设计中系统错误的可能性,从而降低了所有投票系统在面对相同输入时同时崩溃的可能性。 –彼得M
@PeterM:AFAIK也被称为波音777的飞行软件:由三个团队使用三种编程语言编写的三个版本。 –马丁·施罗德(MartinSchröder)
@DanEsparza RAM通常具有电容器(DRAM)或反馈中的一些晶体管(SRAM),用于存储数据。辐射事件可能会虚假地对电容器进行充电/放电,或更改反馈回路中的信号。 ROM通常不需要写入的能力(至少在没有特殊情况和/或更高电压的情况下),因此在物理级别上可能固有地更稳定。 –纳法拉
@DanEsparza:ROM存储器有多种类型。如果“ ROM”是由eeprom或flash以5v只读但在10v是可编程的来模拟的,则实际上“ ROM”仍然易于电离。也许只是比别人少。但是,有一些不错的硬核东西,例如Mask ROM或基于保险丝的PROM,我认为它们真的需要大量的辐射才能开始失效。我不知道是否还有制造。 – quetzalcoatl
您可能也对有关算法容错的丰富文献感兴趣。这包括旧的分配:写一个排序,当恒定数量的比较将失败时(或者,当渐进失败的比较的渐近数量缩放为n个比较的log(n)时,对输入进行正确排序)。
开始阅读的地方是Huang和Abraham在1984年发表的论文“矩阵运算的基于算法的容错”。他们的想法大概与同态加密计算相似(但实际上并不太相同,因为他们正在尝试在操作级别进行错误检测/纠正)。
该论文的最新版本是Bosilca,Delmas,Dongarra和Langou的“基于算法的容错应用于高性能计算”。
我真的很喜欢你的回应。这是一种用于数据完整性的通用软件方法,在我们的最终产品中将使用基于算法的容错解决方案。谢谢! –车
为放射性环境编写代码与为任何关键任务应用程序编写代码实际上没有什么不同。
使用任何半专业嵌入式系统都应采用的日常“面包和黄油”安全措施:内部看门狗,内部低压检测,内部时钟监控器。这些事情甚至都不需要在2016年提及,它们几乎是所有现代微控制器的标准配置。
如果您具有安全和/或面向汽车的MCU,它将具有某些看门狗功能,例如给定的时间窗口,您需要在其中刷新看门狗。如果您具有关键任务实时系统,则这是首选。
通常,使用适合此类系统的MCU,而不要使用一包玉米片中收到的一些通用主流绒毛。如今,几乎每个MCU制造商都有专门为安全应用(TI,Freescale,Renesas,ST,Infineon等)设计的MCU。它们具有许多内置的安全功能,包括锁步内核:这意味着有2个CPU内核执行相同的代码,并且它们必须彼此一致。
重要信息:您必须确保内部MCU寄存器的完整性。所有可写的硬件外围设备的控制和状态寄存器都可能位于RAM内存中,因此容易受到攻击。
为了防止寄存器损坏,最好选择具有内置“一次写入”寄存器功能的微控制器。此外,您需要将所有硬件寄存器的默认值存储在NVM中,并定期将这些值复制到您的寄存器中。您可以用相同的方式确保重要变量的完整性。
注意:始终使用防御性编程。这意味着您必须设置MCU中的所有寄存器,而不仅仅是应用程序使用的寄存器。您不希望某些随机的硬件外设突然唤醒。
检查RAM或NVM中的错误的方法有很多种,包括校验和,“行走模式”,软件ECC等。当今最好的解决方案是不使用其中任何一种,而使用带有内置ECC和类似的检查。由于在软件中执行此操作很复杂,因此错误检查本身可能会引入错误和意外问题。
使用冗余。您可以将易失性和非易失性存储器都存储在两个相同的“镜像”段中,该段必须始终相等。每个段可以附加一个CRC校验和。
为所有可能的中断/异常实现默认的中断服务例程/默认的异常处理程序。甚至那些您不使用的。默认例程除了关闭其自己的中断源外不执行任何操作。
了解并接受防御性编程的概念。这意味着您的程序需要处理所有可能的情况,即使是理论上不可能发生的情况。例子。
高质量的关键任务固件会检测到尽可能多的错误,然后以安全的方式处理或忽略它们。
切勿编写依赖于行为不当的程序。由于辐射或EMI导致硬件意外更改,此类行为可能会发生巨大变化。确保您的程序不受此类废话的最好方法是使用像MISRA这样的编码标准以及静态分析器工具。这也将有助于防御性编程和清除错误(为什么您不希望在任何类型的应用程序中检测到错误?)。
重要信息:不要依赖静态存储持续时间变量的默认值。也就是说,不要相信.data或.bss的默认内容。从初始化到实际使用该变量之间可能有任何时间,可能会有很多时间破坏RAM。而是编写程序,以便在运行时从NVM设置所有此类变量,就在首次使用此类变量之前。
实际上,这意味着,如果在文件范围内或将变量声明为静态变量,则永远不要使用=对其进行初始化(或y