3. 联锁软件安全设计基础

计算机联锁软件与普通业务软件的根本区别在于:它的失效可能直接导致列车冲突、脱轨或挤岔等安全事故。因此,联锁软件的设计不能只追求“功能正确”,而必须从安全完整性、编码规范、防御性编程、内存保护、故障安全模式到生命周期管理形成一条完整的证据链。本节围绕这条主线,讲清楚每一项要求“是什么、为什么、怎么做”。

一、安全完整性要求:先定等级,再定措施

安全完整性是指联锁软件在规定的条件下、规定的时间内完成规定安全功能的可信程度。工程上通常参照 EN 50128 / IEC 61508 的 SIL 分级思路,将联锁软件的安全功能划分为 SIL 3 或 SIL 4 等级(我国计算机联锁多按 SIL 4 目标设计安全相关功能)。等级不是标签,而是约束:它决定了开发流程、验证独立性、缺陷率指标和故障安全策略的严格程度。

落地时,首先要把“安全功能”和“非安全功能”分开识别。例如“进路锁闭与解锁”“道岔转换控制”“信号开放条件检查”属于安全功能;“站场图显示刷新”“操作日志查询”属于非安全功能。安全功能必须满足:危险侧失效率低于定量目标(如 SIL 4 对应 THR 约 10⁻⁹~10⁻⁸/小时量级),且任何单点故障不得导致危险侧输出。示例性指标可设为:安全相关模块语句覆盖率 100%、分支覆盖率 100%、MC/DC 覆盖率 100%,这是高等级联锁软件的常见验证门槛,具体数值应以项目安全计划为准。

二、编码规范:把“可验证”写进代码

编码规范的核心目的不是美观,而是让代码可审查、可分析、可证明。联锁软件通常禁用或严格限制以下语言特性:动态内存分配(malloc/free)、递归、函数指针、可变参数、goto、隐式类型转换、浮点运算用于安全判断。原因是这些特性会引入不可静态分析的控制流或不确定的运行时行为。

一个可执行的规范示例如下:所有安全变量使用固定宽度整型(如 uint16_t);布尔判断必须显式比较;每个函数只有一个出口;数组访问前必须做上下界检查;禁止使用编译器优化可能重排的共享变量,改用显式同步。下面是一段符合防御性风格的进路锁闭条件判断示例:

/* 进路锁闭条件检查:所有条件显式判断,单一出口 */
SafetyResult CheckRouteLock(const RouteCtx *ctx)
{
    SafetyResult ret = SAFETY_FAIL;
    if (ctx == NULL) { return SAFETY_FAIL; }              /* 空指针防御 */
    if (ctx->routeId >= MAX_ROUTE_NUM) { return SAFETY_FAIL; } /* 越界防御 */
    if (ctx->sectionFree != TRUE) { return SAFETY_FAIL; }  /* 区段空闲 */
    if (ctx->switchLocked != TRUE) { return SAFETY_FAIL; } /* 道岔锁闭 */
    if (ctx->conflictRoute == TRUE) { return SAFETY_FAIL; }/* 敌对进路 */
    ret = SAFETY_PASS;
    return ret;                                            /* 单一出口 */
}

注意这段代码的写法:每个条件都单独判断并直接返回失败,避免复杂的布尔表达式嵌套;失败即导向安全侧,而不是继续执行。这正是编码规范与故障安全思想在代码层面的结合。

三、防御性编程与内存保护

防御性编程假设“输入一定会有异常、调用者一定会犯错、硬件一定会翻转”。联锁软件中常见的防御手段包括:对所有外部输入(来自通信前置机、驱动采集板)做范围与合法性校验;对关键数据结构增加冗余副本并周期比对;对函数入参做空指针与越界检查;对状态机增加非法状态迁移的拒绝分支。

