## 一、问题背景:为什么高优先级任务卡死更危险 在RTOS中,高优先级任务一旦进入死循环或永久阻塞,会抢占CPU或占用关键资源,导致低优先级任务饿死,系统看似运行实则瘫痪。硬件看门狗(IWDG)只能检测主循环是否喂狗,若高优先级任务卡死但中断仍正常,硬件看门狗可能被中断服务程序误喂,从而掩盖故障。因此,必须引入**任务级监控**,在软件层面检测每个任务的运行状态。 ## 二、检测原理:心跳表与软件看门狗 核心思想:每个任务周期性更新自己的“心跳”时间戳,监控任务(通常为低优先级或专用任务)定期检查所有心跳,若某个任务超过阈值未更新,则判定为卡死。 - **心跳表**:全局数组,每个任务对应一个`last_heartbeat`变量,记录上次正常运行的Tick值。 - **监控任务**:以固定周期(如100ms)扫描心跳表,计算当前Tick与`last_heartbeat`的差值,若大于`max_idle_ticks`(如500ms),则触发恢复策略。 - **喂狗操作**:每个任务在关键循环或阻塞等待后更新自己的心跳,注意避免在中断中更新(除非使用中断安全版本)。 ## 三、FreeRTOS实现步骤 ### 1. 定义心跳表结构 ```c #include "FreeRTOS.h" #include "task.h" #define MAX_TASKS 10 #define HEARTBEAT_TIMEOUT_MS 500 #define MONITOR_PERIOD_MS 100 typedef struct { TaskHandle_t handle; TickType_t last_heartbeat; const char *name; } HeartbeatEntry; static HeartbeatEntry heartbeat_table[MAX_TASKS]; static int num_tasks = 0; ``` ### 2. 注册任务与更新心跳 ```c void TaskMonitor_Register(TaskHandle_t handle, const char *name) { if (num_tasks < MAX_TASKS) { heartbeat_table[num_tasks].handle = handle; heartbeat_table[num_tasks].name = name; heartbeat_table[num_tasks].last_heartbeat = xTaskGetTickCount(); num_tasks++; } } void TaskMonitor_Heartbeat(TaskHandle_t handle) { for (int i = 0; i < num_tasks; i++) { if (heartbeat_table[i].handle == handle) { heartbeat_table[i].last_heartbeat = xTaskGetTickCount(); break; } } } ``` ### 3. 监控任务实现 ```c void vTaskMonitor(void *pvParameters) { TickType_t last_wake = xTaskGetTickCount(); const TickType_t period = pdMS_TO_TICKS(MONITOR_PERIOD_MS); for (;;) { vTaskDelayUntil(&last_wake, period); TickType_t now = xTaskGetTickCount(); for (int i = 0; i < num_tasks; i++) { TickType_t diff = now - heartbeat_table[i].last_heartbeat; if (diff > pdMS_TO_TICKS(HEARTBEAT_TIMEOUT_MS)) { // 任务卡死,执行恢复策略 configASSERT(0); // 或调用系统复位、任务删除等 // 示例:打印错误并复位 printf("Task %s stuck!\n", heartbeat_table[i].name); NVIC_SystemReset(); } } } } ``` ### 4. 在任务中调用心跳更新 以高优先级任务为例,在其主循环或关键点调用: ```c void vHighPriorityTask(void *pvParameters) { TaskMonitor_Register(xTaskGetCurrentTaskHandle(), "HighTask"); for (;;) { // 执行任务工作 // ... // 更新心跳,放在循环末尾或阻塞等待前 TaskMonitor_Heartbeat(xTaskGetCurrentTaskHandle()); // 可能阻塞等待事件,但阻塞时间不能超过超时阈值 if (xQueueReceive(xQueue, &data, pdMS_TO_TICKS(200)) == pdTRUE) { // 处理数据 } // 注意:阻塞时间需小于HEARTBEAT_TIMEOUT_MS,否则会被误判 } } ``` ### 5. 创建监控任务 ```c void App_Init(void) { xTaskCreate(vTaskMonitor, "Monitor", 256, NULL, 1, NULL); // 低优先级 // 创建其他任务... } ``` ## 四、关键注意事项 - **优先级设置**:监控任务应设为最低优先级,避免影响实时性,但需确保其能周期性运行。若所有任务都卡死,监控任务也无法运行,此时需依赖硬件看门狗兜底。 - **阻塞时间控制**:任务在阻塞等待(如队列、信号量)时,若阻塞时间超过超时阈值,会被误判。解决方案:将阻塞时间拆分为多个小段,每段后更新心跳;或使用`xTaskGetTickCount`在阻塞前后计算差值,但更推荐前者。 - **中断安全**:若在中断中更新心跳,需使用`portYIELD_FROM_ISR`或临界区保护,但通常不建议,因为中断频繁会干扰监控逻辑。 - **时间戳溢出**:FreeRTOS的TickCount是32位无符号,溢出周期约49天(1ms tick),差值计算采用无符号减法,自动处理溢出,无需额外处理。 - **恢复策略**:检测到卡死后,可采取: - 系统复位(最安全) - 删除卡死任务并重建(需确保资源释放) - 记录日志后进入安全模式 - **与硬件看门狗协同**:软件看门狗负责定位,硬件看门狗负责最终兜底。监控任务每次正常扫描后喂硬件看门狗,若监控任务本身卡死,硬件看门狗将复位系统。 ## 五、扩展:优先级反转与监控盲区 高优先级任务卡死可能由低优先级任务持有资源导致(优先级反转)。监控任务无法直接检测,但可通过心跳表发现高优先级任务长时间未更新,从而触发恢复。另外,若监控任务被更高优先级任务抢占,可能导致监控延迟,因此监控任务优先级应低于所有被监控任务,但高于空闲任务。 ## 六、总结 基于心跳表的软件看门狗是RTOS任务监控的有效手段,能快速定位卡死任务,配合硬件看门狗形成双重保障。实际工程中需根据任务实时性要求调整超时阈值,并充分测试阻塞场景,避免误报。该机制已在多个工业控制项目中验证,显著提升了系统可靠性。