简介:面向航空航天等安全关键领域的系统安全分析资料,适合具备自动化控制、软硬件设计基础的专业研究与工程人员。资料从适航标准切入,指出现行故障模式与影响分析(FMEA)、故障树分析(FTA)在复杂航空系统中过度依赖分析者个人经验,难以全面预测异常行为与快速响应设计变更,进而引出基于时态逻辑和高阶谓词逻辑建模的形式化验证方法。内容梳理了定理证明、模型检查等主要形式化技术的发展脉络,并结合SysML、AltaRica、AADL、NuSMV、PRISM、SPIN等工具说明其在系统设计、硬件验证、软件验证中的实际用途;章节以民用飞机电气系统安全评估为案例,演示从系统建模、安全需求描述到自动验证的完整流程,有助于理解多时钟域信号交叉等隐患如何通过严谨数学证明加以排除。压缩包内含1个PDF文档,大小约1.96MB,英文原版结构清晰,目录完整,可作研习材料与工程参考。目前已有83人学习下载。
1. 形式化模型在航空安全里到底解决什么问题
形式化模型安全分析方法,在航空圈子里早不是论文里刷概念的技术,它是DAL A/B级机载软件确认与验证环节里,少有的能把“组合失效”讲清楚的手段。传统FMEA擅长枚举单点故障,遇到两个故障同时出现、或者时序错乱导致控制指令反向时,基本靠拍脑袋;形式化模型把系统行为抽象成状态集和迁移关系,再用模型检测工具穷举搜索,把“某条坏路径”变成可回放的反例。这篇文章面向做机载软件验证、航电系统安全性评估、以及做ARP4761流程落地的工程师,讲清楚建模选型、最小可跑案例、常见翻车点和落地组织方式。
2. 形式化模型安全分析方法是怎么搭出来的:从需求到状态空间
2.1 三个基本件:状态集、迁移关系和时间抽象
形式化模型的核心是Kripke结构,一句话解释就是:用状态集合表示系统可能处的局面,用迁移关系表示下一步能跳到哪些局面,用原子命题来描述“这个状态里哪些条件成立”。做安全分析时,我不追求把模型的每个细节都还原成代码级精度,而是把安全相关的事件和故障模式显式建模出来,让模型检测工具自己去遍历所有可能性。
拿一个高度保护系统举例。状态变量至少要有:真实高度、目标高度、传感器是否偏置、飞行员是否接管、当前升降舵指令。迁移关系描述的是“一旦控制器接到某个高度读数,升降舵怎么变化,进而真实高度怎么变化”。时间抽象也很关键,常见的做法是把连续时间切成分步周期,每个迁移代表一个控制周期或一个离散事件,比如故障注入周期、告警刷新周期。
很多工程师在这步就开始懵:为什么安全分析方法非要把系统写成数学结构?因为只有把“系统下一步允许做什么”约束成严格集合,工具才能回答“是否存在一条路径让安全属性被破坏”。你写成自然语言需求,哪怕写得再谨慎,工具也没法判断;写成状态机之后,连“两个需求之间互相矛盾”这种问题也能自动查出来。
2.2 从ARP4761里的安全目标翻译成待验属性
在航空工程里,ARP4761给出了功能危害评估(FHA)、初步系统安全评估(PSSA)、系统性安全评估(SSA)的完整闭环。传统做法是先识别失效条件,定出安全等级,再用故障树和FMEA去论证。形式化模型安全分析方法在这里补的缺口是:把各位安全目标直接转写成可验证属性,让计算机逐条检查。
原则很简单:每条安全需求最终会被削成两个部分,一是“什么是不允许发生的坏情况”,二是“在什么前提条件下不允许发生”。写成形式化语言的公式就是AG (premise -> !bad_state)。比如“当高度传感器发生正向偏置且飞行员未接管时,真实高度不允许进入包线上限”,翻译成CTL公式就是AG (sensor_biased & !pilot_override -> altitude < 5)。这里的AG表示所有可达路径的所有状态都满足条件,也就是说这种坏情况绝对不能被走到。
做这些转换时,我一般会把安全目标拆成一张追踪表:FHA里的失效条件编号、PSSA里的分配需求、形式化属性、源需求、验证结果。审查员来看时,不需要重新读一遍模型,只需要顺着追踪表逐条对照,能省掉大量解释成本。
2.3 属性语言:CTL、LTL和时序逻辑的取舍
安全属性用什么逻辑语言写,直接决定你能表达哪类问题。CTL是分支时间逻辑,描述的是“从某个状态出发是否所有路径都存在某种可能性”,它的关键字包括AG、EF、AF。LTL是线性时间逻辑,描述的是“单条执行路径上随时间必然发生什么”,关键字主要是G、F、U、X。
航空安全属性里最常见的是不变式,也就是AG safe,CTL可以准确表达。还有一类是“故障最终一定会被检测到”,对应AF detect。如果是告警逻辑,用LTL写会更自然,比如“危险状态持续时,告警必须持续直到飞行员确认”可以写成G (danger -> (warning U acknowledge))。
这里有一个容易踩的坑:CTL的AG (p -> EF q)和LTL的G (p -> F q)看起来都像“如果p最终会q”,但它们在有多个分支的模型里含义完全不同。做工具选型时,一定先确定你手上的安全目标需要的是分支性质还是线性性质,再去选模型检测器,否则验证结果很容易被误读。
2.4 工具选型的三个标准
选工具我只关心三件事:状态空间规模扛不扛得住、属性语言能不能覆盖安全目标、反例能不能给到需求工程师看得懂。我自己日常用得最多的三个方向是NuSMV、SPIN和TLA+。
| 工具 | 模型语言 | 逻辑支持 | 最适合的场景 |
|---|---|---|---|
| NuSMV | 同步有限状态机 | CTL/LTL | 机载控制逻辑、需求一致性、失效注入验证 |
| SPIN | Promela,异步进程 | LTL | 通信协议、握手逻辑、总线仲裁 |
| TLA+/TLC | 数学状态机 | TLA | 协议级规格、抽象需求建模 |
NuSMV对上手最友好,支持有限状态枚举,安全属性写成CTL非常直观,反例路径短而且能一步步回放。SPIN更擅长处理并发和异步事件,比如AFDX网络或串口通信。TLA+适合做顶层协议验证,但它对数学要求更高,很多一线工程师学起来比较费劲。
选择时还有一个隐藏标准:工具本身要能输出完整的反例轨迹,而不是只给一个“false”结论。安全分析方法的价值一半在反例的回放里,另一半在属性设计里。如果一类工具只能告诉你安全目标不成立却说不清哪条路径,落地价值会大打折扣。
3. 把航空安全属性跑起来:一个最小高度保护模型验证
3.1 建模输入和需求描述
我习惯用一个极简但能跑通全流程的模型来训练团队,这里以“高度保护”子系统为例。真实工程里高度控制要涉及气压高度、无线电高度、惯导、余度管理等,这里只保留安全分析需要的抽象:真实高度、传感器读数、传感器偏置故障、飞行员接管、升降舵指令。
安全需求设定为:当高度传感器正向偏置(读数持续偏高)且飞行员没有接管时,控制器不能盲目继续让飞机爬升到包线上限。这是一个极其典型的“安全分析方法要解决的组合失效”问题:单看传感器故障本身没有危害,危害来自故障与控制器闭环节流同时出现。
变量范围我故意取得很小。真实高度只取0到5共六档,0代表过低包线、5代表过高包线,正常目标高度取2或3。很多新手一上来就把真实高度建模成0到15000英尺的整数,结果状态数量爆炸,运行半天出不来结果,这就是过度细化的坑。
3.2 用NuSMV建立最小模型
代码可以直接存成height_protect.smv,核心结构是同步有限状态机。
MODULE main VAR altitude : 0..5; -- 真实高度等级:0过低,5过高 target : 2..3; -- 正常包线内的目标高度 sensor_biased : boolean; -- 传感器正向偏置故障 pilot_override : boolean; -- 飞行员是否接管 DEFINE telemetry := case sensor_biased : 5; -- 故障时传感器读数被拉到最高 TRUE : altitude; esac; ASSIGN init(altitude) := 2; init(target) := 2; init(sensor_biased) := FALSE; init(pilot_override) := FALSE; next(pilot_override) := {TRUE, FALSE}; next(sensor_biased) := {FALSE, TRUE}; next(altitude) := case telemetry > target & altitude < 5 : altitude + 1; telemetry < target & altitude > 0 : altitude - 1; TRUE : altitude; esac; SPEC AG (sensor_biased & !pilot_override -> altitude < 5)NuSMV里ASSIGN块内的next(...)用来定义状态迁移。init(...)给初始状态。{TRUE, FALSE}表示非确定选择,这一步特别重要:安全分析不能假设环境友好,传感器故障和飞行员行为都要被建模成“任意时候可能发生”。telemetry是DEFINE,本质是个宏,用来表达控制器看到的传感器读数。当sensor_biased为真时,读数恒为最高档5,控制器就会误认为真实高度偏高,从而持续下压或保持现状,导致真高不降反升。
3.3 运行验证并解读反例
执行命令:
NuSMV height_protect.smv输出里会看到类似这样的信息:
-- specification AG (sensor_biased & !pilot_override -> altitude < 5) is false -- as demonstrated by the following execution sequence反例序列的核心状态会逐步显示sensor_biased = TRUE、pilot_override = FALSE,然后altitude从2一步步增加到5。这条路径说明:一旦偏置故障发生,系统在闭环控制下会持续爬升,直到触碰包线上限,并且没有飞行员接管机制能及时打断。
这里要特别注意:这个模型故意没有加入任何缓解机制,所以反例出现并不意外。真正的安全分析价值在于把它当成“对照组”,再往模型里加抑制逻辑,比如增加故障检测状态或限制指令条件,然后再次验证,直到安全属性成立。我一般会把这一步定性为“失效路径确认”,反馈给FMEA团队,让他们把这条组合失效补进故障树。
3.4 把模型检测结果回填到FMEA
传统FMEA里,传感器偏置往往只被标记为“错误读数,已由飞行机组程序处理”。但它没有回答一个问题:错误读数会不会正好触发控制器连续动作累积到危险边界?形式化模型给出了一条完整状态路径,可以转换成故障树里的一个序列割集:传感器偏置 → 遥测等于上限 → 升降舵持续保持 → 高度到达上限。
我自己处理的流程是:验证结果出来以后,先和FMEA工程师开一个短会,把反例轨迹逐条打印出来,让他们在FMEA表的“最坏后果”栏里补一句“存在组合失效路径”。这种协作方式比单纯交付一份验证报告有用得多,因为审查员关心的是安全分析结论的一致性,而不是某个工具跑没跑过。
4. 形式化模型安全分析常见问题:五个必须提前管住的坑
4.1 状态爆炸:为什么加了四个变量就卡死
现象:模型变量不多,一共才十几个布尔和枚举变量,但模型检测工具跑了几十分钟没结果,内存飙到几个G甚至直接被杀掉。
原因:状态数等于各变量取值范围的乘积。只要你把一个本来就该离散化的连续量建模成0..50000的整数,再配两三个并发模块,状态空间就会瞬间爆炸。NuSMV不是通过随机采样来验证,而是真的遍历所有状态;哪怕只多一个取值范围100的变量,遍历代价就乘了100倍。
解决:抽象降维是安全分析方法的基本功。把不敏感的变化范围压缩成少数区间;把两个温度传感器合并成一个“两地是否一致”布尔量;把不需要同时关心的数据通道折叠成一条抽象连接。模型中保留的变量必须满足一个条件:它们只会影响安全属性,不影响安全目标以外的系统行为。换句话说,为了验证“包线是否会被突破”,就不需要精确知道包线内每个中间高度。
4.2 属性自己写错反而验证为真
现象:某个安全属性跑出来是true,所有人很兴奋,结果换一个表达方式重新验证,发现同样场景下存在危险路径。
原因:这是安全分析方法里最典型的翻车场景。属性是从实现代码逆向推出来的,想表达的“不会坏”其实把前提条件写窄了,比如漏掉了pilot_override的取反,或者漏掉了sensor_biased的组合。
解决:所有CTL/LTL属性必须从需求评审阶段的自然语言安全目标中正向转换,独立的验证人员要重新翻译一遍,不能由写模型的人自己既建模又写属性。每次验证前,我会额外检查一条负属性:故意让坏状态在被禁止的前提成立时可达,确认工具确实能发现反例。这个“模型是否足够敏感”的冒烟测试非常值钱,能在正式验证前暴露出属性写得过严或过松的问题。
4.3 把环境输入当成常量,验证结果无人信
现象:模型里所有输入都被固定成初始值,验证结果显示系统安全,交付给审查员时被质疑:你没有考虑飞行员操作吗?你没有考虑故障随机性吗?
原因:模型检测器遵循的是“系统与环境共同演化”的思路。飞行员操作、天气变化、传感器漂移、维护动作都属于环境。如果你在ASSIGN里把它们写成FALSE或固定值,等于假设环境永远友好,这不符合安全分析场景。
解决:把环境输入都设置为非确定集合,比如next(pilot_override) := {TRUE, FALSE}。不要担心非确定会让反例变长,这正是让人信服的原因:系统必须在所有可能的飞行员行为、所有可能的失效时机下仍然满足安全属性,证明才是成立的。
4.4 模型与DO-178C证据链脱节
现象:安全分析通过形式化模型验证,但适航审查时审查员不认可,说“我看不懂这个模型和源码的关系”。
原因:DO-178C并不要求每个项目都用形式化方法,但要求你用的技术有明确的目标映射,并能说明工具的输出如何支撑结论。只交付模型文件、源代码和验证结果,没有建立双向追溯,审查员自然不认。
解决:在PSAC里单独列一节形式化安全分析方法的范围,说明模型与哪些需求对应,属性与哪些FHA条目对应,模型检测输出如何作为补充证据。每次验证的记录都要留日志,包括模型文件哈希、工具版本、命令行、输出文件、反例轨迹。我习惯把它们归档到配置管理库里,和代码一起打标签,这样审查时几分钟就能调出完整证据链。这里工具版本更要固定,因为不同版本的模型解析顺序可能产生结果差异,而这种差异属于审查最讨厌的“黑匣子”。
4.5 反例太长没有人愿意复现
现象:生成的反例有几十上百步,需求工程师和飞行控制团队看完开头就放弃,安全分析报告变成一张无人认领的打印纸。
原因:模型一次性建得太完整,把许多与分析目标无关的中继状态也都纳入了反例轨迹。结果反例确实真实,但没人愿意手工逐条检查。
解决:用“限界模型检测”先检查短步数内是否有反例,比如NuSMV -bmc -bmc_length 10,如果短步数能发现坏路径,优先把短路径作为问题暴露。另一个技巧是把无关的中间计算折叠掉,比如用DEFINE隐藏遥测内部计算,只保留影响安全属性的关键状态。反例交付规范也很有效:每一行只列“新的故障状态、控制指令、关键变量变化”,剔掉重复的仪表读数,让飞行控制工程师在五分钟内能读懂。
5. 航空应用落地的三个场景:需求一致性、CI、告警漏报验证
5.1 需求一致性与完整性:在需求评审阶段跑形式化模型
传统需求评审是在文档层面进行的,靠专家肉眼找矛盾。比如一条需求说“当高度低于最低包线时启动爬升”,另一条说“当存在失速威胁时禁用一切爬升指令”。这两条单独都合理,放在一起就可能在某些状态下无法决策。用形式化模型做安全分析方法时,可以在评审前把需求翻译成状态机输入,然后验证一个简单的不变式:每个可达状态里至少有一个允许执行的动作。
模型语言并不需要和最终软件一致,关键技术是让每条需求对应一个状态变量和迁移规则。NuSMV会告诉你是否存在一个初始状态和一条路径,让两条需求同时试图执行冲突动作。这种验证可以把需求问题提前到方案阶段解决,而不是等编码后在测试里发现,省下的返工成本远高于建模投入。
5.2 把验证模型放进CI:最小化安全模型每日回归
形式化模型的安全分析方法不一定非要放在适航认证节点上才用,它完全可以进日常持续集成。我现在会把关键安全属性做成一个独立小模型库,只要需求或接口发生变化,就重新跑一轮模型检测。
脚本可以这样组织:
#!/bin/bash set -u MODEL_LIST="height_protect.smv tcash_alert.smv arinc429_monitor.smv" for model in $MODEL_LIST; do NuSMV "$model" > "run_${model%.smv}.log" 2>&1 if grep -q "is false" "run_${model%.smv}.log"; then echo "[FAIL] $model contains a counterexample" exit 1 fi if grep -q "specification" "run_${model%.smv}.log"; then echo "[PASS] $model verified" else echo "[WARN] $model has no verifiable property" fi done这个脚本的逻辑很简单:NuSMV对每条属性都会给出“is true”或“is false”,grep到is false就让CI失败。有人会担心模型文件变化导致结果不可比,所以我会对模型文件做校验,比如用sha256sum生成指纹,连同日志一起归档。这样任何一次需求改动导致的安全属性被破坏,都能在当天被捕捉到,而不是在测试阶段或审查阶段才暴露。
CI里的模型检测耗时会被控制得很短,所以这里的模型必须是高度抽象的。它不追求验证完整系统,而是保护“上次验证过、这次不准被悄悄改坏”的安全属性。这个习惯在项目后期非常值钱,特别是人员变动频繁时,它相当于给安全分析上了一道后悔药。
5.3 飞行告警逻辑:用LTL验证“危险最终必被警告”
告警逻辑是航空应用里特别适合形式化验证的领域。比如TCAS类的防撞逻辑,要求“当入侵飞机进入某一区域时,必须向飞行员发出告警”。传统测试很难覆盖所有人都不会在错误时序下关闭告警,或者说很难证明不存在漏报路径。
形式化建模时把告警系统单独抽出来,输入变量包括探测到危险、告警被抑制、飞行员确认、飞行阶段切换。LTL属性可以这样写:
G (danger -> F warning)这条属性表达的是:在任何一条路径上,只要危险状态发生,将来必有告警事件。验证若失败,反例会显示一条路径,危险状态之后连续几个周期都没有告警信号。接下来就要去判断反例是属性写得太强,还是真存在漏报间隙。
反例回放时我最关心的是时序细节:危险信号在离上升沿差一个时钟周期时被触发,告警逻辑因为状态机的同步策略而漏掉了一个采样窗口。这类问题在测试台上往往是偶发,在模型里却是确定的。用LTL验证之后,把反例轨迹贴到问题单里,软件团队能在半小时内定位是哪条状态迁移条件漏写了。
6. 提高验证效率的实用技巧:抽象降维与反例交付
6.1 抽象降维三件事
第一部分是区间化。连续参数按安全边界和危险边界折成三到五个档位,只要不改变安全属性的真值,就压缩范围。第二部分是去掉“旁观状态”。如果某个变量不出现在属性里,也不影响其他变量的下一次取值,它就是不相关的旁观变量,直接删掉。第三部分是故障模式归一化。同一类型传感器的一组漂移值,只要都导向同一个“读数偏高”的决策分支,就合并成一个布尔量。这三步做下来,状态空间通常会缩小几个数量级。
6.2 反例交付给确认和适航审查的格式
我一般用一张四列表格交付反例:周期、状态变化、控制指令变化、解释。表格不会太长,一般一页纸讲清楚一条危险路径。审查员可以对照它回放模型输出,也可以直接把它当作安全评估报告的附件。反例要配上模型文件、属性文件和工具日志路径,不能只给一张截图,否则就是把一个黑匣子交了出去。
这种“先压缩、再解释、最后归档”的处理方式,让我把这些方法从研发团队的玩具变成了适配审查的证据链。我在每个项目里都会刻意保留一份反例归档,它们就是安全分析师的经验库,新人在接手时能直接看这些反例理解系统最危险的行为。这也是我一直坚持把模型检测结果落到纸面、落到追踪表里的原因。做到这一步,形式化模型安全分析方法才算真正在团队里扎下根,希望帮到你。
本文还有配套的精品资源,点击获取