watchdog 是什么?
看门狗(Watchdog)是一种常见的硬件或软件机制,广泛用于嵌入式系统、服务器和实时操作系统中,用于检测和恢复系统故障。它的核心作用是在系统异常(如死机或程序失控)时自动采取恢复措施(通常是重启系统),以提高系统的稳定性和可靠性。
看门狗硬件一般独立于 cpu,在 cpu 外部连接到系统中与 cpu 并行工作,不依赖 cpu 的硬件特性让它能够在 cpu 异常时触发重启动作恢复业务。
其基本工作原理如下:
- 喂狗
系统正常运行时,周期性地喂狗,重置计时器
看门狗硬件在预定时间内未接收到喂狗信号,判断系统失效 - 超时处理
看门狗检测到超时,会触发硬件重启 - 配置参数
超时时间:看门狗定时器周期
看门狗硬件可以理解为一个定时器,定时结束的效果是"狗叫",对应的硬件动作是触发整个设备重启;当周期性喂狗时,定时器周期被重置,业务正常工作。
内核 watchdog 框架浅析
用户态程序如何使用看门狗硬件?
内核示例看门狗喂狗程序
内核示例代码位置------samples/watchdog/watchdog-simple.c
jsx
// SPDX-License-Identifier: GPL-2.0
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
int main(void)
{
int fd = open("/dev/watchdog", O_WRONLY);
int ret = 0;
if (fd == -1) {
perror("watchdog");
exit(EXIT_FAILURE);
}
while (1) {
ret = write(fd, "\0", 1);
if (ret != 1) {
ret = -1;
break;
}
sleep(10);
}
close(fd);
return ret;
}
上述程序主要流程如下:
- open /dev/watchdog
- 每隔 10s write /dev/watchdog
- 写入异常则关闭 /dev/watchdog
linux 内核通过设备文件来对上层应用暴露 watchdog 硬件,应用层通过普通的文件读写及 ioctl 命令来控制 watchdog 硬件。open 文件对应开启看门狗,write 文件对应喂狗,close 文件对应关闭看门狗,应用程序通过这些普通的文件访问操作就能够使用看门狗。
仔细阅读上述代码,不难发现一个问题:它并没有设置喂狗超时,那如何保证看门狗正常工作?
一般来说 watchdog 模块支持在模块加载的时候设置模块参数配置喂狗 timeout 及是否设置 nowayout,当模块参数并未设置 timeout 时,驱动一般会在 probe 的时候填充一个默认的 timeout,不同的驱动实现这个值有所区别。
linux 内核看门狗框架对外提供了 watchdog_init_timeout 接口供看门狗驱动调用,用于设置缺省 timeout,默认设置为此函数的 timeout_parm,当此参数非法时,尝试读取设备的 timeout-sec 属性值进行设置,当上述设置都时效时则保留旧值,同时用户也可以通过 ioctl 来单独设置自己需要的 timeout。
内核看门狗框架对喂狗操作的互斥处理
/dev/watchdog 设备文件的 write 方法中有如下代码:
c
mutex_lock(&wd_data->lock);
wdd = wd_data->wdd;
if (wdd)
err = watchdog_ping(wdd);
mutex_unlock(&wd_data->lock);
此代码在获取到互斥锁只有调用 watchdog_ping 来进行喂狗保证喂狗操作互斥,同时互斥锁的加入也限定函数需要在进程上下文执行,这也意味着下层驱动实现的喂狗操作不需要再自己做互斥处理就能够正常工作。
基于 ioctl 接口的喂狗操作
除了周期性 write 到 /dev/watchdog 设备文件,内核还支持通过 ioctl 配置 WDIOC_KEEPALIVE 属性来触发喂狗操作,同时 ioctl 还提供其它看门狗属性的获取、设置功能,诸如看门狗超时的设置与获取、看门狗状态的获取等功能。
用户态程序异常退出时内核的处理
上述基于用户态的喂狗方案工作在用户态,不免存在进程异常退出的问题。试想当因为某种异常触发喂狗进程在未关闭看门狗时就提前终止,这样如果没有人继续喂狗系统将会重启,而此时业务程序可能仍旧在正常运行。
应用层接口访问的异常可以通过编码处理,但对信号机制却难以实现(用户态进程无法屏蔽所有信号),这时候就需要内核干预了。
内核会在进程退出的时候释放它占有的所有文件,对于喂狗进程来说,内核会对 /dev/watchdog 文件执行 close 操作,该操作对应的内核函数是 watchdog_release。
阅读内核最新代码确定,内核仅当 watchdog 设备收到了魔术字符(向设备文件写入了 'V')或驱动未设置 watchdog 设备的 WDIOF_MAGICCLOSE 属性时才会关闭看门狗,其它情况看门狗会继续运行。
无人喂狗时内核的处理
5.x 内核中实现了一个 watchdogd daemon 进程,此进程为内核进程,调度策略为 SCHED_FIFO 同时优先级设置为最高,它仅在如下两个场景中进行喂狗:
- 看门狗硬件无法关闭,probe 后就保持 running 状态,此时还在内核启动阶段,用户态喂狗程序未运行,watchdod 进行喂狗,当用户态喂狗程序接管时停止喂狗
- 当用户态设置了个超过看门狗硬件支持的最大超时的 timeout 时,watchdogd 介入喂狗
watchdog 与重启、关机动作的联动
重启、关机作为一个正常的设备操作也需要与 watchdog 联动,避免 watchdog 干扰到正常的重启、关机过程。
内核 watchdog 框架会在 watchdog 设备注册的时候注册一个 reboot 的 notifier 事件,在此 notifier 事件中判断设备是否设置了 STOP_ON_REBOOT 标志,对设置了此标志的设备调用看门狗驱动实现的 stop 接口关闭看门狗,保证不中断正常的系统重启、关机过程。
一般在 watchdog 驱动 probe 过程中,在调用 devm_watchdog_register_device 注册看门狗设备前,调用函数 watchdog_stop_on_reboot 来设置 STOP_ON_REBOOT 标志。需要注意的是并非所有的看门狗驱动都支持此标志。
如果对看门狗的操作仅在用户态,则只能通过时间来保证重启、关机,并没有针对性关闭看门狗的方法与时机。
watchdog 能否在触发重启前收集遗言?
有些 watchdog 支持在重启计算机前某一时间点上,设置一个预超时机制。可以通过 NMI, interrupt,或者其他机制实现,允许系统在重启前记录遗言信息。
支持 pretimeout 的 watchdog 驱动
- orion
- imx_sc
- sprd
- qcom-wdt
- softdog
- npcm
- pm8916
- ftwdt010
- imx2
仅有部分硬件支持此功能,无法形成通用性的方案。
pretimeout 工作原理
watchdog 设备支持 pretimeout 中断,用户配置 pretimeout 后,此中断开启,根据配置时间 watchdog 会在超时前触发 pretimeout 中断,内核执行 watchdog 中断,调用 pretimeout 配置的 govern,目前支持两种 gover:
-
noop
仅输出一条警告信息
-
panic
触发内核 panic
默认的 govern 由内核配置文件决定。
softdog watchdog pretimeout 实现原理
- 初始化的时候初始化一个 pretimeout hrtimer,时间按照用户配置进行设定,在 hrtimer 定时器回调函数中调用 watchdog_notify_pretimeout 触发 govern 中的 pretimeout 方法。
- 在喂狗函数中重置 pretimeout hrtimer
如何在重启后确定是看门狗触发的重启?
部分硬件支持读取看门狗的状态寄存器来获取一些特殊的状态值诸如判断系统因为看门狗重启的原因。
用户态程序可以调用如下接口获取看门狗驱动支持的属性:
c
struct watchdog_info ident;
ioctl(fd, WDIOC_GETSUPPORT, &ident);
ident 结构定义如下:
c
================ =============================================
identity a string identifying the watchdog driver
firmware_version the firmware version of the card if available
options a flags describing what the device supports
================ =============================================
实际状态可以通过 ioctl 配置 GET_STATUS 和 GET_BOOT_STATUS 属性获取并按照掩码判断,此功能依赖硬件支持。
参考链接
https://zhuanlan.zhihu.com/p/618340691
https://blog.51cto.com/u_12227/9004027
https://www.cnblogs.com/arnoldlu/p/10868124.html
https://www.kernel.org/doc/Documentation/watchdog/watchdog-kernel-api.rst
https://developer.toradex.com/software/linux-resources/linux-features/watchdog-linux/
https://barrgroup.com/blog/how-use-watchdog-timers-properly-when-multitasking