Skip to content

Latest commit

 

History

History
2794 lines (2193 loc) · 93.4 KB

File metadata and controls

2794 lines (2193 loc) · 93.4 KB

Linux 内核 Watchdog(看门狗)子系统深度分析

目录

  1. 概述
  2. 整体架构
  3. 核心数据结构
  4. 框架核心:watchdog_core.c
  5. 字符设备层:watchdog_dev.c
  6. 魔幻字符与 NOWAYOUT 机制
  7. 预超时机制:watchdog_pretimeout
  8. 软件看门狗:softdog
  9. Intel TCO 硬件看门狗
  10. ARM SP805 硬件看门狗
  11. 内核锁死检测:kernel/watchdog.c
  12. sysfs 接口
  13. 与 systemd watchdog 协议的集成
  14. 硬件 vs 软件看门狗对比
  15. 驱动开发实践
  16. watchdog_ops 深度剖析
  17. 硬件 watchdog 超时处理:reset vs panic
  18. Pretimeout Governor 完整实现
  19. NMI Watchdog:perf 事件驱动的硬锁死检测
  20. Buddy Watchdog:无 PMU 的跨 CPU 硬锁死检测
  21. IPMI Watchdog:服务器级可靠性保障
  22. 嵌套看门狗与多级看门狗架构
  23. RAS 场景中的 Watchdog 应用
  24. 看门狗子系统的并发模型与锁分析
  25. 电源管理与看门狗的交互
  26. WDIOC_* ioctl 接口完整参考
  27. 看门狗子系统 tracepoint
  28. Kconfig 配置选项全览
  29. 常见问题与调试方法
  30. 小结

1. 概述

Watchdog(看门狗)定时器是一种硬件或软件机制,用于在系统发生故障(死锁、挂起、内核崩溃等)时自动触发系统复位或其他恢复动作。其基本原理是:守护进程或内核线程必须在规定的超时时间内周期性地"喂狗"(keepalive ping),若超时未喂狗,看门狗认为系统已挂起,触发重启。

Linux 内核的 Watchdog 子系统分为两个相互独立但彼此关联的层次:

  1. 设备看门狗框架drivers/watchdog/):管理硬件/软件看门狗设备,对用户空间暴露 /dev/watchdog* 字符设备接口,供用户态守护进程(如 systemd)使用。

  2. 内核锁死检测器kernel/watchdog.c):纯内核机制,用于检测 CPU 级别的 hardlockup(硬锁死)和 softlockup(软锁死),与用户态无关,采用 NMI/hrtimer 实现。

两者在概念上都是"看门狗",但实现路径、代码位置、使用场景完全不同,本文将分别深入分析。


2. 整体架构

+------------------------------------------------------------------+
|                         用户空间                                  |
|                                                                  |
|  systemd/watchdogd    应用程序                                    |
|  sd_notify("WATCHDOG=1")  write('/dev/watchdog', 'keepalive')    |
+-----------------------------+------------------------------------+
                              |
+-----------------------------v------------------------------------+
|                    字符设备接口层                                  |
|           /dev/watchdog (misc, minor=130)                        |
|           /dev/watchdog0, /dev/watchdog1, ...                    |
|                                                                  |
|  watchdog_fops:                                                  |
|    .open    = watchdog_open      (启动看门狗)                     |
|    .write   = watchdog_write     (喂狗 / 魔幻字符'V')             |
|    .ioctl   = watchdog_ioctl     (WDIOC_* 控制命令)               |
|    .release = watchdog_release   (关闭时决定是否停止)              |
|              [drivers/watchdog/watchdog_dev.c]                   |
+-----------------------------+------------------------------------+
                              |
+-----------------------------v------------------------------------+
|                    Watchdog 核心框架                              |
|         watchdog_core_data (内部运行时状态)                       |
|         watchdog_device    (驱动描述符)                           |
|         watchdog_ops       (驱动回调函数表)                       |
|                                                                  |
|  hrtimer + kthread_worker "watchdogd"                            |
|  (处理 max_hw_heartbeat_ms 场景下的自动喂狗)                      |
|              [drivers/watchdog/watchdog_core.c]                  |
+-------+------------------+--------------+------------------------+
        |                  |              |
+-------v------+  +--------v-------+  +---v---------------------+
| softdog       |  | Intel iTCO_wdt |  | ARM sp805_wdt           |
| (纯软件)      |  | (ICH/PCH 硬件) |  | (AMBA 总线硬件)         |
| hrtimer 实现  |  | I/O 端口操作   |  | MMIO 寄存器操作         |
| softdog.c     |  | iTCO_wdt.c     |  | sp805_wdt.c             |
+---------------+  +----------------+  +-------------------------+

+------------------------------------------------------------------+
|              内核锁死检测层(独立系统)                            |
|                                                                  |
|  kernel/watchdog.c                                               |
|                                                                  |
|  Softlockup Detector:                                            |
|    每 CPU hrtimer (sample_period ~= watchdog_thresh*2/5 秒)      |
|    每 CPU kthread (softlockup_fn, 用 stop_machine 驱动)          |
|    检测条件: 调度器超过 2*watchdog_thresh 秒未调度               |
|                                                                  |
|  Hardlockup Detector:                                            |
|    NMI/perf_event 或 hrtimer_interrupts 计数                     |
|    检测条件: hrtimer 中断超过 watchdog_thresh 秒未递增            |
+------------------------------------------------------------------+

3. 核心数据结构

3.1 watchdog_info(用户空间信息描述符)

文件include/uapi/linux/watchdog.h(第 18-22 行)

struct watchdog_info {
    __u32 options;          /* 驱动支持的功能标志位 */
    __u32 firmware_version; /* 固件版本 */
    __u8  identity[32];     /* 设备标识字符串 */
};

options 字段使用以下标志位(include/uapi/linux/watchdog.h 第 39-51 行):

标志位 含义
WDIOF_OVERHEAT 0x0001 因 CPU 过热复位
WDIOF_FANFAULT 0x0002 风扇故障
WDIOF_CARDRESET 0x0020 上次由看门狗触发复位
WDIOF_SETTIMEOUT 0x0080 支持设置超时时间
WDIOF_MAGICCLOSE 0x0100 支持魔幻关闭字符
WDIOF_PRETIMEOUT 0x0200 支持预超时通知
WDIOF_KEEPALIVEPING 0x8000 支持 keepalive ping

3.2 watchdog_ops(驱动操作函数表)

文件include/linux/watchdog.h(第 47-60 行)

struct watchdog_ops {
    struct module *owner;
    /* mandatory operations */
    int (*start)(struct watchdog_device *);
    /* optional operations */
    int (*stop)(struct watchdog_device *);
    int (*ping)(struct watchdog_device *);
    unsigned int (*status)(struct watchdog_device *);
    int (*set_timeout)(struct watchdog_device *, unsigned int);
    int (*set_pretimeout)(struct watchdog_device *, unsigned int);
    unsigned int (*get_timeleft)(struct watchdog_device *);
    int (*restart)(struct watchdog_device *, unsigned long, void *);
    long (*ioctl)(struct watchdog_device *, unsigned int, unsigned long);
};

设计约束(watchdog_core.c 第 249 行):

  • start 是唯一强制要求实现的回调。
  • stop 是可选的:若驱动不提供 stop 且未设置 max_hw_heartbeat_ms,注册会失败(-EINVAL)。
  • 若驱动不提供 ping,框架在需要喂狗时会调用 start 代替。

3.3 watchdog_device(看门狗设备描述符)

文件include/linux/watchdog.h(第 98-126 行)

struct watchdog_device {
    int id;                          /* 设备编号,由框架分配 */
    struct device *parent;           /* 父设备 */
    const struct attribute_group **groups;
    const struct watchdog_info *info;
    const struct watchdog_ops *ops;
    const struct watchdog_governor *gov; /* 预超时 governor */
    unsigned int bootstatus;         /* 启动时的初始状态 */
    unsigned int timeout;            /* 当前超时值(秒) */
    unsigned int pretimeout;         /* 预超时值(秒) */
    unsigned int min_timeout;        /* 最小超时值(秒) */
    unsigned int max_timeout;        /* 最大超时值(秒) */
    unsigned int min_hw_heartbeat_ms; /* 硬件最小心跳间隔(毫秒) */
    unsigned int max_hw_heartbeat_ms; /* 硬件最大超时(毫秒) */
    struct notifier_block reboot_nb; /* 重启通知 */
    struct notifier_block restart_nb;/* 重启处理通知 */
    struct notifier_block pm_nb;     /* 电源管理通知 */
    void *driver_data;               /* 驱动私有数据 */
    struct watchdog_core_data *wd_data; /* 框架内部数据 */
    unsigned long status;            /* 状态位 */
    struct list_head deferred;       /* 延迟注册链表节点 */
};

状态位定义(include/linux/watchdog.h 第 119-124 行):

#define WDOG_ACTIVE          0  /* 看门狗是否正在运行 */
#define WDOG_NO_WAY_OUT      1  /* nowayout 特性是否开启 */
#define WDOG_STOP_ON_REBOOT  2  /* 重启时是否停止 */
#define WDOG_HW_RUNNING      3  /* 硬件看门狗是否正在计时 */
#define WDOG_STOP_ON_UNREGISTER 4 /* 注销时是否停止 */
#define WDOG_NO_PING_ON_SUSPEND 5 /* 挂起时是否暂停喂狗 */

WDOG_ACTIVEWDOG_HW_RUNNING 的区别至关重要:

  • WDOG_ACTIVE:用户空间已打开设备并激活了看门狗(由框架跟踪)。
  • WDOG_HW_RUNNING:硬件计时器正在倒计时(可能由 BIOS 或上次启动留下)。

3.4 watchdog_core_data(框架内部运行时数据)

文件drivers/watchdog/watchdog_core.h(第 46-63 行)

struct watchdog_core_data {
    struct device dev;               /* 对应 /sys/class/watchdog/watchdogN */
    struct cdev cdev;                /* 字符设备 /dev/watchdogN */
    struct watchdog_device *wdd;     /* 反向指针到 watchdog_device */
    struct mutex lock;               /* 保护并发访问的互斥锁 */
    ktime_t last_keepalive;          /* 上次用户态喂狗时间 */
    ktime_t last_hw_keepalive;       /* 上次实际硬件喂狗时间 */
    ktime_t open_deadline;           /* 用户态接管的最后期限 */
    struct hrtimer timer;            /* 自动喂狗定时器 */
    struct kthread_work work;        /* 提交到 watchdogd kthread */
#if IS_ENABLED(CONFIG_WATCHDOG_HRTIMER_PRETIMEOUT)
    struct hrtimer pretimeout_timer; /* 预超时 hrtimer */
#endif
    unsigned long status;            /* 内部状态位 */
#define _WDOG_DEV_OPEN      0  /* 设备是否已打开(单开保护) */
#define _WDOG_ALLOW_RELEASE 1  /* 是否收到魔幻字符 'V' */
#define _WDOG_KEEPALIVE     2  /* 是否收到 keepalive */
};

max_dogs 常量(watchdog_core.h 第 36 行):

#define MAX_DOGS    32  /* 系统最多支持 32 个看门狗设备 */

3.5 watchdog_governor(预超时 governor)

文件drivers/watchdog/watchdog_pretimeout.h(第 9-12 行)

struct watchdog_governor {
    const char name[WATCHDOG_GOV_NAME_MAXLEN]; /* 最长 20 字符 */
    void (*pretimeout)(struct watchdog_device *wdd);
};

内置的 governor 实现(CONFIG_WATCHDOG_PRETIMEOUT_DEFAULT_GOV):

  • noop:什么都不做,仅记录日志。
  • panic:调用 panic(),触发 kdump 采集现场后重启。

4. 框架核心:watchdog_core.c

文件drivers/watchdog/watchdog_core.c

4.1 初始化流程

框架使用 subsys_initcall_sync 级别初始化(第 489 行),确保在大多数驱动之前完成:

static int __init watchdog_init(void)
{
    int err;
    err = watchdog_dev_init();   // 初始化字符设备层
    if (err < 0)
        return err;
    watchdog_deferred_registration(); // 处理早期注册的设备
    return 0;
}
subsys_initcall_sync(watchdog_init);

watchdog_dev_init()watchdog_dev.c 第 1223-1253 行)完成:

  1. 创建 watchdogd kthread worker(调度策略 SCHED_FIFO)。
  2. 注册 watchdog class(/sys/class/watchdog/)。
  3. 分配字符设备号区间(alloc_chrdev_region,最多 32 个)。

4.2 延迟注册机制

部分看门狗驱动(如 iTCO)需要在 misc 子系统就绪之前就能工作(防止 BIOS 开启的看门狗在内核启动期间超时)。框架通过 wtd_deferred_reg_list 链表实现延迟注册(第 64-87 行):

驱动调用 watchdog_register_device()
        |
        v
