Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

3.7 程序流监控——检验大脑有没有跳过步骤

你的EPS(电动助力转向)控制任务每5毫秒执行一次。它的执行序列精确地分为四个步骤:

CP1 — ReadSensors:通过SPI读取方向盘转角传感器和扭矩传感器的原始数据。 CP2 — CalculateTorque:将传感器原始值转换为物理量,计算驾驶员施加的转向力矩,结合车速计算助力力矩。 CP3 — ApplyLimits:对助力力矩施加饱和限制(最大80Nm)、变化率限制(最大20Nm/ms)和温度降额曲线。 CP4 — OutputCommand:将最终转向助力指令写入电机驱动器的PWM占空比寄存器。

这是一个完美的线性序列:每一步的输出是下一步的输入,缺一不可。如果跳过了CP2(CalculateTorque),CP3收到的助力力矩就是上一周期的旧值。如果跳过了CP3(ApplyLimits),CP4将未经过限幅处理的原始指令直接送入电机驱动器,理论上,一个软件故障可能让电机输出300Nm的扭矩,足以在0.1秒内将方向盘从中心位置拧到极限位置。

在一个闷热的下午,这个噩梦变成了现实。

EPS控制任务中有一个局部数组float32 tempBuffer[32],用于扭矩计算的中间滤波。由于一个递归调用路径在某种极罕见的车速传感器故障模式下被触发,栈使用量超出了静态分析的上限。tempBuffer[31]溢出了栈顶,恰好覆盖了栈帧中的返回地址。

CalculateTorque()执行完毕,它没有返回它的调用者(主任务循环),而是“返回“到了OutputCommand()的入口地址。CPU从返回地址寄存器中取出被篡改的值,跳转到CP4,完美跳过了CP3(ApplyLimits)。

电机驱动器接收到的指令是一个从未经过限制的、可能超出物理安全范围的原始扭矩值。更可怕的是,这个跳过是静默的,CPU没有崩溃,WatchDog没有被触发,任务在5ms后被重新调度,进入下一个周期的CP1……

而真正保护你的,可能只有一层薄薄的防线,程序流监控(Program Flow Monitoring)

传统的Alive Supervision只能证明“CPU还在运行“,不能证明“CPU运行在正确的路径上“。一个完全崩溃的任务踢不了狗,这是最容易检测的。但一个局部跳转的任务,每一步都在执行,只是跳过了关键的安全计算:在Alive Supervision看来是完全正常的。这就是为什么需要逻辑监控(Logical Supervision):不是监控“有没有跑“,而是监控“跑的路径对不对“。

WdgM的代数数据模型

AUTOSAR的WdgM(Watchdog Manager)定义了一套完整的程序流监控框架。它的核心数据模型由三个层次构成:

第一层:SupervisedEntity(被监控实体)

  • 可以是一个Task、一个Runnable、或一个中断服务例程。
  • 每个SupervisedEntity有其独立的监控配置:本地状态、允许的Checkpoint集合、Transition表格。

第二层:Checkpoint(检查点)

  • 在代码中插入的特殊标记点。
  • WdgM提供了函数 WdgM_CheckpointReached(SupervisedEntityId, CheckpointId) 用于在代码中标记执行进度。
  • Checkpoint分为两类:
    • StartCheckpoint:作为程序流的起始节点(起点)。
    • FinalCheckpoint:必需到达的终止节点(如任务执行的最后一个步骤)。
    • 中间Checkpoint:不标记起始或终止,仅标记中间状态。

第三层:Transition(转移)

  • 定义了从一个Checkpoint跳转到另一个Checkpoint是否合法。
  • 配置表 Transitions[] 是一个二维布尔矩阵:Transitions[source][destination] = TRUE 表示从source CP到destination CP的转移是允许的。

迁移表实例

以我们的EPS任务为例,配置表如下:

Transitions[CP1_ReadSensors]  = {FALSE, TRUE,  FALSE, FALSE}
Transitions[CP2_CalculateTorque] = {FALSE, FALSE, TRUE,  FALSE}
Transitions[CP3_ApplyLimits]  = {FALSE, FALSE, FALSE, TRUE}
Transitions[CP4_OutputCommand] = {FALSE, FALSE, FALSE, FALSE}  // 终止点

这个表格的意思是:

  • 从CP1只能到CP2(不允许CP1→CP3,CP1→CP4)
  • 从CP2只能到CP3
  • 从CP3只能到CP4
  • CP4是终结点,不能向任何其他Checkpoint转移

当栈溢出导致跳过了CP3(ApplyLimits),程序流变成了 CP2 → CP4。WdgM检查Transitions[CP2][CP4] = FALSE,非法转移。系统检测到异常。

监控的两根支柱:逻辑 + 时间

