可靠计算系统设计分析与验证课程笔记 5:从不同开发阶段的验证方法出发,讨论故障仿真、硬件与软件故障注入、实验结果分析、定向故障注入,以及现场数据中的相关失效

故障注入与实验验证

上一篇介绍了 watchdog、heartbeat、控制流检查和应用断言等检测机制。要知道这些机制是否有效,还需要让系统真正面对错误,观察它能否及时发现异常、限制传播并恢复服务

故障注入(Fault Injection)通过人为施加可控制的扰动,研究系统在异常条件下的行为。它既可以验证一个恢复方案,也可以揭示尚未考虑的传播路径,例如一个寄存器比特翻转如何演化为错误地址、内核崩溃或静默的数据损坏

实验结论始终与故障模型、注入位置、触发时刻、工作负载和观察范围有关。把这些条件写清,才能判断结果能够支持什么设计决策

不同开发阶段怎样验证可靠性

系统的开发阶段不同,可观察的信息和适用的实验手段也不同

阶段 主要方法 能得到的信息 关键限制
早期设计 在 CAD、逻辑或系统模型中进行故障仿真 故障影响、错误延迟、检测覆盖率、恢复时间分布 模型与输入是否足够准确
原型阶段 在可运行原型中进行硬件或软件故障注入 实际传播路径、检测与恢复行为、服务中断 注入场景能否代表目标故障
运行阶段 收集真实工作负载下的日志与性能数据 自然失效特征、失效率、失效间隔、相关性 数据完整性与被观测系统的代表性

仿真允许在硬件尚未实现时修改设计;原型实验可以观察软硬件之间的实际交互;现场测量则提供真实环境中的证据。三者可以互相校验,例如用现场数据修正故障模型,再用注入实验检验改进后的恢复机制

故障注入常常主动提高异常出现的频率,以便在有限时间内观察足够多的案例。因此,注入实验中的“每分钟崩溃次数”不能直接当作系统在生产环境中的自然失效率

先明确注入的是故障还是错误

沿用故障传播链

故障 Fault⟶错误 Error⟶失效 Failure\text{故障 Fault}\longrightarrow\text{错误 Error}\longrightarrow\text{失效 Failure}

改变晶体管电流,是在较低层模拟故障的物理作用;直接修改内存中的一个比特,则通常是在高层放入一个已经损坏的状态,也就是模拟故障所产生的错误

两者都可以纳入广义的故障注入,但实验解释不同。例如寄存器翻转可以研究错误值被使用后的行为,却不直接说明某种物理故障产生该错误值的概率

一次注入需要至少回答以下问题:

维度 需要明确的内容 例子
What 扰动类型、数量和持续时间 单比特翻转、持续 stuck-at、消息丢失
Where 注入层次与具体目标 某个寄存器、内核栈、API 返回值
When 时间或事件触发条件 运行后某时刻、执行到某条指令、某次请求到达
Workload 系统正在处理什么任务 文件读写、事务请求、网络通信
Observation 怎样判定结果与结束实验 输出校验、告警、崩溃转储、超时

“向程序注入随机错误”仍然不够具体:在全部地址中均匀抽样,在被访问地址中抽样,以及在每次内存访问时抽样,会得到不同的实验分布

设计阶段:分层故障仿真

电气层:保留物理作用细节

电气层仿真可以改变电流、电压或器件参数,研究物理扰动如何影响电路。例如带电粒子引起的瞬态电流脉冲,可能在节点上形成电压变化,再传播到后续逻辑

简单的 stuck-at 模型无法表达所有这类现象。瞬态脉冲的宽度、幅度、发生时刻和传播路径,都可能决定它是否最终被寄存器捕获

这一层细节丰富,但计算成本高,通常适合研究局部器件、电路或关键路径

逻辑层:用抽象故障模型扩大规模

逻辑层通常操作门、信号线和触发器,常见模型包括:

  • Stuck-at-0 / Stuck-at-1:在指定持续时间内强制信号为固定值,可用于模拟永久性故障
  • 瞬态反相或比特翻转:在某一时刻改变逻辑值,模拟短暂扰动产生的错误

为了让逻辑模型更接近电气行为,可以先在低层进行大量实验,再构造故障字典。字典按输入向量、故障位置和注入时刻记录可能的输出错误模式及其频率