wtd_deferred_reg_done == false?
        |
   Yes  |  No
        |    +-> 立即调用 __watchdog_register_device()
        v
watchdog_deferred_registration_add(wdd)
        |
        v
等待 watchdog_deferred_registration() 被 subsys_initcall_sync 调用

4.3 设备注册(___watchdog_register_device)

核心注册函数(第 241-339 行)完成以下工作:

  1. 参数验证:检查 infoopsstart 是否非空;若无 stop 且无 max_hw_heartbeat_ms 则拒绝注册(第 249 行)。

  2. ID 分配:优先使用 DT alias(of_alias_get_id),否则从 IDA 分配(第 261-272 行)。

  3. 字符设备注册:调用 watchdog_dev_register(wdd)

  4. 重启策略:根据模块参数 stop_on_reboot 决定是否注册 reboot_notifier(第 296-318 行)。

  5. 重启处理:若驱动提供 ops->restart,注册 restart_handler(优先级可由 watchdog_set_restart_priority() 设置,第 320-327 行)。

  6. PM 通知:若设置 WDOG_NO_PING_ON_SUSPEND,注册 pm_notifier(第 329-336 行)。

4.4 超时验证逻辑

watchdog_timeout_invalid()include/linux/watchdog.h 第 172-189 行):

static inline bool watchdog_timeout_invalid(const struct watchdog_device *wdd,
                                            unsigned int t)
{
    return t > UINT_MAX / 1000 ||          // 防止毫秒转换溢出
           t < wdd->min_timeout ||         // 低于最小值
           (!wdd->max_hw_heartbeat_ms &&   // 硬件无自动调整能力
            wdd->max_timeout &&
            t > wdd->max_timeout);         // 超出最大值
}

当驱动设置了 max_hw_heartbeat_ms 时,超时上限不受 max_timeout 约束,因为框架会通过 kthread worker 自动分段喂狗。


5. 字符设备层:watchdog_dev.c

文件drivers/watchdog/watchdog_dev.c

5.1 文件操作表

static const struct file_operations watchdog_fops = {
    .owner          = THIS_MODULE,
    .write          = watchdog_write,
    .unlocked_ioctl = watchdog_ioctl,
    .compat_ioctl   = compat_ptr_ioctl,
    .open           = watchdog_open,
    .release        = watchdog_release,
};

/dev/watchdog(第一个设备)同时作为 miscdevice 存在(minor=130,WATCHDOG_MINOR),供传统程序使用(第 996-1000 行):

static struct miscdevice watchdog_miscdev = {
    .minor = WATCHDOG_MINOR,
    .name  = "watchdog",
    .fops  = &watchdog_fops,
};

5.2 open:打开即启动

watchdog_open()(第 859-915 行)是看门狗生命周期的起点:

用户调用 open('/dev/watchdog')
        |
        v
检查 _WDOG_DEV_OPEN 位(单次打开保护)
        |
        v
try_module_get(wdd->ops->owner)(防止驱动被卸载)
        |
        v
watchdog_start(wdd)(实际启动硬件计时器)
        |
        v
设置 open_deadline = KTIME_MAX(用户接管后无限期)
        |
        v
stream_open(inode, file)(不可 seek 的流式文件)

watchdog_start()(第 248-280 行)处理两种情况:

  • 若硬件已在运行(WDOG_HW_RUNNING)且驱动有 ping:调用 __watchdog_ping(),然后设置 WDOG_ACTIVE
  • 否则:调用 ops->start(),同时设置 WDOG_ACTIVEWDOG_HW_RUNNING

5.3 write:喂狗操作

watchdog_write()(第 694-733 行)是用户态喂狗的主要接口:

static ssize_t watchdog_write(struct file *file, const char __user *data,
                              size_t len, loff_t *ppos)
{
    // 1. 每次写入都先清除魔幻字符标志
    clear_bit(_WDOG_ALLOW_RELEASE, &wd_data->status);

    // 2. 扫描写入内容,检测是否包含魔幻字符 'V'
    for (i = 0; i != len; i++) {
        if (get_user(c, data + i))
            return -EFAULT;
        if (c == 'V')
            set_bit(_WDOG_ALLOW_RELEASE, &wd_data->status);
    }

    // 3. 执行实际的喂狗操作
    err = watchdog_ping(wdd);
    ...
}

关键设计:任何写入都会喂狗,写入内容可以是任意字节,但若包含字符 'V'(0x56),则额外设置 _WDOG_ALLOW_RELEASE 标志,允许后续 close() 时停止看门狗。

5.4 ioctl:控制接口

watchdog_ioctl()(第 747-846 行)实现所有 WDIOC_* 命令:

WDIOC_GETSUPPORT    (0) -> copy_to_user(wdd->info)          获取驱动信息
WDIOC_GETSTATUS     (1) -> watchdog_get_status(wdd)         获取当前状态
WDIOC_GETBOOTSTATUS (2) -> wdd->bootstatus                  获取启动状态
WDIOC_SETOPTIONS    (4) -> WDIOS_ENABLECARD / WDIOS_DISABLECARD
WDIOC_KEEPALIVE     (5) -> watchdog_ping(wdd)               手动喂狗
WDIOC_SETTIMEOUT    (6) -> watchdog_set_timeout() + ping    设置超时
WDIOC_GETTIMEOUT    (7) -> wdd->timeout                     获取超时
WDIOC_SETPRETIMEOUT (8) -> watchdog_set_pretimeout()        设置预超时
WDIOC_GETPRETIMEOUT (9) -> wdd->pretimeout                  获取预超时
WDIOC_GETTIMELEFT  (10) -> watchdog_get_timeleft()          获取剩余时间

ioctl 编号定义(include/uapi/linux/watchdog.h 第 24-34 行),使用幻数 'W'

#define WATCHDOG_IOCTL_BASE 'W'
#define WDIOC_GETSUPPORT  _IOR('W', 0, struct watchdog_info)
#define WDIOC_SETTIMEOUT  _IOWR('W', 6, int)
// ...

5.5 release:关闭处理

watchdog_release()(第 937-985 行)是看门狗安全设计的关键:

static int watchdog_release(struct inode *inode, struct file *file)
{
    // 检查是否可以停止
    if (!watchdog_active(wdd))
        err = 0;
    else if (test_and_clear_bit(_WDOG_ALLOW_RELEASE, &wd_data->status) ||
             !(wdd->info->options & WDIOF_MAGICCLOSE))
        err = watchdog_stop(wdd);   // 正常停止

    // 若无法停止,继续喂狗(防止立即超时)
    if (err < 0) {
        pr_crit("watchdog%d: watchdog did not stop!\n", wdd->id);
        watchdog_ping(wdd);
    }

    // 清除单次打开标志,允许重新打开
    clear_bit(_WDOG_DEV_OPEN, &wd_data->status);
    ...
}

watchdog_stop()(第 292-320 行)检查 WDOG_NO_WAY_OUT(nowayout)标志:

if (test_bit(WDOG_NO_WAY_OUT, &wdd->status)) {
    pr_info("watchdog%d: nowayout prevents watchdog being stopped!\n", wdd->id);
    return -EBUSY;
}

5.6 自动喂狗机制(kthread worker)

max_hw_heartbeat_ms 被设置时,用户可以请求比硬件支持更长的超时时间,框架会自动定期喂狗。这通过 hrtimer + kthread_work 实现(第 76-142 行):

watchdog_need_worker() 判断条件:
    hm = wdd->max_hw_heartbeat_ms
    t  = wdd->timeout * 1000 (ms)

    需要 worker 的条件:
        (hm && WDOG_ACTIVE && t > hm)    // 用户请求超时 > 硬件最大超时
     OR (t && !WDOG_ACTIVE && WDOG_HW_RUNNING) // 硬件在跑但用户未接管

喂狗间隔 = min(hw_heartbeat_ms, timeout_ms) / 2

watchdog_timer_expired()(第 229-237 行)是 hrtimer 回调,将工作项提交到 FIFO 优先级的 watchdogd kthread:

static enum hrtimer_restart watchdog_timer_expired(struct hrtimer *timer)
{
    wd_data = container_of(timer, struct watchdog_core_data, timer);
    kthread_queue_work(watchdog_kworker, &wd_data->work);
    return HRTIMER_NORESTART;
}

5.7 open_deadline 机制

CONFIG_WATCHDOG_OPEN_TIMEOUT(默认 0,即无限等待)控制在用户空间接管看门狗之前内核自动维持喂狗的最长时间(第 63-74 行)。

boot_enabled watchdog 启动
        |
        v
hrtimer 开始自动喂狗(handle_boot_enabled=true 时)
        |
        v
倒计时 open_timeout 秒
        |
   超时未被 open()?
        |
   Yes  |
        v
停止自动喂狗,watchdog 超时后重启
(防止守护进程故障无法被发现)

6. 魔幻字符与 NOWAYOUT 机制

6.1 Magic Close(魔幻关闭)

看门狗的一个核心安全特性:意外关闭文件描述符不应停止看门狗。实现方式:

  1. 驱动在 watchdog_info.options 中设置 WDIOF_MAGICCLOSE
  2. 用户程序在关闭前,向 /dev/watchdog 写入包含字符 'V'(ASCII 86)的数据。
  3. 框架在 watchdog_write() 中检测到 'V',设置 _WDOG_ALLOW_RELEASE
  4. 此后 close() 调用 watchdog_release(),检测到该标志后才真正停止看门狗。

若程序崩溃而未写入 'V'close() 不会停止看门狗(_WDOG_ALLOW_RELEASE 未设置),看门狗持续倒计时,最终触发系统复位。

注意事项(watchdog_dev.c 第 709-710 行注释):

"just in case someone wrote the magic character five months ago" 每次 write() 开始时都会先清除 _WDOG_ALLOW_RELEASE,防止旧的魔幻字符残留。

6.2 CONFIG_WATCHDOG_NOWAYOUT

nowayout 是更强的安全保证:看门狗一旦启动,任何情况下都无法从用户态停止

配置(include/linux/watchdog.h 第 128-129 行):

#define WATCHDOG_NOWAYOUT           IS_BUILTIN(CONFIG_WATCHDOG_NOWAYOUT)
#define WATCHDOG_NOWAYOUT_INIT_STATUS (WATCHDOG_NOWAYOUT << WDOG_NO_WAY_OUT)

CONFIG_WATCHDOG_NOWAYOUT=y 时,WATCHDOG_NOWAYOUT 为 1,驱动初始化时自动设置 WDOG_NO_WAY_OUT

也可在运行时通过 sysfs 设置(watchdog_dev.c 第 455-472 行):

static ssize_t nowayout_store(...)
{
    /* nowayout cannot be disabled once set */
    if (test_bit(WDOG_NO_WAY_OUT, &wdd->status) && !value)
        return -EPERM;           // nowayout 一旦设置不可撤销
    watchdog_set_nowayout(wdd, value);
    return len;
}

两者的行为差异:

                    写入 'V' 后 close()    崩溃/意外 close()
                   +-----------------+------------------+
 无 MAGICCLOSE     | 停止看门狗       | 停止看门狗       |
 有 MAGICCLOSE     | 停止看门狗       | 继续运行,复位   |
 有 NOWAYOUT       | 无法停止(EBUSY)| 继续运行,复位   |
                   +-----------------+------------------+

7. 预超时机制:watchdog_pretimeout

7.1 设计思想

预超时(pretimeout)是指在看门狗实际超时之前提前若干秒发出通知,给系统一个"最后机会"收集诊断信息(如内存 dump、堆栈打印)再复位。

时间轴:
|-------- timeout ---------|
|--- pretimeout ---||------| <- 在距超时 pretimeout 秒处触发通知
T                  T+pt   T+timeout
                   ^       ^
                   通知    复位

7.2 实现路径

文件drivers/watchdog/watchdog_pretimeout.c

内核支持两种预超时实现方式:

