9. Watchdog 操作指南

本文档说明在 CV184x 平台上通过 Linux 标准 Watchdog 框架操作看门狗,包括驱动配置、常用 ioctl 及示例代码。

9.1. 操作准备

1. 内核要求

使用 SDK 发布的内核,并确保以下配置已开启(驱动随内核编译,无需 insmod):

CONFIG_WATCHDOG=y
CONFIG_WATCHDOG_CORE=y
CONFIG_DW_WATCHDOG=y

2. 使用方式

在控制台通过命令行操作 /dev/watchdog 设备,或在内核态/用户态程序中通过 open、ioctl、write、close 等接口操作看门狗即可。

9.2. 操作流程

典型使用流程如下:

  1. 打开设备 :打开 /dev/watchdog,打开即启动看门狗,硬件开始计时。

  2. (可选)设置超时 :通过 WDIOC_SETTIMEOUT 设置超时时间(秒);不设置则使用驱动默认值(42 秒)。

  3. 立即喂狗 :打开后应尽快调用 WDIOC_KEEPALIVE 进行一次喂狗,避免在默认/设定超时内未喂狗导致重启。

  4. 周期喂狗 :在业务运行期间,在超时时间到达前定期调用 WDIOC_KEEPALIVE 喂狗;喂狗间隔须小于当前超时时间。

  5. 关闭设备 :不再需要看门狗时,按顺序执行:先 WDIOC_SETOPTIONS 传入 WDIOS_DISABLECARD 禁用看门狗,再 write(fd, "V", 1) 写入魔术字符,最后 close(fd);否则关闭文件描述符后硬件仍会继续计时,超时将触发重启。

需要查询当前超时或剩余时间时,可在上述流程中穿插使用 WDIOC_GETTIMEOUTWDIOC_GETTIMELEFT

9.3. 操作示例

概述

  • Watchdog 采用标准 Linux 看门狗框架,提供硬件看门狗。用户通过打开设备、设置超时时间、定期喂狗(keepalive)或关闭设备即可使用。

  • 当看门狗超时未被喂狗时,系统将重启。

  • 默认状态 :看门狗默认关闭,由用户决定是否开启。

  • 可设定的超时时间 (单位:秒):1、2、5、10、21、42、85。若设定的值不在上述列表,驱动会选用 大于等于 该值的最接近项;例如设定 8 秒时,实际采用 10 秒。若未设定超时,驱动默认使用 42 秒。

以下示例需包含头文件 <linux/watchdog.h>,ioctl 宏(如 WDIOC_SETTIMEOUTWDIOC_KEEPALIVE)由该头文件提供,无需自行定义。

9.3.1. 打开 Watchdog 并立即喂狗

打开 /dev/watchdog 即启动看门狗,硬件开始计时。打开后应尽快进行一次喂狗 ,否则在超时时间内未喂狗会导致系统重启。

/* 需包含头文件:stdio.h, stdlib.h, unistd.h, fcntl.h, string.h, errno.h, sys/ioctl.h, linux/watchdog.h */
int wdt_fd;
int ret;

wdt_fd = open("/dev/watchdog", O_WRONLY);
if (wdt_fd < 0) {
    printf("打开 /dev/watchdog 失败: %s\n", strerror(errno));
    return -1;
}
printf("[步骤 1] 打开 /dev/watchdog 成功, fd=%d\n", wdt_fd);

ret = ioctl(wdt_fd, WDIOC_KEEPALIVE, 0);
if (ret != 0)
    printf("警告: 打开后立即喂狗 ioctl 返回 %d: %s\n", ret, strerror(errno));
else
    printf("      打开后已执行一次喂狗 (WDIOC_KEEPALIVE)\n\n");

9.3.2. 设置超时时间

通过 ioctl WDIOC_SETTIMEOUT 设置超时时间,单位为秒。可设置值为:1、2、5、10、21、42、85;传入其他值时,驱动会取大于等于该值的最接近支持值。

int timeout = 10;   /* 单位:秒,支持 1/2/5/10/21/42/85 */
int ret = ioctl(wdt_fd, WDIOC_SETTIMEOUT, &timeout);
if (ret != 0) {
    printf("[步骤 2] 设置超时失败: %s\n", strerror(errno));
} else {
    printf("[步骤 2] 设置超时 %d 秒成功,驱动实际采用: %d 秒\n\n", 10, timeout);
}

9.3.3. 喂狗(Keepalive)

在超时时间到达前,需定期调用 WDIOC_KEEPALIVE 喂狗,否则看门狗超时将触发系统重启。喂狗间隔应小于当前设定的超时时间。

const int keepalive_loops = 5;   /* 演示喂狗次数,可改为 while(running) 等 */
int i;

