2. VxWorks看门狗API概览:wdCreate、wdDelete、wdStart、wdCancel函数介绍、看门狗ID管理

好,咱们进入正题。看门狗这玩意儿,说白了就是一个定时炸弹——你必须在它爆炸之前,告诉系统“我还活着”。VxWorks提供了一套非常简洁的API来操作它。我最早接触这些函数时,觉得太简单了,不就是创建、启动、取消、删除嘛。但实际用起来,坑还真不少。

今天我就把这四个核心函数掰开揉碎了讲,顺便聊聊看门狗ID的管理。你想想看,一个系统里可能同时跑着十几个看门狗,ID怎么分配、怎么回收,这都是学问。

2.1 wdCreate —— 创建看门狗定时器

先看创建函数。原型长这样:

WDOG_ID wdCreate(void)

嗯,参数都没有,直接返回一个看门狗ID。这个ID的类型是 WDOG_ID,本质上是一个指针,指向内核内部维护的一个看门狗控制块。

我个人习惯在系统初始化阶段就把所有看门狗创建好。为什么?因为创建操作会分配内存,如果在中断服务程序里调用 wdCreate(),一旦内存不足,系统可能直接挂掉。

重要提示: wdCreate() 可能返回 NULL。永远不要假设创建成功。我在项目中遇到过,某次内存泄漏导致看门狗创建失败,结果系统跑飞了都没人知道。

创建成功后,这个看门狗处于“空闲”状态。它不会自己开始计时,你得手动启动它。

2.2 wdStart —— 启动看门狗定时器

启动函数是重头戏:

STATUS wdStart
    (
    WDOG_ID wdogId,     /* 看门狗ID */
    int     delay,      /* 延迟时间,单位是tick */
    FUNCPTR routine,    /* 超时回调函数 */
    int     parameter   /* 传给回调函数的参数 */
    )

这里有几个关键点,我挨个说。

第一个参数:看门狗ID。 就是刚才 wdCreate() 返回的那个。别传错了,传个野指针进去,系统会直接 panic。

第二个参数:延迟时间。 单位是 tick,不是毫秒!这个坑我踩过。VxWorks的 tick 周期默认是 16.67ms(60Hz),但你可以通过 sysClkRateGet() 查当前系统的 tick 频率。我曾经在移植代码时,把另一个 RTOS 的毫秒参数直接搬过来,结果看门狗超时时间快了 60 倍,系统疯狂复位。

第三个参数:回调函数。 超时后执行的函数。注意,这个函数是在系统时钟中断的上下文中执行的!所以回调函数里绝对不能做阻塞操作,比如 taskDelay()semTake() 这些想都别想。我建议回调函数里只做最轻量级的事情,比如设置一个标志位,或者直接调用 reboot()

第四个参数:传给回调函数的参数。 这是一个 32 位的整数。你可以传一个任务的 ID,或者一个结构体的指针(但要确保指针有效)。

小技巧: 如果你需要多个看门狗共用一个回调函数,可以通过这个参数区分是哪个看门狗超时了。比如传一个枚举值进去。

2.3 wdCancel —— 取消看门狗定时器

有时候任务提前完成了,不需要看门狗继续盯着,那就取消它:

STATUS wdCancel
    (
    WDOG_ID wdogId
    )

这个函数很简单,传入看门狗ID,返回 OK 或 ERROR。但有一个细节:取消一个已经超时的看门狗,会返回 ERROR。 为什么?因为看门狗一旦超时,它的控制块就被内核回收了,你再去取消,内核找不到这个定时器了。

我建议你在取消之前,先检查一下看门狗的状态。不过 VxWorks 没有提供直接查询状态的 API,所以我的做法是:在回调函数里设置一个全局标志位,主任务在取消前先检查这个标志。

2.4 wdDelete —— 删除看门狗定时器

当看门狗不再需要时,记得删除它,释放资源:

STATUS wdDelete
    (
    WDOG_ID wdogId
    )

这里有个大坑:绝对不能删除一个正在运行的看门狗! 必须先 wdCancel(),再 wdDelete()。否则,内核在时钟中断里还会去访问这个已经被释放的控制块,结果就是系统崩溃。

警告: 我曾经在项目中看到同事直接调用 wdDelete() 而没有先取消,结果系统每隔几分钟就莫名其妙地挂一次。查了两天才找到原因。记住:先取消,再删除,顺序不能乱。

2.5 看门狗ID管理

好了,四个函数讲完了。但还有一个更底层的问题:看门狗ID怎么管?

VxWorks 内核内部维护了一个看门狗控制块池。每次 wdCreate() 就从池里取一个,wdDelete() 就还回去。这个池的大小是有限的,由内核配置决定。

我见过一些新手,每次需要看门狗时就 wdCreate(),用完就 wdDelete()。这样频繁分配释放,容易产生内存碎片。我的建议是:

  • 静态分配: 在系统初始化时,把所有可能用到的看门狗一次性创建好。
  • ID映射: 用一个数组或哈希表,把看门狗ID映射到具体的功能模块。比如看门狗ID 0 给主控任务,ID 1 给通信任务。
  • 避免ID泄漏: 每次 wdCreate() 后,把ID保存到全局变量或任务控制块里。删除后,把ID置为 NULL。

这里给一个简单的ID管理示例:

/* 看门狗ID表 */
typedef struct {
    WDOG_ID wdogId;
    BOOL    inUse;
    char    moduleName[16];
} WDOG_TABLE;

WDOG_TABLE wdogTable[10];  /* 最多10个看门狗 */

/* 初始化 */
void wdogTableInit(void) {
    int i;
    for (i = 0; i < 10; i++) {
        wdogTable[i].wdogId = wdCreate();
        wdogTable[i].inUse = FALSE;
        if (wdogTable[i].wdogId == NULL) {
            /* 创建失败,处理错误 */
        }
    }
}

/* 分配看门狗 */
WDOG_ID wdogAlloc(const char *moduleName) {
    int i;
    for (i = 0; i < 10; i++) {
        if (!wdogTable[i].inUse) {
            wdogTable[i].inUse = TRUE;
            strncpy(wdogTable[i].moduleName, moduleName, 15);
            return wdogTable[i].wdogId;
        }
    }
    return NULL;  /* 没有可用看门狗 */
}

/* 释放看门狗 */
void wdogFree(WDOG_ID wdogId) {
    int i;
    for (i = 0; i < 10; i++) {
        if (wdogTable[i].wdogId == wdogId) {
            wdCancel(wdogId);   /* 先取消 */
            wdogTable[i].inUse = FALSE;
            /* 注意:不删除,只是标记为可用 */
            break;
        }
    }
}

你看,这样管理,既避免了频繁创建删除的开销,又能快速定位是哪个模块在看门狗超时了。调试的时候,打印一下 moduleName,问题出在哪一目了然。

最后总结一下:看门狗API虽然只有四个函数,但用好了不容易。记住三点:创建要检查返回值、启动要算对tick、删除前必须先取消。 做到这三点,看门狗这块基本就不会出大问题。