方式一:硬件预超时(驱动实现 ops->set_pretimeout

  • 硬件在达到预超时点时产生中断/NMI
  • 驱动调用 watchdog_notify_pretimeout(wdd)
  • 框架通知 governor

方式二:hrtimer 软件模拟CONFIG_WATCHDOG_HRTIMER_PRETIMEOUT

  • 每次喂狗后启动 hrtimer,在 timeout - pretimeout 秒后触发
  • watchdog_core.h 第 79-87 行提供了相应接口

watchdog_notify_pretimeout()(第 102-114 行):

void watchdog_notify_pretimeout(struct watchdog_device *wdd)
{
    unsigned long flags;
    spin_lock_irqsave(&pretimeout_lock, flags);
    if (!wdd->gov) {
        spin_unlock_irqrestore(&pretimeout_lock, flags);
        return;
    }
    wdd->gov->pretimeout(wdd);    // 调用 governor 的处理函数
    spin_unlock_irqrestore(&pretimeout_lock, flags);
}

7.3 governor 管理

watchdog_pretimeout.c 维护一个全局 governor 链表(第 35 行):

static LIST_HEAD(governor_list);

governor 的注册/注销使用 governor_lock mutex 保护,但 governor 的调用路径(watchdog_notify_pretimeout)使用 pretimeout_lock spinlock,因为可能在中断上下文中调用。

governor 可在 sysfs 中动态切换(第 596-617 行):

/sys/class/watchdog/watchdog0/pretimeout_governor       <- 读写当前 governor
/sys/class/watchdog/watchdog0/pretimeout_available_governors <- 列出可用 governor

8. 软件看门狗:softdog

文件drivers/watchdog/softdog.c

8.1 实现原理

softdog 是纯软件实现的看门狗,不依赖任何硬件定时器寄存器,使用内核 hrtimer 模拟。

关键参数(第 31-62 行):

#define TIMER_MARGIN    60   /* 默认超时 60 秒 */
static unsigned int soft_margin = TIMER_MARGIN;  /* 模块参数 */
static bool nowayout = WATCHDOG_NOWAYOUT;
static int soft_noboot;    /* =1 时超时后打印但不重启 */
static int soft_panic;     /* =1 时超时后 panic 而非 reboot */
static char *soft_reboot_cmd;       /* 自定义重启命令 */
static bool soft_active_on_boot;    /* 启动时立即激活 */

两个核心 hrtimer(第 64-65 行):

static struct hrtimer softdog_ticktock;    /* 主超时定时器 */
static struct hrtimer softdog_preticktock; /* 预超时定时器 */

8.2 喂狗实现

softdog_ping()(第 133-150 行):

static int softdog_ping(struct watchdog_device *w)
{
    if (!hrtimer_active(&softdog_ticktock))
        __module_get(THIS_MODULE);   // 防止模块被卸载

    // 重置主定时器到 timeout 秒后
    hrtimer_start(&softdog_ticktock, ktime_set(w->timeout, 0),
                  HRTIMER_MODE_REL);

    // 若启用预超时,设置预超时定时器
    if (IS_ENABLED(CONFIG_SOFT_WATCHDOG_PRETIMEOUT)) {
        if (w->pretimeout)
            hrtimer_start(&softdog_preticktock,
                          ktime_set(w->timeout - w->pretimeout, 0),
                          HRTIMER_MODE_REL);
        else
            hrtimer_cancel(&softdog_preticktock);
    }
    return 0;
}

注意:softdog_ops 中没有单独的 startstart 就是 softdog_ping(第 168-172 行):

static const struct watchdog_ops softdog_ops = {
    .owner = THIS_MODULE,
    .start = softdog_ping,
    .stop  = softdog_stop,
};

8.3 超时触发

softdog_fire()(第 78-122 行)是 hrtimer 到期回调,处理三种模式:

softdog_fire() 触发
        |
        +---> soft_noboot == 1?
        |         YES: pr_crit("Triggered - Reboot ignored")
        |
        +---> soft_panic == 1?
        |         YES: panic("Software Watchdog Timer expired")
        |
        +---> soft_reboot_cmd != NULL?
        |         YES: schedule_work -> kthread_run(kernel_restart)
        |              同时重启 hrtimer(兜底机制,防止 kernel_restart 挂起)
        |
        +---> emergency_restart()   // 默认:立即紧急重启

两阶段重启设计(第 90-116 行):当 soft_reboot_cmd 设置时,先尝试优雅的 kernel_restart(),同时重启 hrtimer(额外 60 秒),若 kernel_restart 也挂起则最终调用 emergency_restart()

8.4 与硬件看门狗的关键差异

特性 softdog 硬件看门狗(如 iTCO)
故障覆盖 仅覆盖内核调度正常的情况 覆盖系统完全死机
CPU 挂死 无法检测(hrtimer 不触发) 可以检测(独立硬件)
掉电/硬件故障 无法处理 独立电源,可处理
最大超时 65535 秒 受硬件寄存器限制
依赖 仅需内核运行 需要特定硬件支持
用途 调试、无硬件看门狗时的替代 生产环境高可靠性要求

9. Intel TCO 硬件看门狗

文件drivers/watchdog/iTCO_wdt.c

9.1 硬件背景

TCO(Total Cost of Ownership)是 Intel ICH(I/O Controller Hub)/ PCH(Platform Controller Hub)内置的看门狗定时器,几乎存在于所有 Intel 桌面/服务器平台。驱动支持版本 1-6,覆盖从 82801AA(ICH,约 1999 年)到现代 Lynx Point 等几十种芯片组。

驱动版本号(第 45 行):

#define DRV_VERSION "1.11"

9.2 I/O 寄存器映射

TCO 通过 I/O 端口操作控制(第 72-80 行):

#define TCO_RLD(p)    (TCOBASE(p) + 0x00) /* 计时器重载/当前值 */
#define TCOv1_TMR(p)  (TCOBASE(p) + 0x01) /* v1 定时器初始值 */
#define TCO1_STS(p)   (TCOBASE(p) + 0x04) /* TCO1 状态寄存器 */
#define TCO2_STS(p)   (TCOBASE(p) + 0x06) /* TCO2 状态寄存器 */
#define TCO1_CNT(p)   (TCOBASE(p) + 0x08) /* TCO1 控制寄存器 */
#define TCO2_CNT(p)   (TCOBASE(p) + 0x0a) /* TCO2 控制寄存器 */
#define TCOv2_TMR(p)  (TCOBASE(p) + 0x12) /* v2 定时器初始值 */

9.3 定时器精度

v1/v2 以 tick 为单位,每 tick 约 0.6 秒(第 140-143 行):

static inline unsigned int seconds_to_ticks(struct iTCO_wdt_private *p, int secs)
{
    return p->iTCO_version == 3 ? secs : (secs * 10) / 6;
}
  • v1:最大 63 ticks = 约 37.8 秒;因为计数两次才重启,实际约 75 秒。
  • v2:最大 1023 ticks = 约 613.8 秒(约 10 分钟)。
  • v3+:直接以秒为单位。

9.4 NO_REBOOT 位

硬件提供 NO_REBOOT 位,设置后即使看门狗超时也不会触发重启(BIOS 默认设置此位以防止意外重启)。驱动在启动时必须清除此位才能正常工作(第 284-287 行):

if (p->update_no_reboot_bit(p->no_reboot_priv, false)) {
    dev_err(wd_dev->parent,
            "failed to reset NO_REBOOT flag, reboot disabled by hardware/BIOS\n");
    return -EIO;
}

NO_REBOOT 位的位置随 TCO 版本变化(第 152-172 行):

  • v1:PCI 配置空间 0xd4,bit 1。
  • v2:MMIO GCS 寄存器,bit 5。
  • v3/v5:MMIO PMC 寄存器,bit 4。
  • v6+:TCO1_CNT 寄存器的 bit 0。

9.5 启动/停止/喂狗

启动(第 279-306 行):

1. 清除 NO_REBOOT 位
2. 写入 0x01 到 TCO_RLD(触发重载)
3. 清除 TCO1_CNT[11](Timer Halt 位 = 0 = 使能计数)

停止(第 308-325 行):

1. 设置 TCO1_CNT[11] = 1(Timer Halt)
2. 设置 NO_REBOOT 位(防止后续意外重启)

喂狗(第 327-343 行):

v2+: outw(0x01, TCO_RLD)   /* 重载计时器 */
v1:  outw(0x0008, TCO1_STS) /* 清除超时状态位 */
     outb(0x01, TCO_RLD)

9.6 私有数据结构

struct iTCO_wdt_private {
    struct watchdog_device wddev;      /* 嵌入 watchdog_device */
    unsigned int iTCO_version;         /* TCO 版本 1-6 */
    struct resource *tco_res;          /* TCO I/O 资源 */
    struct resource *smi_res;          /* SMI I/O 资源 */
    unsigned long __iomem *gcs_pmc;    /* GCS/PMC MMIO(v2/v3 NO_REBOOT) */
    struct pci_dev *pci_dev;
    bool suspended;
    void *no_reboot_priv;
    int (*update_no_reboot_bit)(void *p, bool set); /* 函数指针,适配不同版本 */
};

9.7 挂起/恢复

iTCO 在 suspend-to-idle(S0ix)时需要手动停止再重启,因为 S0ix 会停止 tick(第 609-631 行)。在传统 ACPI S3 休眠时,平台固件负责处理,驱动无需操作。


10. ARM SP805 硬件看门狗

文件drivers/watchdog/sp805_wdt.c

10.1 硬件特性

SP805 是 ARM 公司设计的 PrimeCell 看门狗定时器 IP,通过 AMBA(AXI/APB)总线连接,广泛用于各种 ARM SoC(高通、ST、恩智浦等)。

关键寄存器(第 40-55 行):

#define WDTLOAD    0x000  /* 装载值寄存器(计时器初值) */
#define WDTVALUE   0x004  /* 当前计数值(只读) */
#define WDTCONTROL 0x008  /* 控制寄存器 */
    #define INT_ENABLE   (1 << 0)  /* 使能中断(第一次超时) */
    #define RESET_ENABLE (1 << 1)  /* 使能复位(第二次超时) */
#define WDTINTCLR  0x00C  /* 中断清除(写入即喂狗) */
#define WDTLOCK    0xC00  /* 寄存器锁定控制 */
    #define UNLOCK   0x1ACCE551   /* 解锁魔数 */
    #define LOCK     0x00000001   /* 任意值锁定 */

10.2 两阶段超时设计

SP805 的超时分两阶段(sp805_wdt.c 第 99-106 行注释):

第一次超时 -> 产生中断(INT_ENABLE)
                    |
                    v
                喂狗(写 WDTINTCLR)?
                    |
              Yes   |  No(未处理中断)
                    |
                    v
                第二次超时 -> 系统复位(RESET_ENABLE)

因此负载值是期望超时时间对应周期的一半:

load = div_u64(rate, 2) * timeout - 1;

10.3 寄存器锁定

SP805 提供硬件锁保护关键寄存器,防止意外写入(第 167-180 行):

writel_relaxed(UNLOCK, wdt->base + WDTLOCK);  // 先解锁(写 0x1ACCE551)
writel_relaxed(wdt->load_val, wdt->base + WDTLOAD);
writel_relaxed(INT_MASK, wdt->base + WDTINTCLR);
writel_relaxed(INT_ENABLE | RESET_ENABLE, wdt->base + WDTCONTROL);
writel_relaxed(LOCK, wdt->base + WDTLOCK);   // 再锁定(写任意值)
readl_relaxed(wdt->base + WDTLOCK);           // flush posted writes

10.4 驱动注册流程

// sp805_wdt_probe() 第 303 行
watchdog_set_restart_priority(&wdt->wdd, 128); // 默认优先级

// 若硬件已在运行(BIOS 启动),继续维持并标记
if (wdt_is_running(&wdt->wdd)) {
    wdt_enable(&wdt->wdd);
    set_bit(WDOG_HW_RUNNING, &wdt->wdd.status);
}

watchdog_stop_on_reboot(&wdt->wdd);
ret = watchdog_register_device(&wdt->wdd);

11. 内核锁死检测:kernel/watchdog.c

文件kernel/watchdog.c

内核看门狗检测器是完全独立于设备看门狗框架的机制,专门检测内核自身的锁死状态,不涉及 /dev/watchdog

11.1 核心配置变量

// 第 46-52 行
unsigned long __read_mostly watchdog_enabled;
int __read_mostly watchdog_user_enabled = 1;
int __read_mostly watchdog_thresh = 10;  // 默认阈值 10 秒

通过内核命令行参数控制:

  • nowatchdog:禁用所有看门狗检测(第 408-413 行)。
  • nosoftlockup:禁用软锁死检测。
  • nmi_watchdog=0:禁用硬锁死检测(第 118-132 行)。
  • watchdog_thresh=N:设置阈值(第 422-427 行)。

11.2 Softlockup(软锁死)检测

原理:检测内核是否超过 2 * watchdog_thresh 秒没有进行任务调度切换。

实现(第 369-396 行,sample_period 约为 watchdog_thresh * 2 / 5 秒):

每个 CPU 有:
  watchdog_hrtimer    -> 每 sample_period 触发一次
  watchdog_touch_ts   -> 最近一次 reschedule 的时间戳
  watchdog_report_ts  -> 最近一次检测的时间戳

watchdog_timer_fn()(hrtimer 回调,第 773-850 行)工作流程:

hrtimer 到期
        |
        v
watchdog_hardlockup_kick()     // 同时驱动硬锁死检测
        |
        v
停止一个 CPU 上执行 softlockup_fn()
(通过 stop_one_cpu_nowait 提交 stop_machine 工作)
        |
        v
softlockup_fn() 更新 watchdog_touch_ts
        |
        v
is_softlockup() 检查:
  now - period_ts > get_softlockup_thresh() (=watchdog_thresh*2)?
        |
   Yes  |
        v
触发 softlockup 报警(打印堆栈、可选 panic)

get_softlockup_thresh() 定义(第 630-633 行):

static int get_softlockup_thresh(void)
{
    return watchdog_thresh * 2;  // 默认 20 秒
}

touch_softlockup_watchdog()(第 687-692 行)可在内核已知的合法延迟处(如 I/O 等待、suspend)主动重置时间戳,防止误报:

notrace void touch_softlockup_watchdog(void)
{
    touch_softlockup_watchdog_sched();
    wq_watchdog_touch(raw_smp_processor_id());
}
EXPORT_SYMBOL(touch_softlockup_watchdog);

11.3 Hardlockup(硬锁死)检测

原理:检测 CPU 是否超过 watchdog_thresh 秒不能响应 NMI/hrtimer 中断(完全不可中断的死循环、关中断的无限循环等)。

CONFIG_HARDLOCKUP_DETECTOR_COUNTS_HRTIMER 实现(第 136-278 行):

每个 CPU:
  hrtimer_interrupts   (atomic_t) -> 每次 hrtimer 中断递增
  hrtimer_interrupts_saved        -> 上次 NMI 检查时的快照

NMI 处理器调用 watchdog_hardlockup_check(cpu):
  if hrtimer_interrupts 与上次快照相同:
      CPU 在整个 watchdog_thresh 期间没有执行过 hrtimer 中断
      -> 判定为 hardlockup
      -> pr_emerg("CPU%u: Watchdog detected hard LOCKUP on cpu %u")
      -> if hardlockup_panic: nmi_panic()

is_hardlockup()(第 162-177 行):

static bool is_hardlockup(unsigned int cpu)
{
    int hrint = atomic_read(&per_cpu(hrtimer_interrupts, cpu));
    if (per_cpu(hrtimer_interrupts_saved, cpu) == hrint)
        return true;   // 计数未变化,CPU 锁死
    per_cpu(hrtimer_interrupts_saved, cpu) = hrint;
    return false;
}

11.4 中断风暴检测(CONFIG_SOFTLOCKUP_DETECTOR_INTR_STORM)

这是较新的扩展功能(第 429-608 行),用于识别软锁死是否由中断风暴引起:

每 sample_period 采样一次 CPU 利用率:
  System% / Softirq% / Hardirq% / Idle%
  保存最近 5 个采样周期

若 hardirq% > 50% 且临近 softlockup 阈值:
  开始计数各中断源的触发次数(kstat_snapshot_irqs)
  在报警时打印前 5 个最频繁的中断:
    "CPU#X Detect HardIRQ Time exceeds 50%. Most frequent HardIRQs:"

11.5 与设备看门狗的关系

+-----------------+          +----------------------+
| kernel/watchdog |          | drivers/watchdog     |
| (lockup detect) |          | (device watchdog)    |
+-----------------+          +----------------------+
       |                              |
       | 检测内核锁死                  | 监控用户态守护进程
       |                              |
       v                              v
  打印堆栈/panic              触发硬件复位
  (无法重置硬件)              (需要喂狗)
       |                              |
       +------------------------------+
                    |
              两者互补,但完全独立
              (共享 watchdog_thresh sysctl 参数)

两者通过 /proc/sys/kernel/ sysctl 共享部分参数:

  • watchdog:总开关
  • watchdog_thresh:锁死检测阈值(设备看门狗的超时独立设置)
  • nmi_watchdog:硬锁死检测开关

12. sysfs 接口

文件drivers/watchdog/watchdog_dev.c(第 445-657 行)

12.1 属性列表

配置 CONFIG_WATCHDOG_SYSFS=y 时,每个看门狗设备在 /sys/class/watchdog/watchdogN/ 下暴露以下属性:

/sys/class/watchdog/watchdog0/
├── state                    # "active" 或 "inactive"
├── options                  # 驱动支持的功能(十六进制)
├── fw_version               # 固件版本
├── identity                 # 设备标识字符串
├── timeout                  # 当前超时时间(秒)
├── min_timeout              # 最小超时(秒)
├── max_timeout              # 最大超时(秒)
├── pretimeout               # 预超时(秒,0=禁用)
├── timeleft                 # 距下次超时的剩余秒数
├── bootstatus               # 启动时状态(是否由看门狗触发上次重启)
├── status                   # 当前运行状态标志(十六进制)
├── nowayout                 # nowayout 标志(0/1)
├── pretimeout_governor      # 当前预超时 governor(可读写)
└── pretimeout_available_governors  # 可用 governor 列表

典型读取示例:

# 查看设备信息
cat /sys/class/watchdog/watchdog0/identity
# Intel TCO Watchdog

# 查看当前超时
cat /sys/class/watchdog/watchdog0/timeout
# 30

# 查看剩余时间
cat /sys/class/watchdog/watchdog0/timeleft
# 27

# 查看上次重启原因(非零表示由看门狗触发)
cat /sys/class/watchdog/watchdog0/bootstatus
# 0

# 切换预超时 governor
echo panic > /sys/class/watchdog/watchdog0/pretimeout_governor

# 启用 nowayout
echo 1 > /sys/class/watchdog/watchdog0/nowayout
# 注意:设置后无法撤销(EPERM)

12.2 可见性控制

wdt_is_visible()(第 619-634 行)根据设备能力动态隐藏不支持的属性:

static umode_t wdt_is_visible(struct kobject *kobj, struct attribute *attr, int n)
{
    // pretimeout 属性:若驱动不支持预超时则隐藏
    if (attr == &dev_attr_pretimeout.attr && !watchdog_have_pretimeout(wdd))
        mode = 0;
    // governor 属性:需要预超时且开启 GOV 支持
    else if ((attr == &dev_attr_pretimeout_governor.attr || ...) &&
             (!watchdog_have_pretimeout(wdd) || !IS_ENABLED(CONFIG_WATCHDOG_PRETIMEOUT_GOV)))
        mode = 0;
    return mode;
}

12.3 状态标志解读

watchdog_get_status() 返回值中各位的含义(watchdog_dev.c 第 331-357 行):

位字段            含义
WDIOF_CARDRESET   0x0020  上次复位由看门狗触发
WDIOF_MAGICCLOSE  0x0100  已收到魔幻关闭字符(_WDOG_ALLOW_RELEASE 置位)
WDIOF_KEEPALIVEPING 0x8000 keepalive 状态(_WDOG_KEEPALIVE 置位后清除)
WDIOF_PRETIMEOUT  0x0200  支持预超时(HRTIMER 预超时编译时决定)

13. 与 systemd watchdog 协议的集成

13.1 协议概述

systemd 实现了对看门狗的完整支持,分为两个层次:

  1. 系统级(system watchdog):systemd 负责定期喂 /dev/watchdog,保证整个系统存活。
  2. 服务级(service watchdog):被 systemd 管理的服务通过 sd_notify(WATCHDOG=1) 通知 systemd 自己存活,systemd 再转化为对硬件看门狗的喂狗。

13.2 系统级看门狗

systemd 在启动时从环境或配置文件读取看门狗超时,并以超时的一半频率喂狗(防止误差导致超时)。

/etc/systemd/system.conf

[Manager]
RuntimeWatchdogSec=10    # 运行时喂狗间隔
ShutdownWatchdogSec=10m  # 关机/重启时额外超时

systemd 的操作流程:

systemd 启动
        |
        v
open('/dev/watchdog', O_WRONLY|O_CLOEXEC)
        |
        v
ioctl(WDIOC_SETTIMEOUT, &timeout)  // 设置超时
        |
        v
ioctl(WDIOC_GETTIMEOUT, &actual)   // 获取实际生效的超时(硬件可能调整)
        |
        v
周期性 write(fd, "1", 1)            // 喂狗(每 RuntimeWatchdogSec/2 一次)
        |
        v
关机时写入 'V' 然后 close()         // 正常停止

13.3 服务级看门狗

服务程序使用 sd_notify() 发送心跳(libsystemd 提供):

// 服务程序内部(伪代码)
#include <systemd/sd-daemon.h>

// 获取 systemd 期望的喂狗间隔
uint64_t watchdog_usec;
sd_watchdog_enabled(0, &watchdog_usec);
// watchdog_usec 是 WATCHDOG_USEC 环境变量的值

// 主循环中周期性通知
while (running) {
    do_work();
    sd_notify(0, "WATCHDOG=1");  // 告知 systemd 服务存活
    sleep(watchdog_usec / 2 / 1000000);
}

systemd 的服务单元配置:

[Service]
WatchdogSec=30    # 若 30 秒内无 WATCHDOG=1,systemd 认为服务挂起
Restart=on-watchdog  # 服务超时后自动重启

13.4 数据流

服务程序
    |
    | sd_notify("WATCHDOG=1")  (通过 Unix socket $NOTIFY_SOCKET)
    v
systemd (PID 1)
    |
    | 接收到心跳,记录时间戳
    | 若超过 WatchdogSec 未收到心跳,发送 SIGABRT/重启服务
    |
    | 每隔 RuntimeWatchdogSec/2 喂一次硬件
    |
    v write("/dev/watchdog", ...)
/dev/watchdog
    |
    v
iTCO / SP805 / softdog
    |
    v
硬件复位计时器重置

13.5 预超时与 systemd

当硬件看门狗支持预超时时,systemd 可通过以下方式配置:

[Manager]
RuntimeWatchdogPreSec=5   # 预超时 5 秒前通知
RuntimeWatchdogPreGovernor=panic  # 预超时时 panic(触发 kdump)

或通过 sysfs 直接设置:

echo 5 > /sys/class/watchdog/watchdog0/pretimeout
echo panic > /sys/class/watchdog/watchdog0/pretimeout_governor

14. 硬件 vs 软件看门狗对比

+------------------------------------------------------------------+
|                       对比维度                                    |
+------------------+----------------------+-----------------------+
|                  |    softdog           |  硬件看门狗(iTCO)   |
+------------------+----------------------+-----------------------+
| 超时触发依赖      | 内核 hrtimer         | 独立硬件计时器         |
| CPU 完全挂死     | 无法检测             | 可以检测              |
| 关中断死循环     | 无法检测             | 可以检测              |
| 内存故障         | 无法处理             | 正常触发复位          |
| 掉电恢复         | 无看门狗             | 独立电源,持续计时     |
| 超时精度         | ~ms 级              | 0.6 秒/tick(TCO v1)|
| 最大超时         | 65535 秒            | v2: ~614 秒           |
| 所需硬件         | 无                  | Intel ICH/PCH         |
| BIOS 交互        | 无                  | NO_REBOOT 位          |
| 生产环境适用性    | 低                  | 高                    |
+------------------+----------------------+-----------------------+

从驱动实现角度的差异:

softdog 操作函数softdog.c 第 168-172 行):