例如,一个四位加法器受到电流脉冲影响后,可能只改变进位,也可能同时改变若干和位。高层仿真可以使用这些模式,而不必每次重新模拟全部晶体管

字典中的概率仍然对应生成它时使用的条件。输入分布、器件参数或时序改变后,需要重新检查模型是否适用

功能层:观察软件与系统行为

功能层把组件抽象成处理器、存储器、通信链路和节点,可以注入

  • 内存或寄存器比特翻转
  • 消息内容损坏、丢失或延迟
  • 节点服务中断
  • I/O 错误或异常返回值

这一层能够运行更完整的软件和工作负载,因而适合观察应用相关的传播路径。同样的错误值,可能在一个程序中被覆盖,在另一个程序中成为数组索引,在第三个程序中改变控制流

分层仿真的取舍是:较低层解释扰动怎样形成错误,较高层解释错误怎样影响服务。连接两层时,需要保留对上层结果有决定作用的信息

原型阶段:硬件故障注入

FPGA 中的掩码注入

一种实现方式是把故障注入模块与目标电路一起综合到 FPGA 中,为目标触发器配置对应的注入掩码位

处理器通过扫描链装入掩码,当注入信号有效时,对指定位置的组合逻辑输出进行翻转,并把结果写入触发器。对单个位置,可示意为

Di′=Di⊕(inject∧mi)D_i'=D_i\oplus(\mathrm{inject}\land m_i)

其中 mi=1m_i=1 表示选择该位置。这样能够精确控制被改变的状态位和捕获时刻,适合研究寄存器状态错误及其后续传播

注入电路也会占用面积、影响布线或时序,因此需要确认加入注入模块后的原型仍能代表待评估设计

插入网络数据通路的注入器

网络硬件注入器可以直接放在数据通路中,按流水线完成

接收⟶解析⟶按条件修改⟶重新发送\text{接收}\longrightarrow\text{解析}\longrightarrow\text{按条件修改} \longrightarrow\text{重新发送}

以 FPGA 为核心,可以根据包头、控制信息或特定事件触发注入,修改某些软件接口难以触及的链路控制状态,并统计流量和注入结果

它可以作为透明的链路中间设备工作,而不成为一个普通网络节点。短流水线有助于减少附加延迟,但“透明”仍需通过正常运行对照来验证,尤其要观察吞吐量、延迟和协议时序是否被改变

硬件注入的优势是接近目标层次、时间控制精细;成本则包括专用设备、实现复杂度和可访问位置的限制

软件实现的故障注入

软件实现的故障注入通常记为 SWIFI,也可见 SIFI。它利用操作系统、调试器、插桩或接口拦截来改变目标状态,具有成本较低、易于自动化和便于扩展模型的特点

常见实现方式

方法 实现思路 适合观察的问题
编译期修改 修改源代码、字节码或生成代码 特定软件缺陷、判断条件或计算逻辑变化
运行时内存修改 改变代码段、数据段、堆或栈 状态损坏、指针错误、指令损坏
寄存器修改 在断点或异常处修改寄存器 地址、运算结果或控制状态损坏
API / 系统调用拦截 改变参数、返回码或返回数据 错误处理路径、依赖服务异常
资源限制 限制内存、磁盘空间或带宽 资源不足时的降级与恢复
网络注入 丢弃、延迟或修改消息 重试、超时、复制和一致性处理

软件工具只能改变自己能够访问或拦截的状态。普通用户态工具对内核、设备内部状态和底层链路控制的可见范围,与内核模块或专用硬件不同

API 拦截:制造依赖失败

历史上的 Holodeck 工具使用系统调用和 API 拦截,在 Windows 用户应用与其运行环境之间注入异常,例如文件被占用、磁盘数据错误、网络不可用、注册表项损坏以及异常返回值

这类方法直接检验应用如何处理依赖失败:函数返回错误后是否仍使用无效句柄,写入失败后是否错误地报告成功,内存分配失败后是否仍访问指针

注入模型应与接口语义一致。若接口约定失败时不会修改输出缓冲区,实验却同时随意改写缓冲区,就需要把这一额外扰动明确纳入模型

调试器注入:把触发位置与修改动作结合起来