printf("[步骤 3] 开始周期性喂狗(共 %d 次,每次间隔 1 秒)\n", keepalive_loops);
for (i = 0; i < keepalive_loops; i++) {
    ioctl(wdt_fd, WDIOC_KEEPALIVE, 0);
    printf("      第 %d 次喂狗\n", i + 1);
    sleep(1);
}
printf("\n");

9.3.4. 获取当前超时时间

通过 WDIOC_GETTIMEOUT 可查询驱动当前采用的超时时间(秒)。

int timeout_sec = 0;
if (ioctl(wdt_fd, WDIOC_GETTIMEOUT, &timeout_sec) == 0) {
    printf("[步骤 4] 当前超时时间: %d 秒\n", timeout_sec);
} else {
    printf("[步骤 4] 获取超时时间失败(可选)\n");
}

9.3.5. 获取剩余时间

若驱动支持,可通过 WDIOC_GETTIMELEFT 查询距离下次超时还剩多少秒,便于调试或监控。

int timeleft_sec = 0;
if (ioctl(wdt_fd, WDIOC_GETTIMELEFT, &timeleft_sec) == 0) {
    printf("[步骤 5] 距离下次超时剩余: %d 秒\n\n", timeleft_sec);
} else {
    printf("[步骤 5] 获取剩余时间不支持或失败(可选)\n\n");
}

9.3.6. 关闭 Watchdog(Magic Close)

本驱动支持 Magic Close :在关闭设备前,必须先 禁用看门狗 ,再向设备写入魔术字符 'V',最后才能 close。否则即使关闭了文件描述符,硬件看门狗仍会继续计时,超时未喂狗将导致系统重启。

正确顺序:WDIOS_DISABLECARDwrite(..., "V", 1)close

int option = WDIOS_DISABLECARD;
int ret;

printf("[步骤 6] 关闭 Watchdog (Magic Close)\n");
ret = ioctl(wdt_fd, WDIOC_SETOPTIONS, &option);
if (ret != 0)
    printf("      警告: WDIOC_SETOPTIONS WDIOS_DISABLECARD 返回 %d: %s\n", ret, strerror(errno));

if (wdt_fd >= 0) {
    if (write(wdt_fd, "V", 1) != 1)
        printf("      警告: 写入魔术字符 'V' 失败: %s\n", strerror(errno));
    else
        printf("      已写入魔术字符 'V'\n");
    close(wdt_fd);
    wdt_fd = -1;
    printf("      已 close 设备\n");
}
printf("\n程序正常结束\n");

9.3.7. 完整用户态程序示例