static const struct watchdog_ops softdog_ops = {
    .owner = THIS_MODULE,
    .start = softdog_ping,   // start 即 ping(hrtimer_start)
    .stop  = softdog_stop,   // hrtimer_cancel
    // 无 ping 独立实现,无 get_timeleft
};

iTCO 操作函数iTCO_wdt.c 第 440-447 行):

static const struct watchdog_ops iTCO_wdt_ops = {
    .owner         = THIS_MODULE,
    .start         = iTCO_wdt_start,         // I/O 端口操作
    .stop          = iTCO_wdt_stop,          // 设置 Timer Halt 位
    .ping          = iTCO_wdt_ping,          // 写 TCO_RLD
    .set_timeout   = iTCO_wdt_set_timeout,   // 写 TCOv2_TMR
    .get_timeleft  = iTCO_wdt_get_timeleft,  // 读 TCO_RLD
};

15. 驱动开发实践

15.1 最小驱动骨架

实现一个合规的看门狗驱动至少需要:

// 1. 定义私有数据结构(嵌入 watchdog_device)
struct my_wdt {
    struct watchdog_device wdd;
    void __iomem *base;
    struct clk *clk;
};

// 2. 实现操作函数
static int my_wdt_start(struct watchdog_device *wdd)
{
    struct my_wdt *wdt = watchdog_get_drvdata(wdd);
    // 启动硬件计时器
    writel(ENABLE_BIT, wdt->base + CTRL_REG);
    return 0;
}

static int my_wdt_ping(struct watchdog_device *wdd)
{
    struct my_wdt *wdt = watchdog_get_drvdata(wdd);
    // 写入任意值到 reload 寄存器
    writel(wdt->load_val, wdt->base + RELOAD_REG);
    return 0;
}

static int my_wdt_stop(struct watchdog_device *wdd)
{
    struct my_wdt *wdt = watchdog_get_drvdata(wdd);
    writel(0, wdt->base + CTRL_REG);
    return 0;
}

static const struct watchdog_ops my_wdt_ops = {
    .owner       = THIS_MODULE,
    .start       = my_wdt_start,   // 必须实现
    .stop        = my_wdt_stop,    // 必须实现(或设置 max_hw_heartbeat_ms)
    .ping        = my_wdt_ping,
    .set_timeout = my_wdt_set_timeout,
    .get_timeleft = my_wdt_get_timeleft,
};

