4、软件看门狗实现:基于QNX定时器的软件看门狗设计、心跳机制、超时回调

聊完了硬件看门狗,咱们来看看软件看门狗。说实话,在我早期的职业生涯里,总觉得软件看门狗有点「软绵绵」的,不如硬件来得实在。但后来在几个大型通信设备项目中,我彻底改变了这个看法。

软件看门狗,说白了就是利用操作系统提供的定时器机制,在应用层模拟硬件看门狗的行为。它不需要额外的硬件引脚,成本低,而且非常灵活。你想想看,硬件看门狗只能复位整个系统,但软件看门狗可以做到更精细的控制——比如只重启某个任务,或者执行特定的恢复流程。

4.1 基于QNX定时器的软件看门狗设计

QNX提供了非常强大的定时器API。我个人习惯用 timer_create() 配合 SIGEV_SIGNALSIGEV_PULSE 来实现看门狗定时器。这里我推荐使用脉冲(pulse)方式,因为它更轻量,而且不会干扰信号处理。

来看一个我常用的设计框架:

#include <sys/neutrino.h>
#include <time.h>
#include <errno.h>

typedef struct {
    timer_t timer_id;
    uint64_t timeout_ms;
    void (*callback)(void *arg);
    void *callback_arg;
    int channel_id;
    int pulse_code;
} watchdog_t;

// 创建软件看门狗
watchdog_t* watchdog_create(uint64_t timeout_ms, 
                            void (*cb)(void *), 
                            void *arg) {
    watchdog_t *wd = calloc(1, sizeof(watchdog_t));
    if (!wd) return NULL;
    
    wd->timeout_ms = timeout_ms;
    wd->callback = cb;
    wd->callback_arg = arg;
    
    // 创建通道用于接收脉冲
    wd->channel_id = ChannelCreate(0);
    if (wd->channel_id == -1) {
        free(wd);
        return NULL;
    }
    
    // 配置定时器事件为脉冲
    struct sigevent event;
    SIGEV_PULSE_INIT(&event, wd->channel_id, 
                     SIGEV_PULSE_PRIO_INHERIT, 
                     wd->pulse_code, 0);
    
    if (timer_create(CLOCK_MONOTONIC, &event, 
                     &wd->timer_id) == -1) {
        ChannelDestroy(wd->channel_id);
        free(wd);
        return NULL;
    }
    
    return wd;
}

嗯,这里要注意:CLOCK_MONOTONICCLOCK_REALTIME 更可靠。为什么?因为系统时间调整不会影响单调时钟,看门狗不会因为管理员改了系统时间而误触发。

4.2 心跳机制:让系统保持「活着」的状态

心跳机制是软件看门狗的核心。每个受监控的任务需要定期「喂狗」,告诉看门狗:「我还活着,一切正常」。

我曾经在一个多任务系统中遇到过这样的问题:某个任务虽然还在运行,但已经卡在了一个死循环里,它仍然能发送心跳。这让我意识到,单纯的心跳是不够的,我们需要「带健康检查的心跳」。

这里分享一个我改进后的心跳机制:

typedef struct {
    uint64_t last_heartbeat;
    uint64_t max_interval_ms;
    uint32_t task_state;      // 任务状态标志
    uint32_t progress_counter; // 进度计数器
    int task_priority;        // 当前优先级
} heartbeat_t;

// 带状态的心跳上报
int watchdog_heartbeat(watchdog_t *wd, uint32_t state) {
    heartbeat_t hb;
    
    hb.last_heartbeat = ClockCycles();
    hb.task_state = state;
    hb.progress_counter++;
    hb.task_priority = get_my_priority();
    
    // 重置定时器
    struct itimerspec spec;
    spec.it_value.tv_sec = wd->timeout_ms / 1000;
    spec.it_value.tv_nsec = (wd->timeout_ms % 1000) * 1000000;
    spec.it_interval.tv_sec = 0;
    spec.it_interval.tv_nsec = 0;
    
    return timer_settime(wd->timer_id, 0, &spec, NULL);
}

我的经验之谈:心跳间隔建议设置为超时时间的 1/3 到 1/2。比如超时时间设为 3 秒,心跳就每隔 1 秒发送一次。这样即使漏掉一次心跳,还有两次机会补救。

4.3 超时回调:当看门狗「咬人」的时候

超时回调是看门狗的最后一道防线。当定时器到期,说明某个任务没有及时喂狗,这时候需要执行恢复动作。

我曾经在一个项目中,超时回调里直接调用了 exit(),结果导致资源没有释放,文件描述符泄漏。后来我改成了分级恢复策略:

typedef enum {
    RECOVERY_LOG_ONLY,      // 仅记录日志
    RECOVERY_RESTART_TASK,  // 重启任务
    RECOVERY_REBOOT_SYSTEM, // 重启系统
    RECOVERY_PANIC          // 紧急停止
} recovery_level_t;

// 超时回调函数
void watchdog_timeout_callback(void *arg) {
    watchdog_t *wd = (watchdog_t *)arg;
    
    // 第一步:记录现场信息
    log_critical("Watchdog timeout! Task: %s, State: 0x%x", 
                 wd->task_name, wd->last_state);
    
    // 第二步:尝试温和恢复
    if (wd->retry_count < MAX_RETRY) {
        wd->retry_count++;
        log_warning("Attempting recovery #%d", wd->retry_count);
        
        // 发送信号给问题任务
        pthread_kill(wd->task_thread, SIGUSR1);
        
        // 重新启动看门狗
        watchdog_start(wd);
        return;
    }
    
    // 第三步:升级到强硬恢复
    log_error("Max retries reached, forcing restart");
    
    // 杀掉问题任务
    pthread_cancel(wd->task_thread);
    
    // 清理资源
    watchdog_cleanup(wd);
    
    // 重新创建任务
    create_monitored_task(wd->task_config);
}

避坑指南:我曾经在超时回调里做过耗时操作,比如写日志到磁盘。结果看门狗线程被阻塞,导致系统无法及时响应。记住:超时回调要快!快!快!如果需要做复杂操作,用消息队列派发给其他线程处理。

4.4 实战中的设计要点

基于我多年的QNX开发经验,总结几个关键点:

设计要素 推荐做法 不推荐做法
超时时间 根据任务最坏执行时间 × 2 随意设置固定值
心跳频率 超时时间的 1/3 过于频繁(浪费CPU)
回调处理 分级恢复策略 直接复位系统
日志记录 异步写入环形缓冲区 同步写磁盘

另外,我建议在调试阶段把看门狗的超时时间设得长一些,比如 30 秒。等系统稳定后再逐步缩短到目标值。这样能避免在开发过程中被自己的看门狗「咬」到。

核心思想:软件看门狗不是用来惩罚任务的,而是给系统一个自我修复的机会。设计时要考虑「优雅降级」,而不是「粗暴重启」。

最后说一句,软件看门狗和硬件看门狗不是二选一的关系。在实际项目中,我通常两者都用:硬件看门狗作为最后一道防线,软件看门狗做精细化的任务监控。这样既保证了可靠性,又提供了灵活性。