逻辑监控(程序流是否正确)和时间监控(程序是否在规定时间内完成)必须同时工作,缺一不可。它们就像免疫系统的两道防线,抗体识别抗原(逻辑)和炎症反应(时间压力)必须协同作用。

时间监控(Deadline Supervision)

每个SupervisedEntity有两个时间约束:

  • 最小执行时间(MinExecutionTime):任务从StartCheckpoint到FinalCheckpoint的最短耗时。如果太快(例如因为跳过了某步),说明发生了故障。
  • 最大执行时间(MaxDeadline):任务的最长完成时限。超时意味着任务阻塞、死锁或无限制循环。

在我们的EPS案例中:

  • MinExecutionTime = 1200微秒(正常执行4个步骤的最短时间)
  • MaxDeadline = 5000微秒(5ms周期)

如果栈溢出导致跳转到CP4,执行时间可能只有800微秒(跳过了CPU负载最重的CP2和CP3)。800 < 1200 → 最小执行时间违规 → 故障。

逻辑监控(Logical Supervision)

WdgM内部维护每个SupervisedEntity的状态变量:

  • PreviousCheckpointId_internalLogic:上一次到达的Checkpoint ID。
  • IsInternalGraphActive:当前是否有激活的程序流图(从StartCheckpoint开始后设为TRUE)。

算法伪代码:

void WdgM_UpdateLocalStatus(SupervisedEntityId seId, CheckpointId cpId) {
    // 1. 如果是起始检查点
    if (IsStartCheckpoint(seId, cpId)) {
        if (IsInternalGraphActive(seId)) {
            // 图已经激活,但又收到起始点 = 程序流未正常终止就重启
            ReportFault(seId, WDGM_FAULT_GRAPH_RESTART);
        }
        ActivateGraph(seId);
        SetPreviousCheckpoint(seId, cpId);
        return;
    }

    // 2. 如果不是起始检查点但图未激活
    if (!IsInternalGraphActive(seId)) {
        // 未从起始点开始 = 程序流异常
        ReportFault(seId, WDGM_FAULT_UNEXPECTED_CHECKPOINT);
        return;
    }

    // 3. 检查转移是否合法
    CheckpointId prevCp = GetPreviousCheckpoint(seId);
    if (!IsTransitionValid(seId, prevCp, cpId)) {
        // 非法转移!
        ReportFault(seId, WDGM_FAULT_ILLEGAL_TRANSITION);
        return;
    }

    // 4. 更新状态
    SetPreviousCheckpoint(seId, cpId);

    // 5. 如果是终止检查点
    if (IsFinalCheckpoint(seId, cpId)) {
        // 检查所有必需的终结点是否都到达了
        if (!AllMandatoryFinalCheckpointsReached(seId)) {
            ReportFault(seId, WDGM_FAULT_MISSING_FINAL_CHECKPOINT);
        }
        DeactivateGraph(seId);
    }
}

转移表的设计本质上是将无限的错误执行路径压缩到有限的状态空间中。一个程序可能的执行流程数是天文数字。但通过插入n个Checkpoint和m个合法转移,我们将所有可能路径分类为“合法路径“(出现在转移表中)和“非法路径“(不出现)。这不是穷举所有错误,而是定义“什么是对的“,然后将一切偏离视为故障。

外部看门狗:免疫系统的第二道防线

逻辑监控和时间监控由WdgM执行。但WdgM本身运行在主MCU上。如果主MCU完全停止(时钟失效、电源跌落、内存全腐),WdgM也无法执行监控。

此时需要外部看门狗(External Watchdog),一颗独立于主MCU的监控芯片(如Infineon TLF35584系统基础芯片、NXP PF5020电源管理芯片),通过挑战-响应协议与主MCU通信。

挑战-响应协议的运作机制

  1. 外部看门狗(WD)生成一个伪随机数,通过SPI/WDI线路发送给主MCU。
  2. 主MCU的WdgIf(看门狗接口)模块接收这个“挑战“。
  3. WdgIf调用WdgM的 WdgM_MainFunction(),触发所有SupervisedEntity的本地状态汇总。
  4. WdgM将所有本地状态聚合为一个全局状态(ALL_OK / FAILED / EXPIRED)。
  5. 主MCU计算出响应值(例如挑战数据+全局状态的CRC),发回给外部看门狗。
  6. 外部看门狗验证响应:
    • 如果CRC不匹配(全局状态不一致,说明软件逻辑故障)→ 触发复位。
    • 如果CRC正确但全局状态为FAILED → 触发复位。
    • 如果CRC正确且全局状态为ALL_OK → 重置看门狗定时器。
    • 如果超时未收到响应 → 触发复位。

这个挑战-响应机制的巧妙之处在于:主MCU不仅要“活着“(能回答),还要“清醒“(回答正确)。就像免疫系统中的抗原呈递细胞(APC)不仅要存在,还必须正确地展示MHC-抗原复合体,T细胞才会认为“一切正常“。如果APC展示了错误的抗原,或者完全没有展示,免疫系统就将其视为“被感染“并清除。