static const struct watchdog_info my_wdt_info = {
    .options  = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE,
    .identity = "My Watchdog",
};

// 3. probe 函数
static int my_wdt_probe(struct platform_device *pdev)
{
    struct my_wdt *wdt;

    wdt = devm_kzalloc(&pdev->dev, sizeof(*wdt), GFP_KERNEL);
    if (!wdt)
        return -ENOMEM;

    wdt->wdd.info    = &my_wdt_info;
    wdt->wdd.ops     = &my_wdt_ops;
    wdt->wdd.parent  = &pdev->dev;
    wdt->wdd.timeout = DEFAULT_TIMEOUT;
    wdt->wdd.min_timeout = 1;
    wdt->wdd.max_timeout = MAX_TIMEOUT;

    watchdog_set_drvdata(&wdt->wdd, wdt);
    watchdog_set_nowayout(&wdt->wdd, nowayout);
    watchdog_stop_on_reboot(&wdt->wdd);
    watchdog_stop_on_unregister(&wdt->wdd);

    // 初始化超时(优先使用 DT 属性,其次模块参数,最后默认值)
    watchdog_init_timeout(&wdt->wdd, heartbeat_param, &pdev->dev);

    // 若硬件已在运行(BIOS 启动)
    if (my_wdt_is_running(wdt)) {
        set_bit(WDOG_HW_RUNNING, &wdt->wdd.status);
    }

    return devm_watchdog_register_device(&pdev->dev, &wdt->wdd);
}

15.2 max_hw_heartbeat_ms 的使用

若硬件支持的最大超时较短(如 TCO v1 约 37 秒),但用户希望配置更长的超时(如 120 秒),可设置此字段让框架自动喂狗:

wdt->wdd.max_hw_heartbeat_ms = 37000;  // 硬件最大 37 秒
// 用户可设置 timeout = 120 秒
// 框架会每 37/2 = 18.5 秒自动调用 ops->ping

此时 max_timeout 不再限制用户可设置的超时上限(参见 watchdog_timeout_invalid())。

15.3 处理 WDOG_HW_RUNNING

若看门狗在 probe 时已经运行(由 BIOS 或 bootloader 启动),驱动需要:

  1. 调用 set_bit(WDOG_HW_RUNNING, &wdd->status) 通知框架。
  2. 框架会启动自动喂狗 worker(若 handle_boot_enabled=true)。
  3. open_deadline 之前,用户态程序必须打开设备接管,否则自动喂狗停止,看门狗超时重启。

此设计防止了一种场景:BIOS 开启的看门狗在内核启动后因无人接管而超时重启。

15.4 devm 资源管理

推荐使用 devm_watchdog_register_device()watchdog_core.c 第 432-453 行)代替手动 watchdog_register_device()/watchdog_unregister_device(),驱动卸载时自动注销,减少错误:

// 推荐做法(自动管理)
return devm_watchdog_register_device(&pdev->dev, &wdt->wdd);

// 等价但需手动管理
ret = watchdog_register_device(&wdt->wdd);
// ... 在 remove() 中调用 watchdog_unregister_device(&wdt->wdd);

16. watchdog_ops 深度剖析

16.1 各回调函数的调用时机

以下 ASCII 时序图展示了各 ops 回调在完整生命周期中的调用顺序:

用户操作          框架层调用              驱动 ops 回调
---------         ----------              -------------
open()     -----> watchdog_start()  -----> ops->start()
                                    或     ops->ping()  (若 WDOG_HW_RUNNING)

write()    -----> watchdog_ping()   -----> ops->ping()
                                    或     ops->start() (若无 ping)

ioctl
SETTIMEOUT -----> watchdog_set_timeout() -> ops->set_timeout()
                                         + ops->ping()

ioctl
GETTIMELEFT -----> watchdog_get_timeleft() -> ops->get_timeleft()

ioctl
GETSTATUS  -----> watchdog_get_status()    -> ops->status()

write('V')        set _WDOG_ALLOW_RELEASE
close()    -----> watchdog_stop()   -----> ops->stop()
                  (若允许停止)

reboot     -----> reboot_notifier   -----> ops->stop()
                  (若 WDOG_STOP_ON_REBOOT)

system
restart    -----> restart_handler   -----> ops->restart()

16.2 start 与 ping 的语义区别

startping 看似相似,但语义不同:

ops->start():
  - 初始化并启动硬件计时器
  - 设置初始超时值
  - 框架调用时机:设备首次激活(WDOG_ACTIVE 从 0 变 1)
  - 若驱动不提供 ping,框架用 start 代替 ping

ops->ping():
  - 仅重置(喂狗)已运行的硬件计时器
  - 不需要重新初始化
  - 性能更高(通常只写一个寄存器)
  - 若驱动不提供 ping,框架自动使用 start

__watchdog_ping() 的实现逻辑(watchdog_dev.c 第 144-177 行)体现了这一区分:

static int __watchdog_ping(struct watchdog_device *wdd)
{
    // 检查最小心跳间隔(防止过于频繁的硬件写入)
    earliest_keepalive = ktime_add(wd_data->last_hw_keepalive,
                                   ms_to_ktime(wdd->min_hw_heartbeat_ms));
    now = ktime_get();

    if (ktime_after(earliest_keepalive, now)) {
        // 还未到最早允许的喂狗时间,延迟执行
        hrtimer_start(&wd_data->timer, ktime_sub(earliest_keepalive, now), ...);
        return 0;
    }

    wd_data->last_hw_keepalive = now;

    if (wdd->ops->ping)
        err = wdd->ops->ping(wdd);  // 优先使用 ping
    else
        err = wdd->ops->start(wdd); // 退化为 start

    if (err == 0)
        watchdog_hrtimer_pretimeout_start(wdd);  // 更新预超时计时

    watchdog_update_worker(wdd);  // 更新自动喂狗定时器
    return err;
}

16.3 ops->restart 与重启优先级

ops->restart 是可选的重启处理函数,允许看门狗驱动参与系统重启过程(例如通过硬件看门狗触发更"干净"的重启,而不是依赖软件 kernel_restart)。

重启优先级体系(watchdog_core.c 第 222-238 行注释):

优先级 0:   最低优先级,作为最后手段(能力有限的看门狗)
优先级 128: 默认,满足一般重启需求
优先级 255: 最高优先级,抢占所有其他处理器

通过 watchdog_set_restart_priority() 设置,驱动可以根据自身能力声明合适的优先级:

// SP805 驱动(sp805_wdt.c 第 303 行)
watchdog_set_restart_priority(&wdt->wdd, 128);

17. 硬件 watchdog 超时处理:reset vs panic

17.1 超时行为的决策树

当看门狗超时时,系统行为取决于多个层次的配置:

看门狗超时触发
        |
        v
+-------+-------+
| 硬件看门狗    |   硬件直接触发复位(不经过内核)
+-------+-------+
        |
        v
系统复位 -> 引导加载程序 -> 内核启动
        |
        v
驱动在 probe() 中读取 bootstatus
  (wdd->bootstatus & WDIOF_CARDRESET 非零)
        |
        v
通过 sysfs 告知用户空间上次复位原因

对于软件看门狗(softdog),触发路径不同:

softdog_fire() 被 hrtimer 调用
        |
        +--- soft_noboot=1 ---> pr_crit (仅打印,不复位)
        |
        +--- soft_panic=1  ---> panic() -> kdump -> 复位
        |
        +--- soft_reboot_cmd 非空 -> kernel_restart(cmd)
        |                            + 备用 hrtimer 60s
        |
        +--- 默认            ---> emergency_restart()

17.2 pretimeout 触发 panic 的完整路径

在需要采集崩溃现场(如 kdump)的场景中,pretimeout + panic governor 是标准配置:

看门狗超时倒计时
        |
  距超时 pretimeout 秒
        |
        v
硬件产生预超时中断(或 hrtimer 触发)
        |
        v
驱动调用 watchdog_notify_pretimeout(wdd)
        |
        v
governor->pretimeout(wdd) 被调用
        |
  governor = "panic"
        |
        v
pretimeout_panic() 调用 panic("watchdog pretimeout event\n")
  [drivers/watchdog/pretimeout_panic.c:18]
        |
        v
内核 panic 处理:
  - 打印 oops 信息
  - 触发 kdump(若配置了 crash kernel)
  - 调用 panic_notifiers
        |
        v
系统复位(由看门狗超时或 panic 处理函数触发)

pretimeout_panic.c 的完整实现极其简洁(第 18-21 行):

static void pretimeout_panic(struct watchdog_device *wdd)
{
    panic("watchdog pretimeout event\n");
}

pretimeout_noop.c 只是打印日志(第 18-21 行):

static void pretimeout_noop(struct watchdog_device *wdd)
{
    pr_alert("watchdog%d: pretimeout event\n", wdd->id);
}

17.3 IPMI watchdog 的超时行为选项

IPMI watchdog 提供了更丰富的超时行为选项(ipmi_watchdog.c 第 87-95 行):

/* Actions to perform on a full timeout. */
#define WDOG_TIMEOUT_NONE       0  /* 无操作(禁用) */
#define WDOG_TIMEOUT_RESET      1  /* 系统复位 */
#define WDOG_TIMEOUT_POWER_DOWN 2  /* 关电源 */
#define WDOG_TIMEOUT_POWER_CYCLE 3 /* 断电再上电 */

通过模块参数配置:

# 加载模块时指定超时动作
modprobe ipmi_watchdog action=reset pretimeout=5 preaction=pre_nmi preop=preop_panic

18. Pretimeout Governor 完整实现

18.1 Governor 注册机制

Governor 通过 watchdog_register_governor() 注册(watchdog_pretimeout.c 第 117-151 行):

int watchdog_register_governor(struct watchdog_governor *gov)
{
    struct watchdog_pretimeout *p;
    struct governor_priv *priv;

    priv = kzalloc_obj(*priv);       // 分配 governor_priv 包装

    mutex_lock(&governor_lock);

    if (find_governor_by_name(gov->name)) {   // 防止重名
        mutex_unlock(&governor_lock);
        kfree(priv);
        return -EBUSY;
    }

    priv->gov = gov;
    list_add(&priv->entry, &governor_list);   // 加入全局链表

    // 若是默认 governor,更新所有已注册但无 governor 的设备
    if (!strncmp(gov->name, WATCHDOG_PRETIMEOUT_DEFAULT_GOV, ...)) {
        spin_lock_irq(&pretimeout_lock);
        default_gov = gov;
        list_for_each_entry(p, &pretimeout_list, entry)
            if (!p->wdd->gov)
                p->wdd->gov = default_gov;
        spin_unlock_irq(&pretimeout_lock);
    }

    mutex_unlock(&governor_lock);
    return 0;
}

18.2 双锁架构的设计理由

pretimeout 子系统维护两把锁:

governor_lock (mutex):
  - 保护 governor_list(注册/注销)
  - 保护 pretimeout_list(设备注册/注销)
  - 允许睡眠(用于 sysfs 写操作)

pretimeout_lock (spinlock):
  - 保护 wdd->gov(governor 指针)
  - 保护 default_gov
  - 不允许睡眠(pretimeout 通知可能来自中断上下文)

调用 governor 时只持有 spinlock:

void watchdog_notify_pretimeout(struct watchdog_device *wdd)
{
    unsigned long flags;
    spin_lock_irqsave(&pretimeout_lock, flags);   // 保护 wdd->gov 读取
    if (!wdd->gov) {
        spin_unlock_irqrestore(&pretimeout_lock, flags);
        return;
    }
    wdd->gov->pretimeout(wdd);  // 在持锁情况下调用 governor
    spin_unlock_irqrestore(&pretimeout_lock, flags);
}

注意:pretimeout_panic 在持有 spinlock 的情况下调用 panic(),这在设计上是有意为之——panic 路径本身不需要可重入性,且在极端条件下(如内存损坏)锁的状态已无意义。

18.3 设备与 Governor 的绑定流程

watchdog_register_pretimeout(wdd)
        |
        v
分配 watchdog_pretimeout 节点,加入 pretimeout_list
        |
        v
spin_lock_irq(&pretimeout_lock)
        |
        v
wdd->gov = default_gov   // 绑定到当前默认 governor
        |
        v
spin_unlock_irq(&pretimeout_lock)

-------------------------

用户通过 sysfs 切换 governor:
echo panic > pretimeout_governor
        |
        v
watchdog_pretimeout_governor_set(wdd, "panic")
        |
        v
mutex_lock(&governor_lock)
priv = find_governor_by_name("panic")
        |
        v
spin_lock_irq(&pretimeout_lock)
wdd->gov = priv->gov   // 原子更新 governor 指针
spin_unlock_irq(&pretimeout_lock)
        |
        v
mutex_unlock(&governor_lock)

19. NMI Watchdog:perf 事件驱动的硬锁死检测

19.1 基本原理