以下为将上述各步骤串联而成的完整 C 程序,可直接编译运行。需包含头文件 <linux/watchdog.h>,ioctl 宏由该头文件提供。

  1/*
  2 * Watchdog 操作示例(遵循标准 Linux 看门狗框架)
  3 * 需包含头文件 <linux/watchdog.h>,ioctl 宏由该头文件提供。
  4 */
  5#include <stdio.h>
  6#include <stdlib.h>
  7#include <unistd.h>
  8#include <fcntl.h>
  9#include <string.h>
 10#include <errno.h>
 11#include <sys/ioctl.h>
 12#include <linux/watchdog.h>
 13
 14int main(void)
 15{
 16    int wdt_fd;
 17    int timeout;
 18    int timeout_sec;
 19    int timeleft_sec;
 20    int option;
 21    int ret;
 22    int i;
 23    const int keepalive_loops = 5;  /* 演示喂狗次数,可改为 while(running) 等 */
 24
 25    printf("=== Watchdog 操作示例 ===\n\n");
 26
 27    /* 步骤 1:打开 Watchdog 并立即喂狗 */
 28    wdt_fd = open("/dev/watchdog", O_WRONLY);
 29    if (wdt_fd < 0) {
 30        printf("打开 /dev/watchdog 失败: %s\n", strerror(errno));
 31        return -1;
 32    }
 33    printf("[步骤 1] 打开 /dev/watchdog 成功, fd=%d\n", wdt_fd);
 34
 35    ret = ioctl(wdt_fd, WDIOC_KEEPALIVE, 0);
 36    if (ret != 0)
 37        printf("警告: 打开后立即喂狗 ioctl 返回 %d: %s\n", ret, strerror(errno));
 38    else
 39        printf("      打开后已执行一次喂狗 (WDIOC_KEEPALIVE)\n\n");
 40
 41    /* 步骤 2:设置超时时间(单位:秒,支持 1/2/5/10/21/42/85) */
 42    timeout = 10;
 43    ret = ioctl(wdt_fd, WDIOC_SETTIMEOUT, &timeout);
 44    if (ret != 0) {
 45        printf("[步骤 2] 设置超时失败: %s\n", strerror(errno));
 46    } else {
 47        printf("[步骤 2] 设置超时 %d 秒成功,驱动实际采用: %d 秒\n\n", 10, timeout);
 48    }
 49
 50   /* 步骤 3:设置超时后再次喂狗,并开始周期性喂狗 */
 51   ret = ioctl(wdt_fd, WDIOC_KEEPALIVE, 0);
 52   if (ret != 0) {
 53       printf("[步骤 3] 设置超时后立即喂狗失败: %s\n", strerror(errno));
 54   } else {
 55       printf("[步骤 3] 设置超时后已立即喂狗一次\n");
 56   }
 57
 58   printf("[步骤 3] 开始周期性喂狗(共 %d 次,每次间隔 1 秒)\n", keepalive_loops);
 59   for (i = 0; i < keepalive_loops; i++) {
 60       ret = ioctl(wdt_fd, WDIOC_KEEPALIVE, 0);
 61       if (ret != 0) {
 62           printf("      第 %d 次喂狗失败: %s\n", i + 1, strerror(errno));
 63           break;
 64       }
 65       printf("      第 %d 次喂狗成功\n", i + 1);
 66       sleep(1);
 67   }
 68   printf("\n");
 69
 70   /* 步骤 4:获取当前超时时间 */
 71   timeout_sec = 0;
 72   if (ioctl(wdt_fd, WDIOC_GETTIMEOUT, &timeout_sec) == 0) {
 73       printf("[步骤 4] 当前超时时间: %d 秒\n", timeout_sec);
 74   } else {
 75       printf("[步骤 4] 获取超时时间失败(可选)\n");
 76   }
 77
 78   /* 步骤 5:获取剩余时间(可选,部分驱动支持) */
 79   timeleft_sec = 0;
 80   if (ioctl(wdt_fd, WDIOC_GETTIMELEFT, &timeleft_sec) == 0) {
 81       printf("[步骤 5] 距离下次超时剩余: %d 秒\n\n", timeleft_sec);
 82   } else {
 83       printf("[步骤 5] 获取剩余时间不支持或失败(可选)\n\n");
 84   }
 85
 86    /* 步骤 6:关闭 Watchdog(Magic Close)*/
 87    /* 正确顺序: WDIOS_DISABLECARD -> write("V", 1) -> close */
 88    printf("[步骤 6] 关闭 Watchdog (Magic Close)\n");
 89    option = WDIOS_DISABLECARD;
 90    ret = ioctl(wdt_fd, WDIOC_SETOPTIONS, &option);
 91    if (ret != 0)
 92        printf("      警告: WDIOC_SETOPTIONS WDIOS_DISABLECARD 返回 %d: %s\n", ret, strerror(errno));
 93
 94    if (wdt_fd >= 0) {
 95        if (write(wdt_fd, "V", 1) != 1)
 96            printf("      警告: 写入魔术字符 'V' 失败: %s\n", strerror(errno));
 97        else
 98            printf("      已写入魔术字符 'V'\n");
 99        close(wdt_fd);
100        wdt_fd = -1;
101        printf("      已 close 设备\n");
102    }
103    printf("\n程序正常结束\n");
104    return 0;
105}

9.3.8. 命令行快速示例

以下说明如何在命令行下实现与上文「完整用户态程序示例」相同的功能。注意 :打开 /dev/watchdog 后若不定期喂狗,系统会在超时后重启,请仅在测试环境使用。设置超时(WDIOC_SETTIMEOUT)、查询超时/剩余时间(WDIOC_GETTIMEOUT / WDIOC_GETTIMELEFT)需通过 C 程序或等价工具完成;推荐直接编译并运行上文的完整用户态程序以一次性完成所有步骤。

推荐:一次性完成所有功能

编译并运行上文「完整用户态程序示例」中的完整 C 程序,可依次完成:打开并立即喂狗、设置超时、获取当前超时与剩余时间、周期性喂狗、Magic Close 关闭。

# 编译(示例:假设源文件为 watchdog_demo.c ,编译工具为 arm-none-linux-uclibcgnueabihf-gcc)
arm-none-linux-uclibcgnueabihf-gcc -o watchdog_demo watchdog_demo.c -static
# 将运行文件拷贝到开发板,并运行
./watchdog_demo

步骤 1:手动激活看门狗(无其他进程干扰)

在无其他进程占用或喂狗的前提下,手动启动喂狗进程,使看门狗开始计时并在超时前被持续喂狗。

# 方式 1:用 watchdog 工具(10 秒超时,后台运行)
watchdog -t 10 /dev/watchdog &

# 方式 2:无工具则用 Shell 循环
( while true; do echo -n '.' > /dev/watchdog; sleep 1; done ) &

步骤 2:关闭 Watchdog(Magic Close)

killall watchdog   # 自带 magic close 功能,会自动执行关闭看门狗操作