现实场景的复杂程序流

实际的程序流比简单的线性序列复杂得多。考虑一个包含分支和循环的复杂控制算法:

场景:制动系统的制动力分配

StartCP = CP1_ReadWheelSpeeds

CP1_ReadWheelSpeeds → CP2_FilterSpeedData
CP2_FilterSpeedData → CP3_CalculateSlipRatio

CP3_CalculateSlipRatio [SlipRatio_OK] → CP5_ApplyBrakePressure
CP3_CalculateSlipRatio [SlipRatio_HIGH → EBD需要在后轴降压制] → CP4_ApplyEBD
CP4_ApplyEBD → CP5_ApplyBrakePressure

CP5_ApplyBrakePressure → CP6_VerifyPressureFeedback (FinalCP)

转移表:

Transitions[CP3][CP4] = TRUE  // 分支1:EBD路径
Transitions[CP3][CP5] = TRUE  // 分支2:正常路径
Transitions[CP3][CP2] = FALSE // 不允许往回跳!

WdgM支持这种有向无环图(DAG):因为它的转移表允许一个源对应多个合法目标。只要实际执行路径的每一步都在转移表中,就是合法的。WdgM不关心程序的内部逻辑,它只关心“你走了哪条路“是否在允许的地图中。

循环处理

如果程序需要在一个循环中反复到达同一个Checkpoint呢?例如:

CP1 → CP2 → CP3 → CP2 → CP3 → CP2 → CP3 → CP4 (FinalCP)

WdgM允许在转移表中定义自环:

Transitions[CP2][CP2] = FALSE
Transitions[CP2][CP3] = TRUE
Transitions[CP3][CP2] = TRUE  // 允许回到CP2

这样,CP2→CP3→CP2→CP3就是合法的。WdgM使用一个计数器来限制允许的循环次数(防止死循环),超过上限后触发故障。

检测能力与漏检场景

程序流监控并非银弹,它也有检测极限:

能检测的故障

  1. 非法程序跳转(跳转到错误的Checkpoint)
  2. 跳过关键步骤(从CP2跳到CP4)
  3. 程序流未从起始点启动
  4. 缺失必需的终结点
  5. 执行时间超时(Deadline Supervision)
  6. 执行时间过快(MinExecutionTime)
  7. 外部看门狗超时

不能检测的故障

  1. Checkpoint标记正确到达,但Checkpoint之间的计算逻辑错误(例如限幅函数内部使用了错误的阈值:程序路径正确,数据结果错误),这需要E2E保护和其它数据完整性措施。
  2. 恰好跳转到开始函数的地址(没有跳过任何Checkpoint)。这需要外部看门狗的时间验证。

程序流监控解决的是控制流完整性(CFI)问题。检测粒度取决于Checkpoint的插入密度,越多越精细,但开销也越大。实际工程中通常在每个SIL相关软件分区的入口和出口插入Checkpoint。

WdgM通过Checkpoint-Transition模型,将被监控程序的可能执行路径约束在一个预定义的合法集合中。但它只能验证路径的正确性,不能验证数据的正确性。CRC和E2E保护管数据完整性,WdgM管路径完整性,两者必须协同工作。

本篇小结

  1. 程序流监控(Logical Supervision)通过Checkpoint-Transition模型定义合法执行路径,栈溢出导致CP2→CP4的非法跳转会被WdgM转移表捕获,而Alive Supervision对此完全盲视。
  2. Deadline Supervision与Logical Supervision协同工作:MinExecutionTime防止执行过快(跳过计算步骤),MaxDeadline防止超时(死锁或阻塞)。
  3. WdgM内部维护每个Supervised Entity的PreviousCheckpointId和程序流图激活状态,任何不在转移表中的转换立即触发故障,非法起始点或缺失终结点同样被检测。
  4. 外部看门狗通过挑战-响应协议不仅验证主MCU“活着“,还验证“清醒“——WdgM全局状态被编码进CRC响应,软件栈故障时响应错误触发硬件复位。
  5. 程序流监控的检测粒度取决于Checkpoint插入密度,实际工程中通常在安全相关软件分区的入口和出口设置;它管路径正确性,数据正确性仍需E2E保护。

【下集预告】: WdgM在Checkpoint转移表中抓住了CPU跳过限幅计算,然后呢?故障被检测到之后,系统该做什么(复位、降级、还是什么都不做)?一个EPB在120km/h时如果检测到故障就施加驻车制动,后轮瞬间抱死,比不制动更致命。安全状态不是“关机“这么简单,它是一个依赖车速、场景和时间的条件决策树。下一节看FHTI和FTTI这两个时间常数的数学约束如何决定每一个故障的最终命运,以及为什么有时“刻意不做任何事“才是最安全的响应。