FreeRTOS 软件看门狗与任务死锁监控机制设计实战

封面信息图

在嵌入式多任务实时操作系统(RTOS)中,硬件看门狗(Hardware Watchdog)虽然能够在整机硬件死锁或时钟停振时提供最后的“断电复位保护”,但对于纯软件逻辑层面的任务局部死锁(Task-Level Deadlock),硬件看门狗往往形同虚设。

例如:

  • 任务 A(负责 4G 网络数据上传)由于在获取互斥量时发生死锁,或者卡死在一个没有设置超时的外部阻塞 API 中;
  • 任务 B(负责本地低优先级的 LED 闪烁与硬件看门狗喂狗)依然在欢快地以每 500ms 一次的节奏正常翻转 WDI 引脚;
  • 此时硬件看门狗被持续喂饱,根本不会触发复位,而系统的核心通信业务却早已彻底瘫痪!

构建一套基于 FreeRTOS 软件看门狗(Software Watchdog Daemon)全任务心跳打卡(Heartbeat Check-In Table)机制的工业级监控体系,能够以微秒级的开销在单任务发生卡死时实现精准定位、现场快照捕获与系统自愈。

软件看门狗与硬件看门狗的二级联动拓扑

软件看门狗扮演着“各业务任务与底层硬件看门狗之间的监考官”角色:

两级看门狗协同防御架构:

[ 业务任务 1: Task_Sensor (周期 50ms) ] ──► 定期向【监控总表】刷新自身心跳时间戳 (Check-in)
[ 业务任务 2: Task_Network (周期 100ms)] ──► 定期向【监控总表】刷新自身心跳时间戳 (Check-in)
[ 业务任务 3: Task_Motor (周期 10ms)  ] ──► 定期向【监控总表】刷新自身心跳时间戳 (Check-in)
       │
       ▼ (全局共享打卡状态表)
+=========================================================================+
| 【软件看门狗守护任务 (Software Watchdog Daemon, 优先级最高)】           |
|   - 每 500ms 周期性唤醒并严格审查监控总表                               |
|   - 检查每一个被注册的任务: (当前系统时间 - 任务最后打卡时间) > 最大容忍超时?|
+=========================================================================+
       │
       ├─► 场景 A (所有任务全部健康打卡):
       │     └──► 软件看门狗向【外部硬件看门狗引脚】发送喂狗脉冲!(整机安全)
       │
       └─► 场景 B (发现 Task_Network 已超过 5 秒未打卡!):
             ├──► 1. 立即锁定现场,向 Flash 写入死锁诊断日志 (记录死锁任务 ID 与调用栈);
             └──► 2. 软件看门狗【故意停止向硬件看门狗喂狗】!
                  3. 坐等外部硬件看门狗在 1.6 秒后执行整机硬核物理复位!

工业级软件看门狗 C 语言核心实现

#include <stdint.h>
#include <stdbool.h>
#include <stdio.h>
#include "FreeRTOS.h"
#include "task.h"

#define MAX_WATCHDOG_TASKS 8

typedef struct {
    TaskHandle_t task_handle;
    const char *task_name;
    TickType_t max_allowed_delay_ticks; // 该任务允许的最大未打卡超时阈值
    TickType_t last_checkin_tick;       // 任务最后一次打卡的时间戳
    bool is_registered;
} WatchdogEntry_t;

typedef struct {
    WatchdogEntry_t entries[MAX_WATCHDOG_TASKS];
    uint32_t active_count;
} SoftwareWatchdogManager_t;

static SoftwareWatchdogManager_t g_sw_wdt;

