- 概述
- 整体架构
- 核心数据结构
- 框架核心:watchdog_core.c
- 字符设备层:watchdog_dev.c
- 魔幻字符与 NOWAYOUT 机制
- 预超时机制:watchdog_pretimeout
- 软件看门狗:softdog
- Intel TCO 硬件看门狗
- ARM SP805 硬件看门狗
- 内核锁死检测:kernel/watchdog.c
- sysfs 接口
- 与 systemd watchdog 协议的集成
- 硬件 vs 软件看门狗对比
- 驱动开发实践
- watchdog_ops 深度剖析
- 硬件 watchdog 超时处理:reset vs panic
- Pretimeout Governor 完整实现
- NMI Watchdog:perf 事件驱动的硬锁死检测
- Buddy Watchdog:无 PMU 的跨 CPU 硬锁死检测
- IPMI Watchdog:服务器级可靠性保障
- 嵌套看门狗与多级看门狗架构
- RAS 场景中的 Watchdog 应用
- 看门狗子系统的并发模型与锁分析
- 电源管理与看门狗的交互
- WDIOC_* ioctl 接口完整参考
- 看门狗子系统 tracepoint
- Kconfig 配置选项全览
- 常见问题与调试方法
- 小结
Watchdog(看门狗)定时器是一种硬件或软件机制,用于在系统发生故障(死锁、挂起、内核崩溃等)时自动触发系统复位或其他恢复动作。其基本原理是:守护进程或内核线程必须在规定的超时时间内周期性地"喂狗"(keepalive ping),若超时未喂狗,看门狗认为系统已挂起,触发重启。
Linux 内核的 Watchdog 子系统分为两个相互独立但彼此关联的层次:
-
设备看门狗框架(
drivers/watchdog/):管理硬件/软件看门狗设备,对用户空间暴露/dev/watchdog*字符设备接口,供用户态守护进程(如 systemd)使用。 -
内核锁死检测器(
kernel/watchdog.c):纯内核机制,用于检测 CPU 级别的 hardlockup(硬锁死)和 softlockup(软锁死),与用户态无关,采用 NMI/hrtimer 实现。
两者在概念上都是"看门狗",但实现路径、代码位置、使用场景完全不同,本文将分别深入分析。
+------------------------------------------------------------------+
| 用户空间 |
| |
| 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 秒未递增 |
+------------------------------------------------------------------+
文件: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 |
文件: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代替。
文件: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_ACTIVE 和 WDOG_HW_RUNNING 的区别至关重要:
WDOG_ACTIVE:用户空间已打开设备并激活了看门狗(由框架跟踪)。WDOG_HW_RUNNING:硬件计时器正在倒计时(可能由 BIOS 或上次启动留下)。
文件: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 个看门狗设备 */文件: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 采集现场后重启。
文件:drivers/watchdog/watchdog_core.c
框架使用 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 行)完成:
- 创建
watchdogdkthread worker(调度策略 SCHED_FIFO)。 - 注册
watchdogclass(/sys/class/watchdog/)。 - 分配字符设备号区间(
alloc_chrdev_region,最多 32 个)。
部分看门狗驱动(如 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 调用
核心注册函数(第 241-339 行)完成以下工作:
-
参数验证:检查
info、ops、start是否非空;若无stop且无max_hw_heartbeat_ms则拒绝注册(第 249 行)。 -
ID 分配:优先使用 DT alias(
of_alias_get_id),否则从 IDA 分配(第 261-272 行)。 -
字符设备注册:调用
watchdog_dev_register(wdd)。 -
重启策略:根据模块参数
stop_on_reboot决定是否注册reboot_notifier(第 296-318 行)。 -
重启处理:若驱动提供
ops->restart,注册restart_handler(优先级可由watchdog_set_restart_priority()设置,第 320-327 行)。 -
PM 通知:若设置
WDOG_NO_PING_ON_SUSPEND,注册pm_notifier(第 329-336 行)。
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 自动分段喂狗。
文件:drivers/watchdog/watchdog_dev.c
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,
};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_ACTIVE和WDOG_HW_RUNNING。
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() 时停止看门狗。
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)
// ...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;
}当 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;
}CONFIG_WATCHDOG_OPEN_TIMEOUT(默认 0,即无限等待)控制在用户空间接管看门狗之前内核自动维持喂狗的最长时间(第 63-74 行)。
boot_enabled watchdog 启动
|
v
hrtimer 开始自动喂狗(handle_boot_enabled=true 时)
|
v
倒计时 open_timeout 秒
|
超时未被 open()?
|
Yes |
v
停止自动喂狗,watchdog 超时后重启
(防止守护进程故障无法被发现)
看门狗的一个核心安全特性:意外关闭文件描述符不应停止看门狗。实现方式:
- 驱动在
watchdog_info.options中设置WDIOF_MAGICCLOSE。 - 用户程序在关闭前,向
/dev/watchdog写入包含字符'V'(ASCII 86)的数据。 - 框架在
watchdog_write()中检测到'V',设置_WDOG_ALLOW_RELEASE。 - 此后
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,防止旧的魔幻字符残留。
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)| 继续运行,复位 |
+-----------------+------------------+
预超时(pretimeout)是指在看门狗实际超时之前提前若干秒发出通知,给系统一个"最后机会"收集诊断信息(如内存 dump、堆栈打印)再复位。
时间轴:
|-------- timeout ---------|
|--- pretimeout ---||------| <- 在距超时 pretimeout 秒处触发通知
T T+pt T+timeout
^ ^
通知 复位
文件: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);
}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
文件:drivers/watchdog/softdog.c
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; /* 预超时定时器 */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 中没有单独的 start,start 就是 softdog_ping(第 168-172 行):
static const struct watchdog_ops softdog_ops = {
.owner = THIS_MODULE,
.start = softdog_ping,
.stop = softdog_stop,
};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()。
| 特性 | softdog | 硬件看门狗(如 iTCO) |
|---|---|---|
| 故障覆盖 | 仅覆盖内核调度正常的情况 | 覆盖系统完全死机 |
| CPU 挂死 | 无法检测(hrtimer 不触发) | 可以检测(独立硬件) |
| 掉电/硬件故障 | 无法处理 | 独立电源,可处理 |
| 最大超时 | 65535 秒 | 受硬件寄存器限制 |
| 依赖 | 仅需内核运行 | 需要特定硬件支持 |
| 用途 | 调试、无硬件看门狗时的替代 | 生产环境高可靠性要求 |
文件:drivers/watchdog/iTCO_wdt.c
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"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 定时器初始值 */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+:直接以秒为单位。
硬件提供 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。
启动(第 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)
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); /* 函数指针,适配不同版本 */
};iTCO 在 suspend-to-idle(S0ix)时需要手动停止再重启,因为 S0ix 会停止 tick(第 609-631 行)。在传统 ACPI S3 休眠时,平台固件负责处理,驱动无需操作。
文件:drivers/watchdog/sp805_wdt.c
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 /* 任意值锁定 */SP805 的超时分两阶段(sp805_wdt.c 第 99-106 行注释):
第一次超时 -> 产生中断(INT_ENABLE)
|
v
喂狗(写 WDTINTCLR)?
|
Yes | No(未处理中断)
|
v
第二次超时 -> 系统复位(RESET_ENABLE)
因此负载值是期望超时时间对应周期的一半:
load = div_u64(rate, 2) * timeout - 1;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// 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);文件:kernel/watchdog.c
内核看门狗检测器是完全独立于设备看门狗框架的机制,专门检测内核自身的锁死状态,不涉及 /dev/watchdog。
// 第 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 行)。
原理:检测内核是否超过 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);原理:检测 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;
}这是较新的扩展功能(第 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:"
+-----------------+ +----------------------+
| kernel/watchdog | | drivers/watchdog |
| (lockup detect) | | (device watchdog) |
+-----------------+ +----------------------+
| |
| 检测内核锁死 | 监控用户态守护进程
| |
v v
打印堆栈/panic 触发硬件复位
(无法重置硬件) (需要喂狗)
| |
+------------------------------+
|
两者互补,但完全独立
(共享 watchdog_thresh sysctl 参数)
两者通过 /proc/sys/kernel/ sysctl 共享部分参数:
watchdog:总开关watchdog_thresh:锁死检测阈值(设备看门狗的超时独立设置)nmi_watchdog:硬锁死检测开关
文件:drivers/watchdog/watchdog_dev.c(第 445-657 行)
配置 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)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;
}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 预超时编译时决定)
systemd 实现了对看门狗的完整支持,分为两个层次:
- 系统级(system watchdog):systemd 负责定期喂
/dev/watchdog,保证整个系统存活。 - 服务级(service watchdog):被 systemd 管理的服务通过
sd_notify(WATCHDOG=1)通知 systemd 自己存活,systemd 再转化为对硬件看门狗的喂狗。
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() // 正常停止
服务程序使用 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 # 服务超时后自动重启服务程序
|
| 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
硬件复位计时器重置
当硬件看门狗支持预超时时,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+------------------------------------------------------------------+
| 对比维度 |
+------------------+----------------------+-----------------------+
| | 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
};实现一个合规的看门狗驱动至少需要:
// 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);
}若硬件支持的最大超时较短(如 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())。
若看门狗在 probe 时已经运行(由 BIOS 或 bootloader 启动),驱动需要:
- 调用
set_bit(WDOG_HW_RUNNING, &wdd->status)通知框架。 - 框架会启动自动喂狗 worker(若
handle_boot_enabled=true)。 - 在
open_deadline之前,用户态程序必须打开设备接管,否则自动喂狗停止,看门狗超时重启。
此设计防止了一种场景:BIOS 开启的看门狗在内核启动后因无人接管而超时重启。
推荐使用 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);以下 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()
start 和 ping 看似相似,但语义不同:
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;
}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);当看门狗超时时,系统行为取决于多个层次的配置:
看门狗超时触发
|
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()
在需要采集崩溃现场(如 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);
}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_panicGovernor 通过 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;
}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 路径本身不需要可重入性,且在极端条件下(如内存损坏)锁的状态已无意义。
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)
文件: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 计数是否变化?
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;
}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 有机会触发之前不会误判。
启用 NMI watchdog 会永久消耗一个硬件 PMU 计数器。在启用时内核会打印(第 160-161 行):
NMI watchdog: Enabled. Permanently consumes one hw-PMU counter.
这在以下场景下需要权衡:
- 性能分析工具(perf stat)可用的计数器减少一个
- 某些嵌入式处理器只有极少量 PMU 计数器
- 虚拟机环境中 PMU 可能不可用(
arch_perf_nmi_is_available()检查)
文件: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 秒)执行一次检查
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);
}当 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)
文件: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 / 专有固件) |
| - 独立电源轨 |
| - 看门狗定时器硬件 |
+-------------------------------------------+
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 单位)
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);
}在 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 路径中中断和调度器可能不可用,需要忙等待完成。
当 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;
}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 实现
Linux 系统中可能同时存在多个看门狗设备,每个设备独立工作:
/dev/watchdog0 <- 第一个注册的设备(通常是平台首选)
/dev/watchdog1 <- 第二个设备
...
/dev/watchdog31 <- 最多 32 个(MAX_DOGS)
/dev/watchdog <- 指向第一个设备(backward compatibility)
典型场景:服务器同时有 Intel TCO 看门狗(/dev/watchdog0)和 IPMI 看门狗(/dev/watchdog,通过 miscdevice)。
在高可靠性系统中,可以使用级联看门狗:
应用程序
|
| 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 看门狗):检测系统级别的彻底失联
+------------------+ +------------------+ +------------------+
| 应用层看门狗 | | 内核软锁死检测 | | NMI 硬锁死检测 |
| (systemd) | | (hrtimer) | | (PMU/NMI) |
+------------------+ +------------------+ +------------------+
| | |
| 检测用户态进程死锁 | 检测调度器阻塞 | 检测关中断锁死
| | |
v v v
重启服务 打印堆栈/panic 打印寄存器/panic
覆盖范围逐渐缩小,但可检测到的故障级别逐渐加深
+--------------------------------------------------------------------------+
| 用户态故障 | 调度器阻塞 | 内核软锁死 | 关中断锁死 | 完全挂死(NMI可检)|
+--------------------------------------------------------------------------+
^ ^ ^ ^ ^
| | | | |
systemd softlockup softlockup hardlockup IPMI WDT
watchdog (hrtimer) (hrtimer) (NMI) (BMC)
RAS(Reliability, Availability, Serviceability,可靠性、可用性、服务性)是服务器和工业系统的关键指标。Watchdog 在 RAS 中扮演核心角色。
可靠性(Reliability):系统在规定时间内无故障运行的概率
- Watchdog 通过自动复位降低 MTTR(平均修复时间)
- 硬件看门狗确保即使 OS 死机也能自动恢复
可用性(Availability):系统处于可用状态的时间比例
- Watchdog + kdump 实现快速故障恢复
- 预超时机制在复位前采集诊断信息,加速根因分析
服务性(Serviceability):系统可被诊断和维修的能力
- bootstatus 记录上次复位原因
- pretimeout + panic + kdump 提供完整的崩溃现场
服务器 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在 RAS 场景中,看门狗超时时间的设置需要权衡多个因素:
超时时间设计矩阵:
太短 合适 太长
------ ------ ------
误报率: 高(正常慢操作触发) 低 低
恢复时间:快 合理 慢
kdump: 可能不完整 充足 充足
磁盘I/O: 可能未完成 通常完成 肯定完成
推荐值:
软看门狗(softdog): 60-120 秒
硬件看门狗(TCO): 30-60 秒
IPMI 看门狗: 120-300 秒
预超时(pretimeout): 10-30 秒(给 kdump 足够时间)
panic_wdt_timeout: 255 秒(默认,满足大多数 kdump 场景)
看门狗复位后,驱动需要在 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从看门狗超时到故障记录的完整链路:
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 寄存器、内存状态
每个 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)
+------------------+ +-------------------+
| 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,防止死锁。
watchdog_timer_fn() 是 hrtimer 回调,运行在 HRTIMER_MODE_REL_PINNED_HARD 模式下(硬实时上下文),必须避免睡眠:
watchdog_touch_ts、watchdog_report_ts:per-CPU 变量,单 CPU 写,多 CPU 读hrtimer_interrupts:atomic_t类型,保证跨 CPU 原子性soft_lockup_nmi_warn:unsigned 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 已在处理,跳过
}看门狗与系统休眠的交互分三种情况(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;
}
}不同看门狗硬件在休眠时的行为不同:
看门狗类型 S3(挂起到内存) S4(挂起到磁盘)
----------- ---------------- ----------------
softdog hrtimer 停止,复位 hrtimer 停止,复位
Intel TCO 平台固件接管 平台固件接管
SP805 需要驱动停止计时 需要驱动停止计时
IPMI BMC 独立运行,可能复位 BMC 独立运行,可能复位
对于需要在 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;
}文件:include/uapi/linux/watchdog.h
命令 编号 方向 参数类型 说明
---- ---- ---- -------- ----
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)。
// include/uapi/linux/watchdog.h 第 51-52 行
#define WDIOS_DISABLECARD 0x0001 /* 关闭看门狗 */
#define WDIOS_ENABLECARD 0x0002 /* 开启看门狗 */
#define WDIOS_TEMPPANIC 0x0004 /* 温度过高时 panic */#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); // 此后看门狗停止文件: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() 后
# 启用 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使用 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# 设备看门狗框架
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 属性暴露
# 生产环境建议开启(便于监控)
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 模拟预超时
# 适用于无硬件预超时支持的驱动
# 软锁死检测
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 误判)
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 看门狗(服务器必备)
现象:close() 返回 EBUSY,进程退出后系统在超时后复位
原因一:nowayout 已设置
cat /sys/class/watchdog/watchdog0/nowayout
# 1 = nowayout 已启用,无法停止原因二:未写入魔幻字符 'V'
# 正确停止看门狗的方式:
echo 'V' > /dev/watchdog0
# 或在 C 代码中:
write(fd, "V", 1);
close(fd);现象:设置 60 秒,但实际超时为 37.8 秒
原因:硬件限制。例如 iTCO v1 最大 37.8 秒,驱动会自动将超时截断为硬件支持的最大值,并通过 WDIOC_GETTIMEOUT 返回实际值。
解决方案:
# 先获取实际超时
ioctl(WDIOC_SETTIMEOUT, &timeout) # 同时返回实际值
# 或
ioctl(WDIOC_GETTIMEOUT, &actual_timeout)若驱动设置了 max_hw_heartbeat_ms,可以设置更长的超时(框架自动喂狗)。
现象:系统日志出现 BUG: soft lockup - CPU#X stuck for 20s!,但系统工作正常
可能原因:
- 系统负载极高(CPU 忙于高优先级任务,调度器长时间未切换)
- 虚拟机被宿主机暂停(VM pause)
- 大量中断处理(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现象: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/ # 列出当前设备数量现象: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用户态
|
| 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 架构)
-
单次打开保护:
_WDOG_DEV_OPEN位确保/dev/watchdog同一时间只能有一个进程控制,防止多个守护进程竞争喂狗导致混乱。 -
魔幻字符设计:写入
'V'才允许 close 停止,是防御性设计——意外关闭文件描述符(如exec带O_CLOEXEC)不会停止看门狗。 -
NOWAYOUT 不可逆:通过
EPERM阻止撤销 nowayout,保证一旦启用就是真正的不可停止,避免攻击者通过关闭看门狗来延长攻击窗口。 -
kthread + hrtimer 自动喂狗:解决了"用户超时 > 硬件最大超时"的矛盾,同时保证了硬件看门狗在用户态接管之前不超时。
-
两层 governor 锁:
governor_lock(mutex,用于注册/注销)和pretimeout_lock(spinlock,用于回调调用),前者允许睡眠,后者保证中断上下文安全。 -
deferred registration:解决了看门狗驱动需要在 misc 就绪之前加载的鸡和蛋问题,通过
subsys_initcall_sync时机处理积压的注册请求。 -
NMI watchdog 的 PMU 消耗:硬锁死检测需要消耗一个硬件 PMU 计数器,这是功能与资源的权衡;buddy watchdog 机制为无 PMU 场景提供了无资源消耗的替代方案。
-
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 分析生成