内存保护方面,高等级联锁软件通常采用“静态分配 + 分区保护”策略:所有安全数据在编译期确定大小,运行期不做堆分配;利用 MPU/MMU 将安全区、非安全区、通信缓冲区隔离,写越界触发异常并导向安全侧;关键变量采用双份存储(原码 + 反码或 CRC 校验),读取时校验一致性,不一致即判定数据损坏。例如对道岔表示状态采用“值 + 反码”存储,读取时执行 if ((val ^ invVal) != 0xFF) → 故障安全处理,可有效发现单比特翻转。

四、故障安全编程模式

故障安全的核心是“故障导向安全”:一旦检测到异常,输出必须落到危险侧最小的状态——信号关闭、道岔不转换、进路不解锁。编程模式上常用三种结构:

  • 双通道比较(2oo2):两套独立软件或两套硬件通道并行计算,结果一致才输出,不一致则关闭输出。
  • 带自检的看门狗:安全任务周期内必须“喂狗”,超时或程序跑飞则硬件复位并切断危险输出。
  • 输出回读校验:驱动信号开放后回读实际输出状态,与期望值不符立即关闭并报警。

下面这张图把本节的关键关系串起来:从安全完整性等级出发,经过编码规范、防御性编程、内存保护,最终汇聚到故障安全输出,并由生命周期管理提供全过程证据。

联锁软件安全设计要素关系图 安全完整性等级(SIL 3/4) 定量失效率目标 + 验证独立性要求 编码规范 禁动态分配/递归/函数指针 固定宽度类型·单一出口 防御性编程 入参校验·状态机拒绝非法迁移 冗余副本周期比对 内存保护 静态分配·MPU分区隔离 值+反码/CRC 一致性校验 故障安全编程模式 双通道比较(2oo2) · 看门狗自检 · 输出回读校验 异常 → 信号关闭 / 道岔不转换 / 进路不解锁 安全侧输出 危险侧失效率满足 SIL 目标 软件生命周期安全管理:需求→设计→编码→验证→确认→变更→退役(全过程证据与独立评估)

五、软件生命周期安全管理流程

上述技术措施必须嵌入到受控的生命周期流程中,否则无法形成可审计的安全证据。联锁软件生命周期一般包括:安全需求规格、软件需求规格、软件架构设计、详细设计与编码、单元与集成测试、系统安全确认、现场安装与验证、运营维护与变更管理、退役。每个阶段都有明确的输入、输出和验证活动,且高安全等级要求验证活动由独立于开发的人员执行。

以变更管理为例,这是实际工程中最容易出问题的环节。任何代码修改,哪怕只改一个判断条件,都必须走“变更申请—影响分析—安全再评估—回归测试—独立复核—版本发布”的闭环。影响分析要回答三个问题:改动是否触及安全功能?是否影响已通过的覆盖率证据?是否需要重新做故障注入测试?例如把进路锁闭条件中的“区段空闲”判断顺序调整,看似无害,但可能改变短路求值行为,必须重新执行该模块的 MC/DC 覆盖与故障注入用例。

实践建议:为每个安全功能建立“需求—代码—测试用例—验证记录”的双向追溯矩阵。评审时随机抽取一条安全需求,能够沿矩阵正向找到实现代码和测试用例,反向从任一安全相关函数找到其对应的需求编号,追溯链断裂即视为安全证据不完整。
常见误区:把“通过了功能测试”等同于“满足安全完整性”。功能测试只能证明正常场景下行为正确,安全完整性还需要故障注入、覆盖率分析、独立性评估和定量失效率论证共同支撑。缺少任何一环,安全论据都不成立。

综上,联锁软件安全设计是一条从等级目标到代码实现、再到流程证据的完整链条:安全完整性确定“要做到什么程度”,编码规范与防御性编程保证“代码本身可信”,内存保护与故障安全模式保证“异常时导向安全”,生命周期管理保证“全过程可追溯、可审计”。四者缺一不可,共同构成联锁软件的安全基础。