文件kernel/watchdog_perf.c

NMI watchdog 利用 CPU 的硬件性能监控单元(PMU)的溢出中断机制。PMU 可以在 CPU 执行指定数量的指令周期后产生 NMI(不可屏蔽中断),这使得即使在关中断的代码路径中也能检测到锁死。

PMU 配置:
  类型: PERF_TYPE_HARDWARE
  事件: PERF_COUNT_HW_CPU_CYCLES (CPU 周期计数)
  采样周期: hw_nmi_get_sample_period(watchdog_thresh)
           = CPU_FREQ_HZ * watchdog_thresh / 5 (每 watchdog_thresh/5 秒一次 NMI)

每次 NMI 触发:
  watchdog_overflow_callback()
        |
        v
  watchdog_check_timestamp()  // 防止 turbo mode 引起的误判
        |
        v
  watchdog_hardlockup_check(cpu, regs)
        |
        v
  is_hardlockup() -> hrtimer_interrupts 计数是否变化?

19.2 perf 事件创建

hardlockup_detector_event_create()(第 121-139 行):

static struct perf_event *hardlockup_detector_event_create(unsigned int cpu)
{
    struct perf_event_attr *wd_attr;
    struct perf_event *evt;

    wd_attr = &wd_hw_attr;
    wd_attr->sample_period = hw_nmi_get_sample_period(watchdog_thresh);

    // 尝试创建 PMU 事件(绑定到指定 CPU)
    evt = perf_event_create_kernel_counter(wd_attr, cpu, NULL,
                                           watchdog_overflow_callback, NULL);
    if (IS_ERR(evt)) {
        // 主事件失败,尝试备用配置
        wd_attr = &fallback_wd_hw_attr;
        wd_attr->sample_period = hw_nmi_get_sample_period(watchdog_thresh);
        evt = perf_event_create_kernel_counter(wd_attr, cpu, NULL,
                                               watchdog_overflow_callback, NULL);
    }
    return evt;
}

19.3 turbo mode 时间戳检查

PMU 在 turbo mode 下 CPU 频率远高于标称频率,可能导致 NMI 触发频率超过 hrtimer,引起误判。watchdog_check_timestamp() 通过时间戳过滤解决此问题(第 59-76 行):

static bool watchdog_check_timestamp(void)
{
    ktime_t delta, now = ktime_get_mono_fast_ns();

    delta = now - __this_cpu_read(last_timestamp);
    if (delta < watchdog_hrtimer_sample_threshold) {
        // 两次 NMI 间隔太短,可能是 turbo mode 引起的误触
        if (__this_cpu_inc_return(nmi_rearmed) < 10)
            return false;   // 跳过本次检查
    }
    __this_cpu_write(nmi_rearmed, 0);
    __this_cpu_write(last_timestamp, now);
    return true;
}

watchdog_hrtimer_sample_threshold 设置为 sample_period * 2,确保在 hrtimer 有机会触发之前不会误判。

19.4 perf NMI watchdog 的硬件资源消耗

启用 NMI watchdog 会永久消耗一个硬件 PMU 计数器。在启用时内核会打印(第 160-161 行):

NMI watchdog: Enabled. Permanently consumes one hw-PMU counter.

这在以下场景下需要权衡:

  • 性能分析工具(perf stat)可用的计数器减少一个
  • 某些嵌入式处理器只有极少量 PMU 计数器
  • 虚拟机环境中 PMU 可能不可用(arch_perf_nmi_is_available() 检查)

20. Buddy Watchdog:无 PMU 的跨 CPU 硬锁死检测

20.1 设计思想

文件kernel/watchdog_buddy.c

对于没有 PMU 或 PMU 不可用的架构(如某些嵌入式 ARM、无 NMI 支持的系统),内核提供了 buddy watchdog 机制:每个 CPU 检查其"下一个" CPU 的 hrtimer 中断计数。

CPU 拓扑示例(4 核):

CPU0 --检查--> CPU1
CPU1 --检查--> CPU2
CPU2 --检查--> CPU3
CPU3 --检查--> CPU0

每隔 3 个 sample_period(约 watchdog_thresh*1.2 秒)执行一次检查

20.2 核心实现

watchdog_buddy_check_hardlockup()(第 85-110 行):

void watchdog_buddy_check_hardlockup(int hrtimer_interrupts)
{
    unsigned int next_cpu;

    // 每隔 3 次 hrtimer 中断检查一次(约 watchdog_thresh 秒)
    if (hrtimer_interrupts % 3 != 0)
        return;

    // 找到需要检查的下一个 CPU
    next_cpu = watchdog_next_cpu(smp_processor_id());
    if (next_cpu >= nr_cpu_ids)
        return;

    smp_rmb();  // 确保看到 watchdog_cpus 的最新状态

    watchdog_hardlockup_check(next_cpu, NULL);
}

20.3 CPU 热插拔的边界条件处理

当 CPU 上线/下线时,buddy 关系需要更新,可能引入边界条件:

CPU 上线:watchdog_hardlockup_enable(cpu)
        |
        v
触摸新 CPU 的时间戳(防止立即被检查时误判)
        |
        v
触摸下一个 CPU 的时间戳(因为前一个 CPU 现在会检查它)
        |
        v
smp_wmb()  // 确保在 watchdog_cpus 中可见之前时间戳已更新
        |
        v
cpumask_set_cpu(cpu, &watchdog_cpus)

CPU 下线:watchdog_hardlockup_disable(cpu)
        |
        v
next_cpu = watchdog_next_cpu(cpu)
触摸 next_cpu 的时间戳(它将被前一个 CPU 重新检查)
        |
        v
smp_wmb()
        |
        v
cpumask_clear_cpu(cpu, &watchdog_cpus)

21. IPMI Watchdog:服务器级可靠性保障

21.1 IPMI 看门狗架构

文件drivers/char/ipmi/ipmi_watchdog.c

IPMI(Intelligent Platform Management Interface)看门狗通过 BMC(Baseboard Management Controller)实现,BMC 是独立于主处理器的管理控制器,拥有独立的电源和固件,可以在主系统完全死机的情况下发出系统复位或关电命令。

+-------------------------------------------+
|  主系统(主 CPU + 操作系统)               |
|                                            |
|  ipmi_watchdog.c                           |
|       |                                    |
|       | IPMI 命令(通过 KCS/BT/SSIF 接口)|
|       v                                    |
|  IPMI 消息层(ipmi_msghandler.c)          |
|       |                                    |
+-------|-----------------------------------+|
        |                                    |
        v                                    |
+-------------------------------------------+
|  BMC(独立管理控制器)                    |
|  - 独立 CPU(通常是 ARM Cortex-M)        |
|  - 独立固件(OpenBMC / 专有固件)         |
|  - 独立电源轨                              |
|  - 看门狗定时器硬件                        |
+-------------------------------------------+

21.2 IPMI 看门狗命令集

IPMI 规范(IPMI v2.0 规范第 27 章)定义了三条看门狗相关命令(ipmi_watchdog.c 第 120-123 行):

#define IPMI_WDOG_RESET_TIMER   0x22  /* App 命令:重置定时器(喂狗) */
#define IPMI_WDOG_SET_TIMER     0x24  /* App 命令:设置定时器参数 */
#define IPMI_WDOG_GET_TIMER     0x25  /* App 命令:获取定时器参数 */

Set Timer 命令的 6 字节数据格式:

字节 1: Timer Use
  [2:0] Timer Use
    001 = BIOS FRB2  (BIOS 固件引导块 2)
    010 = BIOS POST  (BIOS POST 阶段)
    011 = OS Load    (OS 加载阶段)
    100 = SMS/OS     (SMS 或 OS 运行阶段)  <- 内核驱动使用此值
    101 = OEM
  [5]   Don't Stop on Set
  [6]   Don't Log

字节 2: Timer Actions
  [2:0] Timeout Action
    000 = 无操作
    001 = 硬复位
    010 = 关电源
    011 = 断电再上电
  [6:4] Pre-timeout Interrupt
    000 = 无
    001 = SMI
    010 = NMI/Diagnostic Interrupt
    011 = Messaging Interrupt

字节 3: Pre-timeout Interval(预超时间隔,秒)

字节 4: Timer Use Expiration Flags(清除上次超时标志)

字节 5-6: Initial Countdown(初始超时值,100ms 单位)

21.3 IPMI 看门狗的特殊 read() 接口

IPMI 看门狗驱动在标准 watchdog 框架之外,额外实现了 read() 接口(ipmi_watchdog.c 第 743-793 行),用于在预超时发生时通知用户空间:

static ssize_t ipmi_read(struct file *file, char __user *buf,
                         size_t count, loff_t *ppos)
{
    // 阻塞直到 data_to_read 被设置(由预超时处理函数设置)
    init_waitqueue_entry(&wait, current);
    add_wait_queue(&read_q, &wait);
    while (!data_to_read && !signal_pending(current)) {
        set_current_state(TASK_INTERRUPTIBLE);
        schedule();
    }
    ...
}

当 BMC 触发预超时中断时:

// ipmi_wdog_pretimeout_handler() 第 890-910 行
static void ipmi_wdog_pretimeout_handler(void *handler_data)
{
    if (preaction_val != WDOG_PRETIMEOUT_NONE) {
        if (preop_val == WDOG_PREOP_PANIC) {
            if (atomic_inc_and_test(&preop_panic_excl))
                panic("Watchdog pre-timeout");  // 触发 panic
        } else if (preop_val == WDOG_PREOP_GIVE_DATA) {
            // 通知正在 read() 阻塞的用户进程
            mutex_lock(&ipmi_read_mutex);
            data_to_read = 1;
            wake_up_interruptible(&read_q);   // 唤醒阻塞的读操作
            kill_fasync(&fasync_q, SIGIO, POLL_IN);  // 发送异步通知
            mutex_unlock(&ipmi_read_mutex);
        }
    }

    // 标记需要重新设置定时器(部分 BMC 预超时后计时器状态改变)
    atomic_set(&pretimeout_since_last_heartbeat, 1);
}

21.4 Panic 时的看门狗行为

panic() 发生时,IPMI 看门狗驱动会将超时延长到 panic_wdt_timeout(默认 255 秒),给 kdump 足够时间采集内存转储:

// ipmi_wdog_panic_handler() 第 912-931 行
static void ipmi_wdog_panic_handler(void *user_data)
{
    if (watchdog_user && !panic_event_handled &&
        ipmi_watchdog_state != WDOG_TIMEOUT_NONE) {
        panic_event_handled = 1;

        timeout = panic_wdt_timeout;  // 255 秒
        pretimeout = 0;               // 关闭预超时
        panic_halt_ipmi_set_timeout();  // 非阻塞方式发送 Set Timer 命令
    }
}

panic_halt_ipmi_set_timeout() 使用 ipmi_panic_request_and_wait() 而非普通的 ipmi_request_supply_msgs(),因为 panic 路径中中断和调度器可能不可用,需要忙等待完成。

21.5 BMC 复位后的自动重初始化

当 BMC 被复位时(如固件更新),watchdog 定时器配置会丢失。__ipmi_heartbeat() 中处理了这种情况(第 551-574 行):

if (recv_msg.msg.data[0] == IPMI_WDOG_TIMER_NOT_INIT_RESP) {
    timeout_retries++;
    if (timeout_retries > 3) {
        pr_err("Unable to restore the IPMI watchdog's settings\n");
        rv = -EIO;
        goto out;
    }
    // BMC 被复位,重新发送 Set Timer 命令恢复配置
    rv = _ipmi_set_timeout(IPMI_SET_TIMEOUT_NO_HB);
    // 再次尝试喂狗
    goto restart;
}

21.6 IPMI 看门狗 vs 标准 watchdog 框架

IPMI 看门狗驱动是一个例外:它没有使用 watchdog_device / watchdog_ops 框架,而是直接注册了 miscdevice。这是历史原因造成的——IPMI watchdog 比通用 watchdog 框架更早存在,且其特殊功能(read/poll/异步通知)难以用标准框架表达。

标准 watchdog 框架             IPMI watchdog
--------------------           ----------------
watchdog_device                独立的 miscdevice
watchdog_ops                   独立的 file_operations
/dev/watchdog0                 /dev/watchdog (WATCHDOG_MINOR=130)
watchdog_fops.write            ipmi_wdog_fops.write
无 read 接口                   有 read/poll(预超时通知)
无 fasync                      有 fasync(SIGIO 通知)
统一的 ioctl 分发               自定义 ioctl 实现

22. 嵌套看门狗与多级看门狗架构

22.1 多看门狗设备并存

Linux 系统中可能同时存在多个看门狗设备,每个设备独立工作:

/dev/watchdog0  <- 第一个注册的设备(通常是平台首选)
/dev/watchdog1  <- 第二个设备
...
/dev/watchdog31 <- 最多 32 个(MAX_DOGS)

/dev/watchdog   <- 指向第一个设备(backward compatibility)