基于 GDB 的方法可以在函数入口、指令地址或数据访问处设置触发点,暂停执行后读取目标值,再修改内存或寄存器并继续运行

翻转字中第 kk 位的基本操作为

x′=x⊕2k\boxed{x'=x\oplus2^k}

自动化脚本可以组织“启动程序、等待触发、读取状态、执行翻转、继续运行、收集结果”的全过程。没有源代码时,也可能依据二进制符号和反汇编定位,但可获得的语义信息通常更少

暂停与恢复会改变执行时序,特别容易影响并发程序、竞态和实时任务。对于一过性的存储翻转,只修改一次状态与持续强制该位置为错误值,也代表不同的故障持续模型

注入目标与触发时刻怎样选择

随机抽样与热点分析

随机注入可以扩大覆盖范围,但大量选中的位置可能根本不会被当前工作负载访问。代码 profiling 可以找出热点函数和控制流,数据 profiling 可以找出频繁访问的结构,再对这些位置进行有针对性的注入

例如,一项基于 UnixBench 的 Linux 内核分析在 403403 个观测函数中选出了 3232 个核心函数,分布在体系结构相关代码、文件系统、内核公共逻辑和内存管理等子系统。这种目标选择反映的是该工作负载下的执行特征

热点注入提高了观察到错误影响的效率,但实验统计的是被选中位置上的行为。要推断更大范围的风险,需要说明抽样权重;低频但关键的恢复路径、启动路径和边界条件,也需要单独覆盖

时间触发与事件触发

时间触发可以在启动后固定时刻或随机时刻注入,比较容易实现。事件触发则把注入条件绑定到执行状态,例如

  • 某条目标指令即将执行
  • 某个变量第一次被读取
  • 某次系统调用准备返回
  • 某个请求已经执行,但响应尚未发出

事件触发有助于在重复实验中命中相近的语义位置。例如测试“写盘成功后、返回确认前崩溃”,用执行事件定位通常比等待固定毫秒数更准确

自动化注入实验怎样组织

实验框架将控制主机与注入目标分开:控制主机执行实验脚本并收集日志,目标节点的进程管理器负责运行注入器、应用、工作负载与监测器

故障注入框架
故障注入框架

每轮先初始化系统并运行负载,命中触发条件后注入,随后校验结果、保存证据并恢复基线,再进入下一轮

目标内核可能崩溃或挂起,因此关键日志和转储最好能送往独立的收集端,并由外部监控判断是否需要重启。每轮结束后,需要清除上一轮遗留的损坏状态,避免把连续污染误认为独立实验

结果中还应区分“没有触发”“注入器执行失败”和“注入成功但没有影响”。否则工具自身的问题可能被统计成系统成功容错

结果分析:注入、激活与失效不能混为一谈

激活是过程属性,结局是结果属性

损坏位置被执行或读取,称为错误被激活。已激活错误仍可能被覆盖、屏蔽或纠正,因此不一定产生可见失效

实验可以分别记录是否激活、是否告警、是否恢复,再记录最终结局:

观测结果 含义
未激活 在观察窗口内,损坏的指令或数据没有被使用
已激活但未表现异常 错误被使用,但没有观察到目标范围内的异常
检测并恢复 机制发现异常,并在规定条件下恢复服务
错误输出 结果或外部状态偏离要求
崩溃 应用或操作系统终止、panic 或进入不可继续执行的异常路径
挂起 在规定期限内失去有效进展,例如死锁或活锁

这些信息不应简单混成一个互斥标签表:一个实验既可能“已激活”,也可能“被检测”,最终又表现为“崩溃”。需要在数据结构中分开记录,或者明确规定最终分类的优先级

Fail-silent 要求组件发生失效时保持静默,避免向外传播错误结果。如果组件继续返回错误数据,就违反了这一假设。判断这种情况需要独立的结果校验,单看进程是否退出无法发现静默数据损坏

覆盖率必须注明条件

设成功施加扰动的实验数为 NIN_I,其中激活的数量为 NAN_A。若选择“已激活错误”作为检测目标,检测覆盖率应写成

C^det∣A=NA∩DNA\widehat C_{\mathrm{det}\mid A} =\frac{N_{A\cap D}}{N_A}

其中 NA∩DN_{A\cap D} 只统计既已激活又被检测的事件。类似地,给定检测后恢复成功的比例为

C^rec∣D=ND∩SND\widehat C_{\mathrm{rec}\mid D} =\frac{N_{D\cap S}}{N_D}

这里 SS 的判定需要明确,例如“恢复后输出正确且在期限内完成”,而不只是“重启成功”

举一个假设实验:成功注入 10001000 次,400400 次被激活,其中 300300 次被检测到,那么激活比例为 40%40\%,以已激活错误为目标的检测覆盖率为 75%75\%。300/1000=30%300/1000=30\% 表示成功注入实验中产生检测结果的比例,回答的是另一个问题

如果只想统计会导致外部错误的事件,则还需要能够识别这类目标,例如通过关闭保护机制的配对实验估计。未激活或被应用自然屏蔽的错误,不能直接算作检测器的保护成果

延迟也需要一个明确的起点

设 tIt_I 为注入时刻,tAt_A 为激活时刻,tDt_D 为检测时刻,tRt_R 为恢复服务时刻,则可以定义

Lactivation=tA−tI,Ldetection=tD−tAL_{\mathrm{activation}}=t_A-t_I,\qquad L_{\mathrm{detection}}=t_D-t_A

Lrecovery=tR−tDL_{\mathrm{recovery}}=t_R-t_D

从注入到检测的时间包含等待激活的部分,不能直接当作检测器自身的响应时间。分布式实验还应使用可比较的时钟或因果事件来建立顺序

对观察窗口结束时仍未检测或仍未恢复的实验,应报告超时或删失情况。只对成功完成的样本求平均,会遗漏恢复最慢或根本无法恢复的情况

Linux 内核注入:架构怎样影响错误传播

一项历史实验在 Pentium 4 与 PowerPC G4 平台上的 Linux 2.4.22 内核中,向代码、数据、栈和处理器系统寄存器注入错误,累计超过 115000115000 次。工作负载和热点分析用于确定可比较的目标范围

崩溃原因分布揭示检测位置

下图分别统计 P4 的 19821982 个崩溃样本与 PPC G4 的 872872 个崩溃样本

两个平台的内核崩溃原因分布
两个平台的内核崩溃原因分布

比例的分母是各平台的崩溃样本,不能直接比较两平台的总体失效概率。分类粒度也不同:PPC 的 Bad Area 包含空指针等坏地址访问,而 P4 单独列出了 NULL Pointer

这些数据有助于理解错误最终被哪一类机制发现,但一次坏地址异常的根因可能来自更早的指令损坏、返回地址破坏或错误写入,最终异常类型不等于原始故障类型

相同的比特翻转可以产生不同延迟

在一个 PPC 代码注入例子中,一位翻转把 mflr r0 改成了 lhax r0,r8,r0。前者读取链接寄存器,后者按寄存器计算地址并执行半字加载

新的指令仍然合法,但它访问哪里取决于当时的寄存器值。因此,同一条指令上的相同比特翻转,在不同运行状态下产生了约 12851285 到 424256424256 个 CPU 周期的崩溃延迟

栈错误的传播同样受到检测位置影响。较早发现栈越界,可以减少错误状态继续运行的时间;若损坏在较晚的访存阶段才暴露,执行期间可能已经产生更多污染

这些观察说明,错误影响由指令编码、数据布局、运行状态、硬件异常检查与操作系统处理共同决定。历史平台上的结论应结合其内核版本、体系结构和负载理解

定向故障注入:命中因果路径中的窗口

在分布式系统中,某些故障只有发生在特定事件序列中才会暴露问题。例如请求已执行、状态已持久化,但确认尚未送达时崩溃,会检验重试是否导致重复执行

定向故障注入(Targeted Fault Injection,TFI)通过识别因果有序的事件序列,在指定的最后事件执行前施加扰动。它将“何时注入”从墙上时钟时间转化为可解释的执行上下文

一个描述实验的示意配置可以写成

1
2
3
4
5
前置事件:请求到达 -> 事务写入完成
触发位置:发送确认之前
故障类型:服务进程崩溃
注入目标:当前主节点
校验条件:重试后没有重复提交,副本状态一致
定向故障注入的控制与状态同步
定向故障注入的控制与状态同步

中央控制器(CC)下发命令,本地控制器(LC)跟踪目标进程并执行注入,全局状态记录器(BK)反馈进展。ZooKeeper 可以承载命令、同步状态和进程信息,帮助确认前置事件是否完成

“观察到目标事件”和“在目标事件之前完成注入”之间仍可能存在竞态,因此需要由本地拦截点或执行控制来落实时序。确定性的触发描述,也应配合实际事件日志验证

怎样判断实验结论是否可信

有限样本带来统计不确定性

假设实验独立、来自同一个明确的抽样分布,将某种失效视为概率为 pp 的二项事件。nn 次实验中出现 kk 次失效,比例估计为

p^=kn\widehat p=\frac{k}{n}

没有观察到失效也不意味着 p=0p=0。若 k=0k=0,单侧 95%95\% 置信上界可由

(1−pU)n=0.05(1-p_U)^n=0.05

得到

pU=1−0.051/n≈3n\boxed{p_U=1-0.05^{1/n}\approx\frac{3}{n}}

例如 10001000 次独立实验零失效,对应上界约为 0.30%0.30\%。这个界限只针对约定的实验分布,无法覆盖完全没有测试过的故障类别或状态

抽样偏差与工具扰动需要分别处理

增加样本数可以降低随机误差,却无法自动修复偏差。例如只选择容易检测的指令、只测试轻负载,或者把没有触发的实验算成成功,都会使结果偏乐观

工具本身也可能影响结果:断点改变调度顺序,插桩增加执行时间,日志写入改变 I/O 压力,监测进程消耗原本紧张的资源。可以分别进行无注入运行、仅开启工具运行和实际注入运行,观察这些差异

可复现的记录应包含软件与配置版本、负载、随机种子、注入参数、实际触发事件、原始日志、输出判据和恢复动作。统计汇总适合寻找规律,具体案例分析适合解释错误怎样一步步传播

运行阶段:用现场数据修正模型

从日志报告整理为失效事件

现场测量通常先清洗和合并日志,再识别事件、提取指标,必要时建立模型并求解。一个故障可能同时触发应用超时、驱动告警和节点重启,不能把每条日志都当作一次独立失效

分析时应区分重复报告、同一故障引起的传播事件,以及真正独立的故障,并考虑监测中断或日志丢失带来的缺口

相关失效会削弱冗余

冗余模型经常从组件独立失效开始,但共享电源、网络、存储、软件缺陷和运维操作都可能同时影响多个副本

历史上对 DEC 和 Tandem 系统的分析发现,一部分问题涉及相关失效,其比例在被研究数据中约为 10%10\% 到 30%30\%。这个范围反映特定现场样本,不能直接作为所有系统的参数

对于两个副本,独立模型使用

Pr⁡(F1∩F2)=Pr⁡(F1)Pr⁡(F2)\Pr(F_1\cap F_2)=\Pr(F_1)\Pr(F_2)

而存在共同原因时,需要直接估计联合失效概率或建立共因模型。只验证“一个节点失效时另一个能接管”,不足以验证它们依赖的共享资源同时异常时的行为

从事件聚合到失效预测

一种历史分析方法先合并重复报告,再把较短时间内的记录组成错误组,将更长时间内的错误组组成事件,最后寻找事件间共有的症状

例如,可以用 1515 分钟组织错误组、用 2424 小时组织事件,并把至少出现在一半错误组中的错误类型作为症状,再合并具有共同症状的相关事件。这些时间窗和阈值是分析方法的参数,需要结合系统调整

预测可以基于这些症状、性能异常和状态转移关系,但发现相关性不等于已经识别因果。现场数据最直接的价值,是帮助发现实验模型遗漏的故障类型、共同原因和实际触发条件

把验证结果反馈到设计

故障注入得到的不只是一个覆盖率数字,还应包括未被检测的传播路径、恢复失败的位置、服务中断的分布,以及哪些错误已经越过了保护边界

若结果错误但没有告警,需要补充检测;若检测及时却无法接管,需要检查恢复状态和切换协议;若副本同时受到影响,需要重新考虑故障隔离范围。


© 2024 本网站由 renxiaodaifu 使用 Stellar主题 创建
总访问 次 | 本页访问 次
共发表 101 篇 Blog(s) · 总计 293.1k 字