// 1. 注册一个待监控的任务
int register_task_to_watchdog(TaskHandle_t handle, const char *name, uint32_t max_delay_ms) {
    if (g_sw_wdt.active_count >= MAX_WATCHDOG_TASKS) {
        return -1; // 表满
    }

    int slot = g_sw_wdt.active_count;
    g_sw_wdt.entries[slot].task_handle = handle;
    g_sw_wdt.entries[slot].task_name = name;
    g_sw_wdt.entries[slot].max_allowed_delay_ticks = pdMS_TO_TICKS(max_delay_ms);
    g_sw_wdt.entries[slot].last_checkin_tick = xTaskGetTickCount();
    g_sw_wdt.entries[slot].is_registered = true;

    g_sw_wdt.active_count++;
    pr_info("[SW_WDT] Registered task [%s] with timeout %u ms\n", name, max_delay_ms);
    return slot;
}

// 2. 业务任务在正常循环体内调用的打卡函数 (耗时仅需数纳秒!)
void watchdog_task_checkin(int wdt_slot_id) {
    if (wdt_slot_id >= 0 && wdt_slot_id < MAX_WATCHDOG_TASKS) {
        // 原子更新打卡时间戳
        g_sw_wdt.entries[wdt_slot_id].last_checkin_tick = xTaskGetTickCount();
    }
}

// 3. 软件看门狗审查守护任务 (赋予系统最高优先级 configMAX_PRIORITIES - 1)
void SoftwareWatchdog_Supervisor_Task(void *pvParameters) {
    while (1) {
        vTaskDelay(pdMS_TO_TICKS(500)); // 每 500ms 审查一次全表

        TickType_t current_tick = xTaskGetTickCount();
        bool all_tasks_alive = true;

        for (uint32_t i = 0; i < g_sw_wdt.active_count; i++) {
            WatchdogEntry_t *entry = &g_sw_wdt.entries[i];
            if (!entry->is_registered) continue;

            // 计算该任务距离上次打卡已经过去了多长时间
            TickType_t elapsed_ticks = current_tick - entry->last_checkin_tick;

            if (elapsed_ticks > entry->max_allowed_delay_ticks) {
                // 核心故障捕获: 发现某一个业务任务发生死锁卡死!
                pr_err("==============================================================\n");
                pr_err("[SOFTWARE WATCHDOG ALARM] Task DEADLOCK Detected!\n");
                pr_err("Task Name       : [%s]\n", entry->task_name);
                pr_err("Elapsed Inactive: %u ms (Allowed Max: %u ms)\n", 
                       pdTICKS_TO_MS(elapsed_ticks), pdTICKS_TO_MS(entry->max_allowed_delay_ticks));
                pr_err("Action          : Ceasing hardware kick to force full system reboot!\n");
                pr_err("==============================================================\n");

                all_tasks_alive = false;
                break; // 只要有一个任务死锁,整机判定失败!
            }
        }

        if (all_tasks_alive) {
            // 所有任务均在健康生命周期内,向外部物理硬件看门狗执行喂狗!
            kick_hardware_watchdog_pin();
        } else {
            // 核心决绝动作: 故意停止喂狗!
            // 坐等 1.6 秒后硬件看门狗执行冷复位,并让出 CPU 记录 Flash 崩溃现场
            record_deadlock_crash_to_flash();
            while (1) {
                vTaskDelay(pdMS_TO_TICKS(1000));
            }
        }
    }
}

业务任务集成示例

在编写各个业务任务时,打卡操作必须嵌入在每次完整循环处理的末尾:

void NetworkTask(void *pvParameters) {
    // 注册自身,允许最大未打卡超时为 5000ms (5秒)
    int my_wdt_slot = register_task_to_watchdog(xTaskGetCurrentTaskHandle(), "NetworkTask", 5000);

    while (1) {
        // 执行网络数据包轮询与上传 (带超时的安全阻塞)
        process_mqtt_publish_pipeline();

        // 核心打卡: 证明本轮业务成功跑完!
        watchdog_task_checkin(my_wdt_slot);

        vTaskDelay(pdMS_TO_TICKS(100));
    }
}

将软件层面的任务健康状态表与底层硬件看门狗紧密缝合,系统才能彻底消除“局部死锁而整体不复位”的隐形故障,保障多任务固件的绝对健壮性。

Logo

openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构

更多推荐