典型场景:服务器同时有 Intel TCO 看门狗(/dev/watchdog0)和 IPMI 看门狗(/dev/watchdog,通过 miscdevice)。

22.2 级联看门狗的设计模式

在高可靠性系统中,可以使用级联看门狗:

应用程序
  |
  | sd_notify("WATCHDOG=1") 每 5 秒
  v
systemd (WatchdogSec=10s)
  |
  | write(/dev/watchdog0) 每 15 秒
  v
硬件看门狗 (timeout=30s)
  |
  | 超时后硬件复位
  v
BMC IPMI 看门狗 (timeout=300s)
  |
  | 若主系统 300s 内未通过 IPMI 喂狗则触发关机/重启
  v
系统级恢复

三级保护的含义:

  • 第一级(systemd 服务监控):检测特定服务的死锁
  • 第二级(硬件看门狗):检测整个 OS 的死锁
  • 第三级(IPMI 看门狗):检测系统级别的彻底失联

22.3 watchdog 与 softlockup/hardlockup 的协作

+------------------+    +------------------+    +------------------+
| 应用层看门狗      |    | 内核软锁死检测    |    | NMI 硬锁死检测   |
| (systemd)        |    | (hrtimer)        |    | (PMU/NMI)        |
+------------------+    +------------------+    +------------------+
        |                       |                       |
        | 检测用户态进程死锁      | 检测调度器阻塞          | 检测关中断锁死
        |                       |                       |
        v                       v                       v
  重启服务               打印堆栈/panic           打印寄存器/panic

  覆盖范围逐渐缩小,但可检测到的故障级别逐渐加深

+--------------------------------------------------------------------------+
| 用户态故障  | 调度器阻塞 | 内核软锁死 | 关中断锁死 | 完全挂死(NMI可检)|
+--------------------------------------------------------------------------+
     ^               ^             ^             ^              ^
     |               |             |             |              |
  systemd       softlockup    softlockup    hardlockup       IPMI WDT
  watchdog       (hrtimer)    (hrtimer)      (NMI)           (BMC)

23. RAS 场景中的 Watchdog 应用

23.1 RAS 概述

RAS(Reliability, Availability, Serviceability,可靠性、可用性、服务性)是服务器和工业系统的关键指标。Watchdog 在 RAS 中扮演核心角色。

可靠性(Reliability):系统在规定时间内无故障运行的概率

  • Watchdog 通过自动复位降低 MTTR(平均修复时间)
  • 硬件看门狗确保即使 OS 死机也能自动恢复

可用性(Availability):系统处于可用状态的时间比例

  • Watchdog + kdump 实现快速故障恢复
  • 预超时机制在复位前采集诊断信息,加速根因分析

服务性(Serviceability):系统可被诊断和维修的能力

  • bootstatus 记录上次复位原因
  • pretimeout + panic + kdump 提供完整的崩溃现场

23.2 IPMI 看门狗在 RAS 中的应用

服务器 RAS 场景的标准配置:

# 配置 IPMI 看门狗(高可靠性配置)
modprobe ipmi_watchdog \
    timeout=60 \         # 60 秒超时
    pretimeout=10 \      # 超时前 10 秒预警
    preaction=pre_nmi \  # 预超时时触发 NMI
    preop=preop_panic \  # 预超时时触发 panic(采集 kdump)
    action=reset \       # 超时时硬复位
    panic_wdt_timeout=255 # panic 时延长到 255 秒(留给 kdump)

# 配置 kdump(内存转储)
# /etc/kdump.conf: path /var/crash

23.3 看门狗超时时间的设计原则

在 RAS 场景中,看门狗超时时间的设置需要权衡多个因素:

超时时间设计矩阵:

           太短                    合适                    太长
          ------                  ------                  ------
误报率:  高(正常慢操作触发)      低                      低
恢复时间:快                      合理                    慢
kdump:   可能不完整              充足                    充足
磁盘I/O: 可能未完成              通常完成                肯定完成

推荐值:
  软看门狗(softdog):    60-120 秒
  硬件看门狗(TCO):      30-60 秒
  IPMI 看门狗:           120-300 秒
  预超时(pretimeout):   10-30 秒(给 kdump 足够时间)
  panic_wdt_timeout:     255 秒(默认,满足大多数 kdump 场景)

23.4 bootstatus 与故障追踪

看门狗复位后,驱动需要在 probe 阶段读取并保存 bootstatus:

// iTCO 驱动示例(iTCO_wdt.c)
static int iTCO_wdt_probe(...)
{
    // 读取 TCO2_STS 寄存器的 SECOND_TO_STS 位
    p->wddev.bootstatus = 0;
    val16 = inw(TCO2_STS(p));
    if (val16 & 0x0002) {   /* SECOND_TO_STS 位 */
        p->wddev.bootstatus = WDIOF_CARDRESET;
        /* 清除状态位 */
        outw(0x0002, TCO2_STS(p));
    }
}

用户空间可通过以下方式检查:

# 通过 sysfs
cat /sys/class/watchdog/watchdog0/bootstatus

# 通过 ioctl(WDIOC_GETBOOTSTATUS)
# 返回值非零(WDIOF_CARDRESET=0x0020)表示上次由看门狗触发复位

# 也可检查 /var/log/messages 中的启动日志
# iTCO_wdt: TCO WatchDog Timer triggered, last reboot was caused by a TCO Watchdog timeout

23.5 RAS 日志链路

从看门狗超时到故障记录的完整链路:

1. 看门狗超时即将发生(pretimeout)
        |
2. NMI/中断触发预超时通知
        |
3. panic() 调用(若 pretimeout_governor=panic)
        |
4. panic_notifiers 链调用:
   - IPMI 看门狗延长超时(panic_wdt_timeout)
   - 控制台刷新日志
   - kdump 触发内存转储
        |
5. kdump 采集 vmcore:/var/crash/YYYY-MM-DD/vmcore
        |
6. 系统复位
        |
7. 重启后 crash 分析工具解析 vmcore:
   - crash /usr/lib/debug/vmlinux /var/crash/.../vmcore
   - 查看崩溃时的调用栈、CPU 寄存器、内存状态

24. 看门狗子系统的并发模型与锁分析

24.1 驱动层面的锁

每个 watchdog_core_data 实例有一把互斥锁 wd_data->lock,所有字符设备操作(open/write/ioctl/release)都在持锁情况下执行(watchdog_dev.c 第 859, 694, 747, 937 行):

用户线程 A        用户线程 B        kthread worker
  open()             write()          watchdog_ping_work()
    |                  |                      |
    v                  v                      v
mutex_lock(lock)  mutex_lock(lock)  mutex_lock(lock)
    |    ---------> 阻塞等待              阻塞等待
    |
  执行 watchdog_start()
    |
mutex_unlock(lock)
                       |
                  获得锁,执行 watchdog_ping()
                       |
                  mutex_unlock(lock)

24.2 预超时子系统的双锁设计

+------------------+         +-------------------+
|  governor_lock   |         |  pretimeout_lock  |
|  (mutex)         |         |  (spinlock)       |
+------------------+         +-------------------+
         |                            |
         | 保护内容:                  | 保护内容:
         | - governor_list           | - wdd->gov 指针
         | - pretimeout_list         | - default_gov
         |                           |
         | 调用场景:                  | 调用场景:
         | - register_governor()     | - notify_pretimeout()
         | - unregister_governor()   | - governor_set/get
         | - register_pretimeout()   |
         | - governor_set (mutex部分)|

两把锁的获取顺序:始终先获取 governor_lock,再获取 pretimeout_lock,防止死锁。

24.3 内核锁死检测的无锁设计

watchdog_timer_fn() 是 hrtimer 回调,运行在 HRTIMER_MODE_REL_PINNED_HARD 模式下(硬实时上下文),必须避免睡眠:

  • watchdog_touch_tswatchdog_report_ts:per-CPU 变量,单 CPU 写,多 CPU 读
  • hrtimer_interruptsatomic_t 类型,保证跨 CPU 原子性
  • soft_lockup_nmi_warnunsigned long,使用 test_and_set_bit_lock 实现无锁互斥
// 软锁死报警时的多 CPU 互斥(watchdog.c 第 853-857 行)
if (softlockup_all_cpu_backtrace) {
    if (test_and_set_bit_lock(0, &soft_lockup_nmi_warn))
        return HRTIMER_RESTART;  // 其他 CPU 已在处理,跳过
}

25. 电源管理与看门狗的交互

25.1 suspend 路径

看门狗与系统休眠的交互分三种情况(watchdog_core.c 第 194-218 行):

情况一:未设置 WDOG_NO_PING_ON_SUSPEND(默认)

  • 驱动自行处理(通常在驱动的 suspend/resume 回调中)
  • 不使用 pm_notifier

情况二:设置了 WDOG_NO_PING_ON_SUSPEND

  • 框架通过 pm_notifier 在 suspend prepare 阶段调用 watchdog_dev_suspend()
  • watchdog_dev_suspend() 停止 hrtimer,不再自动喂狗
  • 在 post resume 阶段调用 watchdog_dev_resume() 恢复喂狗
static int watchdog_pm_notifier(struct notifier_block *nb, unsigned long mode, void *data)
{
    switch (mode) {
    case PM_HIBERNATION_PREPARE:
    case PM_RESTORE_PREPARE:
    case PM_SUSPEND_PREPARE:
        ret = watchdog_dev_suspend(wdd);   // 停止喂狗
        break;
    case PM_POST_HIBERNATION:
    case PM_POST_RESTORE:
    case PM_POST_SUSPEND:
        ret = watchdog_dev_resume(wdd);    // 恢复喂狗
        break;
    }
}

25.2 S3/S4 休眠时的硬件行为

不同看门狗硬件在休眠时的行为不同:

看门狗类型        S3(挂起到内存)        S4(挂起到磁盘)
-----------       ----------------        ----------------
softdog           hrtimer 停止,复位      hrtimer 停止,复位
Intel TCO         平台固件接管            平台固件接管
SP805             需要驱动停止计时         需要驱动停止计时
IPMI              BMC 独立运行,可能复位   BMC 独立运行,可能复位

25.3 suspend 前的最后一次喂狗

对于需要在 suspend 前喂狗的场景,驱动应该:

// 驱动的 suspend 回调
static int my_wdt_suspend(struct device *dev)
{
    struct my_wdt *wdt = dev_get_drvdata(dev);

    // 1. 最后一次喂狗
    my_wdt_ping(&wdt->wdd);

    // 2. 停止计时器(若不支持 suspend 下的看门狗)
    my_wdt_stop(&wdt->wdd);

    return 0;
}

26. WDIOC_* ioctl 接口完整参考

文件include/uapi/linux/watchdog.h

26.1 ioctl 命令表

命令                编号    方向      参数类型           说明
----                ----    ----      --------           ----
WDIOC_GETSUPPORT    0x80   读        struct watchdog_info  获取驱动能力
WDIOC_GETSTATUS     0x01   读        int               获取当前状态标志
WDIOC_GETBOOTSTATUS 0x02   读        int               获取启动时状态
WDIOC_SETOPTIONS    0x04   写        int               设置选项
WDIOC_KEEPALIVE     0x05   无        -                 手动喂狗
WDIOC_SETTIMEOUT    0x06   读写      int               设置并返回超时
WDIOC_GETTIMEOUT    0x07   读        int               获取当前超时
WDIOC_SETPRETIMEOUT 0x08   读写      int               设置预超时
WDIOC_GETPRETIMEOUT 0x09   读        int               获取预超时
WDIOC_GETTIMELEFT   0x0A   读        int               获取剩余时间

ioctl 编号使用 _IO/_IOR/_IOW/_IOWR 宏构造,幻数为 'W'(0x57)。

26.2 WDIOC_SETOPTIONS 选项值

// include/uapi/linux/watchdog.h 第 51-52 行
#define WDIOS_DISABLECARD    0x0001  /* 关闭看门狗 */
#define WDIOS_ENABLECARD     0x0002  /* 开启看门狗 */
#define WDIOS_TEMPPANIC      0x0004  /* 温度过高时 panic */

26.3 完整的 C 语言使用示例

#include <fcntl.h>
#include <sys/ioctl.h>
#include <linux/watchdog.h>

int wdt_fd = open("/dev/watchdog0", O_RDWR);

// 获取看门狗信息
struct watchdog_info info;
ioctl(wdt_fd, WDIOC_GETSUPPORT, &info);
printf("Identity: %s\n", info.identity);
printf("Options:  0x%08x\n", info.options);

// 获取启动状态(检查上次是否由看门狗触发复位)
int bootstatus;
ioctl(wdt_fd, WDIOC_GETBOOTSTATUS, &bootstatus);
if (bootstatus & WDIOF_CARDRESET)
    printf("Last reboot was caused by watchdog!\n");

