“没有经过测试的东西就是坏的。” ——作者未知
“嗯,一切都会坏掉,不是吗,上校。” ——蒙提·派森的飞行马戏团
一个不幸的事实是,任何带有运动部件的东西最终都会磨损和发生故障,电子电路也不例外。当然,在这种情况下,运动部件是电子。除了电迁移(移动的电子逐渐将金属原子推出位置,导致导线变细,从而增加电阻并最终产生开路)和树枝状生长(相邻导线之间的电压差导致位移的金属原子相互迁移,就像磁铁会相互吸引一样,最终导致短路)的磨损机制外,电子电路也容易受到背景辐射的影响。这些快速移动的带电粒子将电子击出轨道,在它们身后留下电离轨迹。在这些电子找到回家的路之前,曾经没有导电路径的地方现在存在导电路径。
如果路径位于用于存储一位的电容器的两个极板之间,则电容器会放电,并且该位可以从一翻转为零或从零翻转为一。一旦电容器放电,位移的电子就会返回原位,并且该部件似乎已经自行修复,没有永久性损坏,除了可能损坏客户的数据。因此,内存通常受到某种程度的冗余保护,因此可以检测到翻转的位,并可能进行纠正。当然,错误检测和纠正电路本身必须经过测试,这是本文的主要主题。
(如果路径位于电流源和接地之间,则它无法自行修复,直到断电为止。这称为单事件闩锁,它模拟硬故障,至少在断电之前是这样,例如在准备拆卸和更换明显发生故障的部件时。当然,返回的部件将测试为“未发现故障”,让所有相关人员感到沮丧。单事件闩锁是软件难以处理的问题,本文将不再进一步讨论。)
除了此处提到的错误原因外,传输线还容易受到噪声引起的错误的影响,因此传输的信号通常也受到冗余保护。
随着电路密度的增加,特征尺寸变得更小;随着频率的增加,电压变得更低。这些趋势结合起来减少了用于表示一位的电荷量,从而增加了内存对背景辐射的敏感性。例如,最初的UltraSPARC-I处理器以143MHz运行,并具有256KB的e-cache(外部缓存)。缓存设计使用简单的字节奇偶校验来保护数据,这足以满足要求,因为用于保存一位的电荷量足够大,以至于电离粒子只会消耗少量电荷,不足以翻转一位。
然而,当这种设计在UltraSPARC-II处理器中扩展到以400MHz运行并具有8MB e-cache时,用于保存一位的电荷量非常小,以至于背景辐射很容易翻转位,平均每个处理器每年产生一个翻转位。虽然这看起来可能不是一个很高的比率,但拥有12个系统,每个系统有32个处理器的客户平均每天会遇到一次故障。这就是导致Sun在1999年臭名昭著的e-cache奇偶校验危机的原因(稍后会详细介绍;为了好玩,请在网上搜索“e-cache parity”)。
由于错误(无论是瞬时错误还是永久错误)是生活中不可避免的事实,Oracle系统组织(以前是Sun Microsystems的部分部门)的系统设计师开发了一种分层方法来处理这些错误。最底层是硬件错误检测电路,它记录有关错误的信息,以便上层软件可以确定错误是瞬时的还是永久的,或者瞬时错误的发生率是否表明部件发生故障。下一层是错误纠正,可以由硬件、软件或两者结合来执行。第三层是诊断,Solaris操作系统的预测自愈功能在此层确定错误是否是由有故障的部件引起的,以及是否应该更换该部件。最后一层是错误遏制,当预测自愈功能可以隔离硬故障,使系统能够以最小的性能降低继续运行时,从而避免破坏性且昂贵的服务呼叫时,将调用错误遏制。
人们总是希望错误很少发生。然而,当错误确实发生时,人们希望检测、纠正、诊断和遏制的各个层都能完美地执行。确保这一点需要测试各个层,最好是以端到端的方式模拟真实错误的行为。因为(正如人们所希望的那样)错误很少发生(如果不是这样,您就有其他问题了),等待错误自然发生不是一种有效的测试方法。因此,需要错误注入器。
错误注入器需要硬件支持,因为在正常操作期间,硬件只会写入良好数据。(如果没有硬件支持,您可以通过向上层软件馈送错误报告来模拟错误,但这样您就无法测试硬件错误检测器。)硬件设计师理解这一点,因此他们通常提供一些注入错误的方法,以便他们可以测试其检测器。然而,他们并不总是了解将要注入错误的环境。例如,从硬件设计师的角度来看,在非常受控的加电自检(POST)环境中测试检测器就足够了,因此如果注入错误会产生损坏不相关数据或破坏缓存一致性的副作用,那也没什么大不了的。然而,对于软件设计师来说,此类副作用可能会使错误注入硬件变得无用,或者严重限制他或她可以安全注入的错误类型。
例如,虽然硬件错误检测器不在乎是否在干净或脏的缓存行上检测到缓存奇偶校验错误,或者是由用户指令还是内核指令检测到的,但软件层可能会在意。因此,错误注入器必须能够执行所有组合。
“处理错误只是关注细节。注入错误是火箭科学。” ——我
当硬件工程师致力于确定e-cache奇偶校验错误的原因,然后致力于修复时,我被要求领导一个项目,以通过软件减轻错误的影响。不幸的是,UltraSPARC-II使用不精确的陷阱来报告由加载指令或指令提取检测到的e-cache奇偶校验错误,因此即使从干净缓存行上的错误中恢复也是不可能的。我们能够从某些写回检测到的奇偶校验错误中恢复,并且我们肯定改进了内核在遇到奇偶校验错误时的消息。我们原型化了将仅影响用户程序而不影响内核的错误限制为仅该程序(此功能必须等到Solaris 10的系统管理工具及其进程重启器之后才能安全部署),并且我们引入了一个缓存清理器,该清理器使用诊断访问来主动查找干净缓存行上的奇偶校验错误,以安全的方式(即,不会导致内核崩溃的方式)并将它们从缓存中刷新,以防止它们导致中断。每当系统空闲时,我们都会刷新所有干净行和所有无错误脏行。
测试所有这些都需要错误注入器。虽然硬件人员编写了一个,但它不符合我们的需求;例如,您只能给它一个物理地址,在其中注入错误,然后等待系统代码绊倒它。此外,它既不模块化也不易扩展(毕竟,它是硬件人员编写的;公平地说,如果让我设计ASIC,我会做得更糟糕)。相反,我们的错误注入器基于我在1989年编写的一个,用于测试我为Sun的SPARCstation-1编写的内存奇偶校验错误恢复代码。此错误注入器是模块化的、表驱动的,并且易于扩展。当然,没有任何实际的低级错误注入代码适用于UltraSPARC-II,因此我们掏空了它,并在它提供的框架的基础上进行构建。
错误注入器由两部分组成:用户级命令行界面(mtst),以及设备驱动程序(/dev/memtest)。命令行界面允许用户指定奇偶校验错误应注入到干净行还是脏行上,以及其检测应由内核加载指令、用户级加载指令、内核指令提取、用户级指令提取、写回内存、来自另一个处理器的窥探(复制回),还是仅留在缓存中的用户指定位置触发。(最后一个由另一个用户级程序使用,亲切地称为alphabomber,以衡量缓存清理器的有效性。)
在解析和处理其参数后,mtst然后会打开/dev/memtest并发出一个ioctl给它。在ioctl中传递的参数将告诉设备驱动程序是否在其自己的空间中植入错误(用于内核触发的错误),或者在mtst传递给它的地址处(用于用户触发的错误),或者在特定的缓存位置(用于alphabombing)。它们还将指定设备驱动程序本身是否应触发错误,如果是,则通过加载指令、指令提取、写回内存或复制回不同的处理器,以及在陷阱级别零还是陷阱级别一。(出于显而易见的原因,mtstmtst/dev/memtest和
/dev/memtestmtst都不包含在Solaris版本中,它们的源代码也不包含在OpenSolaris中。)
假设设备驱动程序的动作没有故意导致内核崩溃,它将返回到
mtst
这些块中的每一个在内存中都受到8位ECC(错误纠正码)的保护,该ECC可以纠正任何单比特错误并检测任何双比特错误(SEC-DED)。
当在e-cache中时,每个数据字节都受到单个奇偶校验位的保护。
在e-cache和主内存之间有两个并行的UDB(UltraSPARC数据缓冲区)芯片,每个UDB一次转换8字节的ECC保护数据为8字节的奇偶校验保护数据(反之亦然)。当64字节的缓存行从内存移动到e-cache或反之亦然时,每个UDB处理四个8字节的块。
处理器和e-cache之间的接口为16字节宽。处理器的LSU(加载/存储单元)包含一个控制寄存器,该寄存器包含一个名为force mask(FM)的16位字段。FM中的每一位对应于CPU和e-cache之间16字节接口的一个字节。当一位为零时,相应字节的存储以良好的奇偶校验完成。当一位为一时,相应字节的存储以错误的奇偶校验完成。FM位不影响对从e-cache加载的奇偶校验的检查。
将奇偶校验错误注入到e-cache中非常简单。确定所需字节的物理内存地址,并执行以下步骤
1. 使用其物理地址,将所需的字节加载到寄存器中;这具有将其带入e-cache的副作用(如果它尚未在那里)。
2. 禁用中断。
3. 将LSU.FM设置为全一。
4. 将所需的字节存储回其物理地址。(如果由于某种原因,包含的缓存行在加载后从缓存中移出,则这将使其返回到缓存中。)目标字节将以错误的奇偶校验写回缓存行中。
5. 将LSU.FM重置为零。
6. 重新启用中断。现在,所需的字节以错误的奇偶校验位于e-cache中,可以通过多种机制触发潜在错误:用户或内核模式下的数据加载、用户或内核模式下的指令提取、位移刷新以导致写回、来自另一个CPU的访问以导致复制回等等。在LSU.FM不为零的持续时间内,必须禁用中断;否则,如果发生中断并且中断处理程序(或它调用的任何代码)执行存储,则会将不需要的奇偶校验错误引入缓存,并以不可预测的方式触发。
此六步序列用于在与特定物理内存地址、内核虚拟地址或用户虚拟地址对应的位置注入e-cache奇偶校验错误。(虚拟地址由
memtest
设备驱动程序转换为其相应的物理地址。)然而,为了模拟由背景辐射引起的位翻转,我们希望在任意e-cache偏移处注入e-cache奇偶校验错误,而无需考虑与e-cache行对应的物理内存地址。
幸运的是,LSU.FM字段也适用于使用诊断访问对e-cache的存储。不幸的是,诊断加载和存储仅适用于8字节数量,而不适用于单个字节。为了仅影响单个字节,我们必须仅将LSU.FM中与我们要更改的字节对应的一位设置为一。在这种情况下,序列变为
1. 禁用中断。
2. 欺骗指令预取器(见下文)。
3. 将LSU.FM中所需的位设置为一。
4. 使用诊断加载将包含的八个字节加载到寄存器中。
5. 使用诊断存储将包含的八个字节存储回e-cache中。
6. 将LSU.FM重置为零。
7. 重新启用中断。
唯一棘手的部分是防止e-cache的内容在我们加载和存储之间发生变化。窥探活动最坏的情况是将行的状态从独占更改为共享,或从有效更改为无效。由于窥探无法更改数据本身,只能更改标记中的状态,因此如果在加载和存储之间发生窥探,则不会造成任何危害。
但是,有一件事可能会在加载和存储之间更改缓存中的数据。处理器包含一个指令预取器——一个始终处于开启状态并且其行为在UltraSPARC I和II用户手册中没有充分记录的预取器。预取器不断地将指令从处理器的i-cache(指令缓存)移动到处理器的指令缓冲区。如果下一个要预取的指令的地址在i-cache中未命中,则将从e-cache中提取指令;如果地址在e-cache中也未命中,则包含的缓存行将从内存中带入e-cache,从而替换已有的内容。如果此e-cache填充碰巧替换了包含我们要损坏的字节的行,并且如果填充发生在诊断加载和诊断存储之间,我们将把八个字节的陈旧数据写入e-cache(以及其中一个字节上的错误奇偶校验);如果该行作为指令重新执行,这可能会导致以后发生意外故障。(虽然我们希望具有错误奇偶校验的字节最终会导致故障,但我们希望故障是我们预期的故障,而不是我们不预期的故障。)
为了欺骗预取器,我们静态地将代码序列定位在缓存行的开头,该代码序列设置LSU.FM、发出加载和存储、重置LSU.FM、重新启用中断并返回到调用者。当调用此例程时,它会禁用中断,然后分支到刚好超出上述序列的一系列空操作,足以填充指令缓冲区。此序列中的最后一条指令分支回设置LSU.FM的指令。因此,当我们到达加载/存储对的加载时,包含这些指令的缓存行已经位于e-cache中,并且要么已经替换了原始目标(因此我们将在我们e-cache驻留的代码之上注入错误),要么与我们的目标位于不同的缓存行中。在任何一种情况下,指令预取器“看到”加载/存储对之后的指令(包括空操作)已经位于指令缓冲区中,因此它暂时无事可做。这可以防止在加载/存储对执行期间任何行发生更改。(这是错误注入的“火箭科学”部分。)
当然,真正好的方法是拥有一个控制来关闭指令预取器。
在UltraSPARC-II上注入内存错误
“‘那一刻的恐怖,’国王继续说道,‘我永远、永远不会忘记!’‘你会忘记的,’王后说,‘如果你不把它记下来。’” ——路易斯·卡罗尔,《爱丽丝镜中奇遇记》
在UltraSPARC-II系统上注入内存错误比注入e-cache错误更困难。如前所述,虽然e-cache使用字节奇偶校验,但内存使用8位ECC来保护8个字节。数据始终以64字节块在内存和CPU子系统(处理器、两个UDB芯片和e-cache)之间移动,以四个16字节块传输。每个UDB一次处理8个字节,将具有良好ECC的8个字节转换为具有良好奇偶校验的8个字节,反之亦然。
每个UDB都有一个控制寄存器,该寄存器包含一个8位FCBV(强制校验位向量)字段和一个F_MODE(强制模式)位。当设置F_MODE位时,UDB对所有传出(到内存)数据使用FCBV字段的内容作为ECC值,而不是计算良好的ECC。
处理器和e-cache之间的接口为16字节宽。处理器的LSU(加载/存储单元)包含一个控制寄存器,该寄存器包含一个名为force mask(FM)的16位字段。FM中的每一位对应于CPU和e-cache之间16字节接口的一个字节。当一位为零时,相应字节的存储以良好的奇偶校验完成。当一位为一时,相应字节的存储以错误的奇偶校验完成。FM位不影响对从e-cache加载的奇偶校验的检查。
由于FCBV字段(使用时)适用于通过UDB的所有数据,并且最小的传输粒度为64字节,因此不可能仅在一个任意的8字节扩展字上强制错误的ECC。(这意味着我们无法将CE alphabomb到任意位置。)生成单个CE(可纠正错误)或UE(不可纠正错误)要求通过给定UDB的四个8字节扩展字最初是相同的,以便它们都共享相同的良好ECC值。
生成CE或UE通常按以下方式完成
1. 静止窥探活动,因为窥探的数据会通过UDB。
3. 在UDB中设置具有通用良好ECC值的FCBV,并设置其F_MODE。
4. 将所需的8字节块加载到寄存器中;这具有将其带入e-cache的副作用(如果它尚未在那里)。
5. 翻转寄存器中的一位(CE)或两位(UE)。
6. 存储现在已修改的8字节块;它将存储到缓存中,并将缓存行置于修改状态。
7. 位移刷新缓存行回到内存。UDB会将每个带有奇偶校验的8个字节转换为带有ECC的8个字节,但对于ECC位,它们将使用FCBV中的值,该值对于除修改后的块之外的所有块都是良好的。
8. 清除F_MODE。
9. 启用中断。
10. 允许窥探活动。
(虽然我们可以将FCBV和F_MODE的设置限制为仅处理目标位置的UDB,但同时编程它们更容易。)
为了在任意位置注入单个CE,UDB设计应该包含一个“触发器”或“掩码”字段,以指示FCBV字段将应用于哪些扩展字。例如,此字段可以是8位掩码,每个8字节块一位。(一个UDB将使用偶数位,另一个将使用奇数位;这种安排将使编程更简单。)UDB必须在设置F_MODE位时计算通过它的块,并且仅将FCBV应用于那些设置了相应“触发器”位的扩展字。
或者,该设计可以包括八组FCBV字段(每个UDB四个),每个字段都有自己的F_MODE位,以便可以在任何位置植入任意混合的CE、UE和良好数据。
诊断访问的其他用途
“我正在运行级别 1 诊断。” ——中校乔迪·拉福格,《星际迷航:下一代》
如前所述,对e-cache和内存接口芯片的诊断访问对于错误注入至关重要。如果没有在正常系统操作期间使用诊断加载和存储的能力,错误注入将是不可能的。
诊断访问也用于错误预防和纠正,因为缓存清理器使用诊断加载来确定是否存在潜在错误,并确定何时应将行从缓存中移出。
诊断访问也用于故障发生后,读取受影响的缓存行的内容以帮助进行离线诊断。因此,重要的是诊断访问提供对硬件中存储的所有位的可见性。例如,虽然对e-cache的诊断访问不返回奇偶校验位,但奇偶校验检查逻辑起作用,并根据需要在AFSR(异步故障状态寄存器)中设置PSYND(奇偶校验综合征)位。(16个PSYND位对应于处理器和e-cache之间接口中的16个字节。如果一个字节包含奇偶校验错误,则相应的PSYND位设置为一。)因此,即使不能直接观察到奇偶校验位,对缓存的诊断访问也允许推断奇偶校验位。
重要的是要注意,错误注入器和缓存清理器对诊断访问的使用取决于它们不干扰正常系统操作。特别是,在诊断操作进行期间,必须维护系统一致性。
UltraSPARC-II的缓存遵守此要求。CPU对缓存的诊断访问不会干扰缓存对一致性流量的响应。窥探继续进行,并且使缓存行无效的请求会正常处理。
请注意,决定是否在诊断访问时保持一致性是芯片设计师必须做出的许多决策的一个示例。在Sun的e-cache奇偶校验危机之前,这些决策是由硬件设计师在未咨询软件错误处理专家的情况下做出的。自那次危机以来,对新芯片的错误和诊断审查已成为硬件设计周期的必要组成部分。
这些审查是芯片设计师和负责错误处理、诊断和遏制的软件人员的联席会议。它们在设计过程的早期举行,以便可以纠正硬件对错误处理方面的任何缺陷(例如未能捕获重要信息),并且可以纳入改进建议。
其他错误注入方法。
“‘医生,我这样做时会痛。’‘那就别这样做。’” ——亨尼·扬曼
硬件工程师开发了其他注入错误的方法,其中一些方法比其他方法更可用。例如,从我们的e-cache奇偶校验经验中吸取教训,UltraSPARC系列后续处理器(从大约2001年的UltraSPARC-III开始)使用真正的ECC保护e-cache。在UltraSPARC-III中,16个字节的数据受9位ECC保护,并且相同的方案也用于保护内存中的数据。(当数据从e-cache移动到内存时,会检查ECC;纠正单比特错误,并使用特殊综合征重写双比特错误。类似地,当数据从内存移动到e-cache时,会检查ECC;纠正单比特错误,但双比特错误按原样写入e-cache。)
为了将错误注入到UltraSPARC-III e-cache中,硬件工程师试图做类似的事情;另一个控制寄存器包含FM位和强制ECC字段,只有当数据写入e-cache时,才使用此寄存器中的强制ECC。这将很难使用,因为存储不会将数据直接写入e-cache,而是写入w-cache(写缓存)。w-cache中的数据在行从w-cache中移出之前不会与e-cache中的数据合并,这很难控制。幸运的是,我们不必使用此机制,因为硬件工程师提供了更好的东西:直接访问e-cache中的原始位,包括数据和ECC。
此机制使用五个暂存寄存器:四个用于保存32字节的数据(由两个16字节ECC保护块组成的半缓存行),第五个寄存器用于保存保护各个块的两个9位ECC字段。一组诊断加载和存储在e-cache和暂存寄存器之间移动数据,一次移动32个数据字节和18个ECC位;另一组在给定的暂存寄存器和整数寄存器之间移动数据。这允许错误注入器翻转数据和ECC位的任何组合。
结论
“软件。硬件。完整。”
当然,像IBM和Oracle这样既控制其销售的硬件又控制支持它的软件的公司最适合利用错误注入技术来改进其各自系统的错误处理、诊断和遏制,因为硬件和软件人员都在一个组织中,这使得他们在开发新的硬件和软件时能够进行必要的持续互动。当硬件和软件开发在不同的组织之间进行划分时,就像在Windows、VMware和Linux世界中(或者,或者在Intel和AMD世界中)一样,利用错误注入技术来改进产品要困难得多。
问
致谢
我还要感谢Mike Shapiro和Jim Maurer审阅了早期草稿。他们的建议改进了本文。任何遗留的错误完全由我负责。
喜欢它,讨厌它?请告诉我们
[email protected]
Steve Chessin([email protected]) 是Oracle Corporation系统组质量组织的高级首席软件工程师,位于加利福尼亚州门洛帕克。
最初发表于 Queue vol. 8, no. 8—
在数字图书馆中评论本文
更多相关文章
Phil Vachon - 王国之钥
一个不幸的手指错误引发了当前的危机:客户不小心删除了签署新固件更新所需的私钥。他们有一些令人兴奋的新功能要发布,以及通常的一系列可靠性改进。他们的客户越来越不耐烦,但当被问及发布日期时,我的客户不得不拖延。他们如何才能提出一个有意义的日期?他们已经失去了签署新固件版本的能力。
Peter Alvaro, Severine Tymon - 将天才从故障测试中抽象出来
本文呼吁分布式系统研究界改进容错测试的最新技术水平。普通用户需要自动化选择定制故障注入的工具。我们推测,超级用户选择实验的过程可以在软件中有效地建模。本文描述了一个验证此推测的原型,介绍了来自实验室和现场的早期结果,并确定了可以使这一愿景成为现实的新研究方向。
Pat Helland, Simon Weaver, Ed Harris - 大到不能倒
Web规模的基础设施意味着大量服务器协同工作,通常是成千上万的服务器都在朝着同一个目标努力。如何管理这些环境的复杂性?如何引入通用性和简单性?