嵌入式Linux C应用编程——看门狗

看门狗应用编程

1. 看门狗基础概念

1.1 什么是看门狗

  • 看门狗(Watchdog) 本质上是一个在一定时间内可以被复位的计数器,也叫做看门狗定时器(Watchdog Timer)
  • 当看门狗启动后,计数器开始自动计数,经过一定时间如果没有被复位,计数器溢出就会对 CPU 产生一个复位信号使系统重启(俗称 "被狗咬"
  • 系统正常运行时,需要在看门狗允许的时间间隔内对看门狗计数器清零(俗称 "喂狗"),不让复位信号产生
  • 如果程序跑飞,没有及时"喂狗",系统就会"被咬"复位

1.2 看门狗的工作模式

  • 有些看门狗也可以只产生中断信号而不会使系统复位
  • 在产品化的嵌入式系统中,为了使系统在异常情况下能自动复位,一般都需要引入看门狗

1.3 I.MX6U 的看门狗硬件

  • I.MX6UL/I.MX6ULL SoC 集成了两个看门狗定时器(WDOG)
    • WDOG1:普通看门狗,支持产生中断信号以及复位 CPU
    • WDOG2:用于安全目的

2. 看门狗设备节点

2.1 设备节点

  • Linux 系统中所注册的看门狗外设,都会在 /dev/ 目录下生成对应的设备节点
  • 设备节点名称通常为 watchdogX(X 表示数字编号 0、1、2、3...),例如:
    • /dev/watchdog0
    • /dev/watchdog1
  • watchdog0 就是 I.MX6U 的 WDOG1 所对应的设备节点

2.2 默认看门狗设备

  • /dev/watchdog 代表系统默认的看门狗设备
  • 通常 /dev/watchdog 就等于 /dev/watchdog0,代表同一个硬件外设

img

3. 应用层控制看门狗 — ioctl 接口

3.1 头文件

#include <linux/watchdog.h>

3.2 常用 ioctl 指令汇总

ioctl 指令 说明
WDIOC_GETSUPPORT 获取看门狗支持哪些功能
WDIOC_SETOPTIONS 用于开启或关闭看门狗
WDIOC_KEEPALIVE 喂狗操作
WDIOC_SETTIMEOUT 设置看门狗超时时间
WDIOC_GETTIMEOUT 获取看门狗超时时间

3.3 完整的 ioctl 指令宏定义

#define WDIOC_GETSUPPORT    _IOR(WATCHDOG_IOCTL_BASE, 0, struct watchdog_info)
#define WDIOC_GETSTATUS     _IOR(WATCHDOG_IOCTL_BASE, 1, int)
#define WDIOC_GETBOOTSTATUS _IOR(WATCHDOG_IOCTL_BASE, 2, int)
#define WDIOC_GETTEMP       _IOR(WATCHDOG_IOCTL_BASE, 3, int)
#define WDIOC_SETOPTIONS    _IOR(WATCHDOG_IOCTL_BASE, 4, int)
#define WDIOC_KEEPALIVE     _IOR(WATCHDOG_IOCTL_BASE, 5, int)
#define WDIOC_SETTIMEOUT    _IOWR(WATCHDOG_IOCTL_BASE, 6, int)
#define WDIOC_GETTIMEOUT    _IOR(WATCHDOG_IOCTL_BASE, 7, int)
#define WDIOC_SETPRETIMEOUT _IOWR(WATCHDOG_IOCTL_BASE, 8, int)
#define WDIOC_GETPRETIMEOUT _IOR(WATCHDOG_IOCTL_BASE, 9, int)
#define WDIOC_GETTIMELEFT   _IOR(WATCHDOG_IOCTL_BASE, 10, int)

4. 操作步骤详解

4.1 打开看门狗设备

int fd;

fd = open("/dev/watchdog", O_RDWR);
if (0 > fd)
    fprintf(stderr, "open error: %s: %s\n", "/dev/watchdog", strerror(errno));

4.2 获取设备支持功能 — WDIOC_GETSUPPORT

ioctl(int fd, WDIOC_GETSUPPORT, struct watchdog_info *info);

struct watchdog_info 结构体

struct watchdog_info {
    __u32 options;           /* 设备支持的功能/选项 */
    __u32 firmware_version;  /* 设备的固件版本号 */
    __u8  identity[32];      /* 描述性字符串 */
};

options 字段取值

宏定义 说明
WDIOF_OVERHEAT 0x0001 CPU 过热复位置位
WDIOF_FANFAULT 0x0002 风扇故障
WDIOF_EXTERN1 0x0004 外部继电器 1
WDIOF_EXTERN2 0x0008 外部继电器 2
WDIOF_POWERUNDER 0x0010 电源欠压
WDIOF_CARDRESET 0x0020 设备之前复位过 CPU
WDIOF_POWEROVER 0x0040 电源过压
WDIOF_SETTIMEOUT 0x0080 支持设置超时时间(秒)
WDIOF_MAGICCLOSE 0x0100 支持 magic close 字符
WDIOF_PRETIMEOUT 0x0200 支持预超时(秒)
WDIOF_ALARMONLY 0x0400 仅触发告警,不复位
WDIOF_KEEPALIVEPING 0x8000 支持喂狗操作

常见值

  • WDIOF_SETTIMEOUT — 设备支持设置超时时间
  • WDIOF_KEEPALIVEPING — 设备支持"喂狗"操作

使用示例

struct watchdog_info info;

if (0 > ioctl(fd, WDIOC_GETSUPPORT, &info)) {
    fprintf(stderr, "ioctl error: WDIOC_GETSUPPORT: %s\n", strerror(errno));
    return -1;
}

printf("identity: %s\n", info.identity);
printf("version: %u\n", info.firmware_version);

if (0 == (WDIOF_KEEPALIVEPING & info.options))
    printf("设备不支持喂狗操作\n");
if (0 == (WDIOF_SETTIMEOUT & info.options))
    printf("设备不支持设置超时时间\n");

4.3 获取/设置超时时间 — WDIOC_GETTIMEOUT / WDIOC_SETTIMEOUT

获取超时时间

ioctl(int fd, WDIOC_GETTIMEOUT, int *timeout);

设置超时时间

ioctl(int fd, WDIOC_SETTIMEOUT, int *timeout);
  • 超时时间以为单位
  • 设置超时时间时,不可超过其最大值,否则 ioctl() 调用将会失败

使用示例

int timeout;

/* 获取超时时间 */
if (0 > ioctl(fd, WDIOC_GETTIMEOUT, &timeout)) {
    fprintf(stderr, "ioctl error: WDIOC_GETTIMEOUT: %s\n", strerror(errno));
    return -1;
}
printf("current timeout: %ds\n", timeout);

/* 设置超时时间 */
timeout = 10;  // 10 秒钟
if (0 > ioctl(fd, WDIOC_SETTIMEOUT, &timeout)) {
    fprintf(stderr, "ioctl error: WDIOC_SETTIMEOUT: %s\n", strerror(errno));
    return -1;
}

4.4 启用/禁用看门狗 — WDIOC_SETOPTIONS

ioctl(int fd, WDIOC_SETOPTIONS, int *option);

option 取值

宏定义 说明
WDIOS_DISABLECARD 0x0001 禁用看门狗(停止计时)
WDIOS_ENABLECARD 0x0002 启用看门狗(开启计时)

使用示例

int option = WDIOS_ENABLECARD;   // 启用
// int option = WDIOS_DISABLECARD;  // 禁用

if (0 > ioctl(fd, WDIOC_SETOPTIONS, &option)) {
    fprintf(stderr, "ioctl error: WDIOC_SETOPTIONS: %s\n", strerror(errno));
    return -1;
}

注意:这是 Linux 看门狗驱动的关键特性——当调用 open() 打开看门狗设备时,看门狗计时器就会自动启动。所以正确的流程是:打开设备后,先使用 WDIOC_SETOPTIONS 设置选项为 WDIOS_DISABLECARD 来禁用看门狗,等所有设置完成之后,再使用 WDIOS_ENABLECARD 重新启用看门狗。

4.5 喂狗 — WDIOC_KEEPALIVE

ioctl(int fd, WDIOC_KEEPALIVE, NULL);

使用示例

if (0 > ioctl(fd, WDIOC_KEEPALIVE, NULL)) {
    fprintf(stderr, "ioctl error: WDIOC_KEEPALIVE: %s\n", strerror(errno));
}

5. 实战示例程序

5.1 完整代码

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <errno.h>
#include <string.h>
#include <linux/watchdog.h>

#define  WDOG_DEV   "/dev/watchdog"

int main(int argc, char *argv[])
{
    struct watchdog_info info;
    int timeout;
    int time;
    int fd;
    int op;

    if (2 != argc) {
        fprintf(stderr, "usage: %s <timeout>\n", argv[0]);
        exit(EXIT_FAILURE);
    }

    /* 打开看门狗 */
    fd = open(WDOG_DEV, O_RDWR);
    if (0 > fd) {
        fprintf(stderr, "open error: %s: %s\n", WDOG_DEV, strerror(errno));
        exit(EXIT_FAILURE);
    }

    /* 打开之后看门狗计时器会开启,先停止它 */
    op = WDIOS_DISABLECARD;
    if (0 > ioctl(fd, WDIOC_SETOPTIONS, &op)) {
        fprintf(stderr, "ioctl error: WDIOC_SETOPTIONS: %s\n", strerror(errno));
        close(fd);
        exit(EXIT_FAILURE);
    }

    timeout = atoi(argv[1]);
    if (1 > timeout)
        timeout = 1;

    /* 设置超时时间 */
    printf("timeout: %ds\n", timeout);
    if (0 > ioctl(fd, WDIOC_SETTIMEOUT, &timeout)) {
        fprintf(stderr, "ioctl error: WDIOC_SETTIMEOUT: %s\n", strerror(errno));
        close(fd);
        exit(EXIT_FAILURE);
    }

    /* 开启看门狗计时器 */
    op = WDIOS_ENABLECARD;
    if (0 > ioctl(fd, WDIOC_SETOPTIONS, &op)) {
        fprintf(stderr, "ioctl error: WDIOC_SETOPTIONS: %s\n", strerror(errno));
        close(fd);
        exit(EXIT_FAILURE);
    }

    /* 喂狗:在超时时间到来前 100ms 喂狗 */
    time = (timeout * 1000 - 100) * 1000;  // 微秒
    for ( ; ; ) {
        usleep(time);
        ioctl(fd, WDIOC_KEEPALIVE, NULL);
    }
}

5.2 程序逻辑说明

  1. 打开看门狗设备 — 获取文件描述符 fd
  2. 先禁用看门狗 — 使用 WDIOC_SETOPTIONS 设置选项为 WDIOS_DISABLECARD 来禁用看门狗(因为打开设备时它会自动启用)
  3. 获取超时时间参数 — 通过命令行参数 argv[1] 传入
  4. 设置超时时间 — 使用 WDIOC_SETTIMEOUT 指令
  5. 启用看门狗 — 使用 WDIOC_SETOPTIONS 设置选项为 WDIOS_ENABLECARD 来启用看门狗
  6. 循环喂狗 — 在超时时间到来前(预留 100ms)调用 WDIOC_KEEPALIVE 喂狗,不断重置计时器

6. 开发板测试

6.1 编译

使用交叉编译工具链编译示例代码:

arm-linux-gnueabihf-gcc -o watchdog_test watchdog_test.c

6.2 常见问题:Device or resource busy

如果执行程序报错 open error: /dev/watchdog: Device or resource busy,说明看门狗默认已被其他功能使用。

解决方法

  1. 打开 /etc/init.d/watchdog.sh 文件
  2. 在文件开头添加 exit 0,将整个文件注释掉
  3. 开发板重新启动

6.3 测试运行

# 启动看门狗,设置超时时间为 2 秒
./watchdog_test 2

6.4 观察现象

  • 程序运行后会不断喂狗,系统正常运行
  • Ctrl + C 终止进程后,停止喂狗
  • 看门狗计时器超时,系统复位重启
  • 内核会打印:watchdog watchdog0: watchdog did not stop!

6.5 测试完毕后恢复

/etc/init.d/watchdog.sh 文件中的 exit 0 去掉,让系统重新使用看门狗。

7. 总结

步骤 操作 ioctl 指令 说明
1 打开设备 open() 打开 /dev/watchdog/dev/watchdog0
2 禁用看门狗 WDIOC_SETOPTIONS + WDIOS_DISABLECARD 打开设备时看门狗已自动启用,先禁用以便配置
3 设置超时 WDIOC_SETTIMEOUT 设置超时时间(秒)
4 启用看门狗 WDIOC_SETOPTIONS + WDIOS_ENABLECARD 配置完成后重新启用
5 喂狗 WDIOC_KEEPALIVE 在超时前重置计数器

核心思路:看门狗的目的是保证系统在异常情况下能自动复位。正常工作时程序周期性喂狗,一旦程序跑飞停止喂狗,看门狗超时就会复位系统,从而恢复系统的正常运行。

posted @ 2026-07-23 15:29  Javenwww  阅读(38)  评论(0)    收藏  举报