// 设置超时(60 秒)
int timeout = 60;
ioctl(wdt_fd, WDIOC_SETTIMEOUT, &timeout);
// timeout 现在包含实际设置的值(硬件可能调整)
printf("Actual timeout: %d seconds\n", timeout);

// 设置预超时(超时前 10 秒通知)
int pretimeout = 10;
ioctl(wdt_fd, WDIOC_SETPRETIMEOUT, &pretimeout);

// 喂狗(通过 ioctl)
ioctl(wdt_fd, WDIOC_KEEPALIVE, 0);

// 或者通过 write 喂狗(任意字节)
write(wdt_fd, "1", 1);

// 获取剩余时间
int timeleft;
ioctl(wdt_fd, WDIOC_GETTIMELEFT, &timeleft);
printf("Time left: %d seconds\n", timeleft);

// 正常停止(若支持 MAGICCLOSE)
write(wdt_fd, "V", 1);   // 写魔幻字符
close(wdt_fd);            // 此后看门狗停止

27. 看门狗子系统 tracepoint

27.1 可用的 tracepoint

文件include/trace/events/watchdog.h(由 watchdog_core.c 第 42-43 行通过 CREATE_TRACE_POINTS 创建)

看门狗框架在关键路径上提供了 tracepoint,用于调试和性能分析:

tracepoint                    触发位置
---------                     --------
watchdog_start                watchdog_start() 调用 ops->start()
watchdog_ping                 __watchdog_ping() 调用 ops->ping()
watchdog_stop                 watchdog_stop() 调用 ops->stop()
watchdog_set_timeout          watchdog_set_timeout() 后

27.2 使用 ftrace 追踪看门狗事件

# 启用 watchdog tracepoint
echo 1 > /sys/kernel/debug/tracing/events/watchdog/enable

# 查看事件
cat /sys/kernel/debug/tracing/trace

# 典型输出示例:
# watchdogd-1234 [000] .... watchdog_start: watchdog0 err=0
# watchdogd-1234 [000] .... watchdog_ping:  watchdog0 err=0
# watchdogd-1234 [000] .... watchdog_stop:  watchdog0 err=0

27.3 perf 与 NMI watchdog

使用 perf 工具时需注意 NMI watchdog 的影响:

# 查看 NMI watchdog 是否消耗了 PMU 计数器
cat /proc/sys/kernel/nmi_watchdog
# 1 = 启用(消耗一个 PMU 计数器)
# 0 = 禁用

# 临时禁用 NMI watchdog 以释放 PMU 计数器(性能分析场景)
echo 0 > /proc/sys/kernel/nmi_watchdog

# perf stat 时可用的计数器增加一个
perf stat -e cycles,instructions,cache-misses ./my_program

# 恢复 NMI watchdog
echo 1 > /proc/sys/kernel/nmi_watchdog

28. Kconfig 配置选项全览

28.1 核心配置

# 设备看门狗框架
CONFIG_WATCHDOG            y/m     # 看门狗支持总开关

CONFIG_WATCHDOG_CORE       y       # 核心框架(watchdog_core.c + watchdog_dev.c)

CONFIG_WATCHDOG_NOWAYOUT   y/n     # 启动后不可停止的强制选项
                                   # 生产环境建议开启

CONFIG_WATCHDOG_HANDLE_BOOT_ENABLED y/n  # 处理引导时已启动的看门狗
                                         # 建议开启(防止 BIOS WDT 在启动时超时)

CONFIG_WATCHDOG_OPEN_TIMEOUT  0    # 等待用户空间接管的超时(0=无限等待)

CONFIG_WATCHDOG_SYSFS      y/n     # sysfs 属性暴露
                                   # 生产环境建议开启(便于监控)

28.2 预超时相关配置

CONFIG_WATCHDOG_PRETIMEOUT_GOV  y/m  # 预超时 governor 支持

CONFIG_WATCHDOG_PRETIMEOUT_DEFAULT_GOV_NOOP  y  # 默认 governor 为 noop
CONFIG_WATCHDOG_PRETIMEOUT_DEFAULT_GOV_PANIC y  # 默认 governor 为 panic
                                                # 二选一(服务器建议 panic)

CONFIG_WATCHDOG_PRETIMEOUT_GOV_NOOP    y/m  # 编译 noop governor
CONFIG_WATCHDOG_PRETIMEOUT_GOV_PANIC   y/m  # 编译 panic governor

CONFIG_WATCHDOG_HRTIMER_PRETIMEOUT  y/n  # 用 hrtimer 模拟预超时
                                          # 适用于无硬件预超时支持的驱动

28.3 锁死检测相关配置

# 软锁死检测
CONFIG_SOFTLOCKUP_DETECTOR   y/n  # 软锁死检测器

CONFIG_BOOTPARAM_SOFTLOCKUP_PANIC     0/1  # 软锁死时 panic(默认 0)
CONFIG_SOFTLOCKUP_DETECTOR_INTR_STORM y/n  # 中断风暴检测

# 硬锁死检测
CONFIG_HARDLOCKUP_DETECTOR    y/n  # 硬锁死检测总开关

CONFIG_HARDLOCKUP_DETECTOR_PERF  y  # 使用 PMU/perf 实现(x86 默认)
CONFIG_HARDLOCKUP_DETECTOR_BUDDY y  # 使用 buddy 机制(无 PMU 架构)

CONFIG_BOOTPARAM_HARDLOCKUP_PANIC     0/1  # 硬锁死时 panic(服务器建议 1)

CONFIG_HARDLOCKUP_DETECTOR_COUNTS_HRTIMER y  # hrtimer 计数方式(非 perf)
CONFIG_HARDLOCKUP_CHECK_TIMESTAMP      y/n  # 时间戳过滤(防 turbo 误判)

28.4 常用驱动配置

CONFIG_SOFT_WATCHDOG         y/m  # 软件看门狗(softdog)
CONFIG_SOFT_WATCHDOG_PRETIMEOUT y/n  # softdog 预超时支持

CONFIG_ITCO_WDT              y/m  # Intel TCO 看门狗
CONFIG_ITCO_VENDOR_SUPPORT   y/n  # 厂商特定支持(HP iLO 等)

CONFIG_SP805_WATCHDOG        y/m  # ARM SP805

CONFIG_IPMI_WATCHDOG         y/m  # IPMI 看门狗(服务器必备)

29. 常见问题与调试方法

29.1 看门狗无法停止(EBUSY)

现象close() 返回 EBUSY,进程退出后系统在超时后复位

原因一nowayout 已设置

cat /sys/class/watchdog/watchdog0/nowayout
# 1 = nowayout 已启用,无法停止

原因二:未写入魔幻字符 'V'

# 正确停止看门狗的方式:
echo 'V' > /dev/watchdog0
# 或在 C 代码中:
write(fd, "V", 1);
close(fd);

29.2 看门狗超时时间不准确

现象:设置 60 秒,但实际超时为 37.8 秒

原因:硬件限制。例如 iTCO v1 最大 37.8 秒,驱动会自动将超时截断为硬件支持的最大值,并通过 WDIOC_GETTIMEOUT 返回实际值。

解决方案

# 先获取实际超时
ioctl(WDIOC_SETTIMEOUT, &timeout)  # 同时返回实际值
#
ioctl(WDIOC_GETTIMEOUT, &actual_timeout)

若驱动设置了 max_hw_heartbeat_ms,可以设置更长的超时(框架自动喂狗)。

29.3 软锁死误报

现象:系统日志出现 BUG: soft lockup - CPU#X stuck for 20s!,但系统工作正常

可能原因

  1. 系统负载极高(CPU 忙于高优先级任务,调度器长时间未切换)
  2. 虚拟机被宿主机暂停(VM pause)
  3. 大量中断处理(INTR_STORM)

调试方法

# 查看 CPU 利用率统计(若开启了 INTR_STORM 检测)
# 日志中会有如下信息:
# watchdog: CPU#0 Utilization every 4000ms during lockup:
#   #1: 10% system,  5% softirq, 80% hardirq,  5% idle

# 临时增大 softlockup 阈值(减少误报)
echo 30 > /proc/sys/kernel/watchdog_thresh

# 临时禁用 softlockup 检测
echo 0 > /proc/sys/kernel/soft_watchdog

29.4 看门狗驱动注册失败

现象dmesg 显示 failed to register watchdog device (err = -22) 或类似错误

常见原因及排查

# 检查 NO_REBOOT 位(仅 iTCO)
# 驱动日志:failed to reset NO_REBOOT flag, reboot disabled by hardware/BIOS
# 解决:部分主板需要在 BIOS 中启用看门狗功能

# 检查是否有其他驱动占用了 /dev/watchdog (minor=130)
lsmod | grep watchdog
# 若 ipmi_watchdog 已加载,它会占用 /dev/watchdog

# 检查 IDA 是否耗尽(超过 32 个设备)
cat /sys/class/watchdog/  # 列出当前设备数量

29.5 IPMI 看门狗 BMC 无响应

现象ipmi_watchdog: heartbeat send failure: -5 或类似错误

调试步骤

# 1. 检查 IPMI 接口是否可用
ipmitool bmc info

# 2. 检查 ipmi_si 驱动是否加载
lsmod | grep ipmi_si

# 3. 检查 BMC 是否响应 watchdog 命令
ipmitool mc watchdog get

# 4. 检查模块参数
cat /sys/module/ipmi_watchdog/parameters/timeout
cat /sys/module/ipmi_watchdog/parameters/action

30. 小结

架构层次回顾

用户态
  |
  | open/write/ioctl  /dev/watchdog
  v
watchdog_dev.c      字符设备层(单次打开、魔幻字符、ioctl 分发)
  |
  v
watchdog_core.c     注册/注销框架(IDA、notifier、deferred reg)
  |
  | ops->start/stop/ping/set_timeout
  v
驱动层              softdog / iTCO / sp805 / IPMI / ...
  |
  v
硬件/hrtimer        实际计时机制

(独立)
kernel/watchdog.c   内核锁死检测(softlockup/hardlockup)
kernel/watchdog_perf.c   NMI watchdog(perf PMU)
kernel/watchdog_buddy.c  Buddy watchdog(无 PMU 架构)

关键设计决策

  1. 单次打开保护_WDOG_DEV_OPEN 位确保 /dev/watchdog 同一时间只能有一个进程控制,防止多个守护进程竞争喂狗导致混乱。

  2. 魔幻字符设计:写入 'V' 才允许 close 停止,是防御性设计——意外关闭文件描述符(如 execO_CLOEXEC)不会停止看门狗。

  3. NOWAYOUT 不可逆:通过 EPERM 阻止撤销 nowayout,保证一旦启用就是真正的不可停止,避免攻击者通过关闭看门狗来延长攻击窗口。

  4. kthread + hrtimer 自动喂狗:解决了"用户超时 > 硬件最大超时"的矛盾,同时保证了硬件看门狗在用户态接管之前不超时。

  5. 两层 governor 锁governor_lock(mutex,用于注册/注销)和 pretimeout_lock(spinlock,用于回调调用),前者允许睡眠,后者保证中断上下文安全。

  6. deferred registration:解决了看门狗驱动需要在 misc 就绪之前加载的鸡和蛋问题,通过 subsys_initcall_sync 时机处理积压的注册请求。

  7. NMI watchdog 的 PMU 消耗:硬锁死检测需要消耗一个硬件 PMU 计数器,这是功能与资源的权衡;buddy watchdog 机制为无 PMU 场景提供了无资源消耗的替代方案。

  8. IPMI 的独立实现:IPMI watchdog 绕过了通用框架,直接实现 miscdevice,原因是其独特的 read/poll/fasync 接口无法用标准 watchdog_ops 表达,体现了框架的局限性。

相关文件索引

文件 说明
drivers/watchdog/watchdog_core.c 框架核心:注册/注销/notifier
drivers/watchdog/watchdog_dev.c 字符设备:open/write/ioctl/release
drivers/watchdog/watchdog_core.h 内部头:watchdog_core_data 定义
drivers/watchdog/watchdog_pretimeout.c 预超时 governor 管理
drivers/watchdog/watchdog_pretimeout.h watchdog_governor 定义
drivers/watchdog/pretimeout_noop.c noop governor 实现
drivers/watchdog/pretimeout_panic.c panic governor 实现
include/linux/watchdog.h 驱动侧头文件:watchdog_device/ops
include/uapi/linux/watchdog.h 用户空间头文件:ioctl 定义
drivers/watchdog/softdog.c 软件看门狗实现
drivers/watchdog/iTCO_wdt.c Intel TCO 硬件看门狗
drivers/watchdog/sp805_wdt.c ARM SP805 硬件看门狗
drivers/char/ipmi/ipmi_watchdog.c IPMI 看门狗(服务器级)
kernel/watchdog.c 内核锁死检测(softlockup/hardlockup)
kernel/watchdog_perf.c NMI watchdog(perf PMU 实现)
kernel/watchdog_buddy.c Buddy watchdog(无 PMU 架构)

由 Claude Code 分析生成