3、QNX看门狗服务(wdog):wdog服务介绍、启动与配置、基本API函数

3.1 什么是wdog服务?

看门狗,说白了就是一个「系统保命符」。

在嵌入式系统里,最怕什么?最怕程序跑飞了、死锁了、或者某个任务卡住了。这时候如果没有看门狗,系统就彻底「凉了」。QNX的wdog服务,就是专门干这个的——它监控系统是否还活着,如果发现不对劲,就帮你重启或者执行预设的恢复动作。

我个人习惯把wdog比作「系统的心跳监护仪」。它每隔一段时间就要「听」一下系统的心跳。如果听不到了,它就认为系统出事了。

核心概念: wdog服务是一个用户态的守护进程,它通过硬件看门狗定时器来实现系统监控。你可以配置超时时间、触发动作,甚至自定义健康检查逻辑。

3.2 启动与配置wdog

启动wdog其实不复杂。我刚开始接触QNX时,以为这玩意儿要写一堆底层代码。后来发现,QNX已经帮你封装好了。

启动命令很简单:

# 启动wdog服务
wdog -d /dev/watchdog -t 10000 -a reboot

这里解释一下参数:

  • -d /dev/watchdog:指定硬件看门狗设备节点。不同板子可能不一样,我遇到过有些板子是/dev/watchdog0
  • -t 10000:超时时间,单位毫秒。这里设了10秒。
  • -a reboot:超时后的动作,这里是重启。也可以设成haltpanic

嗯,这里要注意:超时时间别设太短。我曾经在一个项目里设了2秒,结果系统稍微忙一点就被重启了。调试了一整天才发现是看门狗太敏感了。

如果你想让wdog开机自启,可以把它加到启动脚本里:

# 在/etc/system.launch中添加
wdog -d /dev/watchdog -t 15000 -a reboot
我的经验: 建议在开发阶段把超时时间设长一点(比如30秒),等系统稳定了再调短。否则你调试时频繁重启,心态会崩的。

3.3 基本API函数

QNX提供了几个核心API来操作看门狗。说实话,常用的就那么几个,我列出来给你看:

函数名 作用 头文件
wdog_open() 打开看门狗设备 <sys/wdog.h>
wdog_close() 关闭看门狗设备 <sys/wdog.h>
wdog_kick() 喂狗(重置定时器) <sys/wdog.h>
wdog_set_timeout() 设置超时时间 <sys/wdog.h>
wdog_get_timeout() 获取当前超时时间 <sys/wdog.h>

来看一个完整的示例代码:

#include <stdio.h>
#include <stdlib.h>
#include <sys/wdog.h>

int main(void) {
    int wd;
    int ret;

    // 打开看门狗
    wd = wdog_open("/dev/watchdog", 0);
    if (wd == -1) {
        perror("wdog_open failed");
        return EXIT_FAILURE;
    }

    printf("看门狗已打开,开始喂狗...\n");

    // 主循环:每隔5秒喂一次狗
    while (1) {
        ret = wdog_kick(wd);
        if (ret == -1) {
            perror("wdog_kick failed");
            break;
        }
        printf("喂狗成功!\n");
        sleep(5);
    }

    // 关闭看门狗
    wdog_close(wd);
    return EXIT_SUCCESS;
}

这段代码的逻辑很直白:打开看门狗,然后每隔5秒喂一次。如果程序卡住了,超过10秒没喂狗,系统就会重启。

警告: 千万不要在喂狗线程里做耗时操作!我曾经见过有人把喂狗和日志写入放在同一个线程里,结果日志文件满了,线程卡住,看门狗也喂不了,系统反复重启。喂狗线程应该独立、轻量、高优先级。

3.4 多任务环境下的喂狗策略

单任务喂狗很简单。但实际项目中,往往是多任务系统。你想想看,如果只有一个任务在喂狗,其他任务都死了,看门狗照样被喂得饱饱的——这就有问题了。

我建议的做法是:

  1. 主任务喂狗:一个高优先级任务专门负责喂狗。
  2. 健康检查:喂狗前,检查其他关键任务是否还活着。
  3. 心跳机制:每个关键任务定期更新一个共享变量,喂狗任务检查这些变量。

举个例子:

// 共享的心跳变量
volatile int task1_heartbeat = 0;
volatile int task2_heartbeat = 0;

// 喂狗线程
void* watchdog_thread(void* arg) {
    while (1) {
        // 检查所有任务的心跳
        if (task1_heartbeat == 0 || task2_heartbeat == 0) {
            printf("检测到任务异常,不喂狗!\n");
            // 让看门狗超时触发重启
            sleep(20);
        } else {
            wdog_kick(wd);
            // 重置心跳标志
            task1_heartbeat = 0;
            task2_heartbeat = 0;
        }
        sleep(5);
    }
    return NULL;
}

这样做的好处是:任何一个任务挂了,看门狗都不会被喂,系统就会自动重启。我在一个车载项目中就用这个方案,效果很好。

避坑指南: 我曾经犯过一个错误——在喂狗前没有重置心跳标志,结果某个任务只更新了一次心跳,后面就死了,但喂狗线程一直读到非零值,系统一直不重启。所以记得每次喂狗后清零心跳标志。

3.5 调试技巧

调试看门狗时,最怕的就是系统反复重启,日志都来不及看。我的做法是:

  • 开发阶段先禁用看门狗,或者把超时时间设得很长。
  • wdog_get_timeout()确认当前配置是否正确。
  • 在喂狗前后加日志,记录时间戳。

还有一个实用命令:

# 查看当前看门狗状态
wdog -i /dev/watchdog

这个命令会显示超时时间、剩余时间、触发动作等信息。调试时非常有用。

好了,关于wdog服务的基本内容就这些。下一章我们会讲如何设计一个更健壮的健康监测框架,包括多级看门狗和故障恢复策略。到时候我会分享一个我在工业项目中用过的实战方案。