深入解析Linux看门狗机制:从内核到用户空间
1. 看门狗回调函数与基本操作
看门狗设备有多个重要的回调函数,这些函数根据设备的能力来支持不同的操作:
- ping回调 :用于向看门狗发送保持活动的ping信号,此方法可选。若未定义,看门狗将通过 .start 操作重启。
- status回调 :可选例程,返回看门狗设备的状态。若定义,其返回值将响应 WDIOC_GETBOOTSTATUS ioctl。
- set_timeout回调 :用于设置看门狗超时值(以秒为单位)。定义此回调时,应设置相应的选项标志,否则设置超时将导致 -EOPNOTSUPP 错误。
- set_pretimeout回调 :用于设置预超时。同样,定义时需设置 WDIOF_PRETIMEOUT 选项标志,否则设置预超时会出错。
- get_timeleft操作 :可选操作,返回重置前剩余的秒数。
- restart例程 :实际上是重启机器(而非看门狗设备)的例程。设置此例程时,可在向系统注册看门狗之前,调用 watchdog_set_restart_priority() 设置重启处理程序的优先级。优先级取值如下:
- 0 :最低优先级,将看门狗的重启功能作为最后手段。
- 128 :默认优先级,在无其他处理程序可用或重启足以重启整个系统时使用。
- 255 :最高优先级,优先于所有其他处理程序。
- ioctl回调 :除非必要(如处理额外或非标准的ioctl命令),否则不应实现此回调。若定义,此方法将覆盖看门狗核心的默认ioctl,除非返回 -ENOIOCTLCMD 。
2. 看门狗设备的注册与注销
看门狗框架提供了两个基本函数用于向系统注册和注销看门狗设备:
int watchdog_register_device(struct watchdog_device *wdd)
void watchdog_unregister_device(struct watchdog_device *wdd)
-
watchdog_register_device():注册成功返回零,失败返回负的错误码。 -
watchdog_unregister_device():执行相反的操作。
为避免手动注销,可使用托管版本的函数:
int devm_watchdog_register_device(struct device *dev, struct watchdog_device *wdd)
托管版本在驱动分离时会自动处理注销。
注册设备前,需确保已处理好看门狗设备的 .info 、 .ops 和与超时相关的字段,并为 watchdog_device 结构分配内存。将该结构封装在更大的每个驱动的数据结构中是一种良好的实践,例如:
struct imx2_wdt_device {
struct clk *clk;
struct regmap *regmap;
struct watchdog_device wdog;
bool ext_reset;
};
以下是一个初始化和注册看门狗设备的示例:
static int init imx2_wdt_probe(struct platform_device *pdev)
{
struct imx2_wdt_device *wdev;
struct watchdog_device *wdog;
int ret;
wdev = devm_kzalloc(&pdev->dev, sizeof(*wdev), GFP_KERNEL);
if (!wdev)
return -ENOMEM;
wdog = &wdev->wdog;
if (imx2_wdt_is_running(wdev)) {
imx2_wdt_set_timeout(wdog, wdog->timeout);
set_bit(WDOG_HW_RUNNING, &wdog->status);
}
ret = watchdog_register_device(wdog);
if (ret) {
dev_err(&pdev->dev, " cannot register watchdog device\n" );
}
return 0;
}
注销设备的示例:
static int exit imx2_wdt_remove(struct platform_device *pdev)
{
struct watchdog_device *wdog = platform_get_drvdata(pdev);
struct imx2_wdt_device *wdev = watchdog_get_drvdata(wdog);
watchdog_unregister_device(wdog);
if (imx2_wdt_is_running(wdev)) {
imx2_wdt_ping(wdog);
dev_crit(&pdev->dev, " Device removed: Expect reboot!\n" );
}
return 0;
}
3. 预超时和调节器的处理
调节器的概念在Linux内核的多个子系统中出现,如热调节器、CPUFreq调节器和看门狗调节器。它实际上是一个实现策略管理(有时以算法形式)的驱动程序,用于响应系统的某些状态或事件。
通过启用 CONFIG_WATCHDOG_PRETIMEOUT_GOV 内核配置选项,可向Linux内核添加对看门狗预超时和调节器的支持。内核中有两个看门狗调节器驱动程序:
- drivers/watchdog/pretimeout_noop.c ,唯一名称为 noop 。
- drivers/watchdog/pretimeout_panic.c ,唯一名称为 panic 。
可通过启用 CONFIG_WATCHDOG_PRETIMEOUT_DEFAULT_GOV_NOOP 或 CONFIG_WATCHDOG_PRETIMEOUT_DEFAULT_GOV_PANIC 来默认使用其中一个。
要将预超时事件传递给当前活动的看门狗调节器,可使用 watchdog_notify_pretimeout() 接口:
void watchdog_notify_pretimeout(struct watchdog_device *wdd)
当看门狗设备因预超时事件生成IRQ时,可在IRQ处理程序中调用 watchdog_notify_pretimeout() 。该接口会在内部查找看门狗调节器并调用其 .pretimeout 回调。
看门狗调节器结构如下:
struct watchdog_governor {
const char name[WATCHDOG_GOV_NAME_MAXLEN];
void (*pretimeout)(struct watchdog_device *wdd);
};
4. GPIO 基看门狗
有时,出于功率效率等原因,使用外部看门狗设备可能比使用SoC自带的看门狗更好。这种外部看门狗设备通常通过GPIO线控制,并可重置系统,通过切换连接的GPIO线来进行ping操作。
Linux内核可通过启用 CONFIG_GPIO_WATCHDOG 配置选项来处理此类设备,该选项会引入底层驱动 drivers/watchdog/gpio_wdt.c 。启用后,它会周期性地切换GPIO线(从1到0再到1)来ping连接的硬件。若硬件未定期收到ping信号,将重置系统。
支持来自设备树,相关绑定文档可在 Documentation/devicetree/bindings/watchdog/gpio-wdt.txt 中找到。以下是一个绑定示例:
watchdog: watchdog {
compatible = " linux,wdt-gpio" ;
gpios = <&gpio3 9 GPIO_ACTIVE_LOW>;
hw_algo = " toggle" ;
hw_margin_ms = <1600>;
};
-
compatible属性必须为linux,wdt-gpio。 -
gpios是控制看门狗设备的GPIO说明符。 -
hw_algo可以是toggle或level。toggle表示通过电平转换来ping外部看门狗设备,level表示应用信号电平即可。
5. 看门狗用户空间接口
在基于Linux的系统中,标准的用户空间与看门狗交互的接口是 /dev/watchdog 文件。守护进程通过该文件通知内核看门狗驱动用户空间仍然活跃。
5.1 启动和停止看门狗
- 启动 :打开
/dev/watchdog设备文件即可自动启动看门狗,示例代码如下:
int fd;
fd = open("/dev/watchdog", O_WRONLY);
if (fd == -1) {
if (errno == ENOENT)
printf(" Watchdog device not enabled.\n" );
else if (errno == EACCES)
printf(" Run watchdog as root.\n" );
else
printf(" Watchdog device open failed %s\n", strerror(errno));
exit(-1);
}
- 停止 :仅关闭设备文件不会停止看门狗,需先向文件写入魔术字符
V,然后关闭文件,示例代码如下:
const char v = 'V';
printf(" Send magic character: V\n");
ret = write(fd, &v, 1);
if (ret < 0)
printf(" Stopping watchdog ticks failed (%d)...\n", errno);
printf(" Close for stopping..\n");
close(fd);
需要注意的是,当内核的 CONFIG_WATCHDOG_NOWAY_OUT 配置选项启用时,看门狗无法停止,且看门狗驱动应设置 WDIOF_MAGIC_CLOSE 标志,否则魔术关闭功能将不起作用。
5.2 向看门狗发送保持活动的ping信号
有两种方法可以向看门狗发送ping信号:
1. 向 /dev/watchdog 写入任何字符,但建议不要写入 V 字符。
2. 使用 WDIOC_KEEPALIVE ioctl:
ioctl(fd, WDIOC_KEEPALIVE, 0);
在使用此ioctl之前,看门狗驱动应设置 WDIOF_KEEPALIVEPING 标志。
为确保系统稳定,建议每半超时时间向看门狗发送一次ping信号。
5.3 获取看门狗的能力和标识
要获取看门狗的能力和标识,可使用 WDIOC_GETSUPPORT ioctl,示例代码如下:
struct watchdog_info ident;
ioctl(fd, WDIOC_GETSUPPORT, &ident);
printf(" WDIOC_GETSUPPORT:\n");
printf("\tident.identity = %s\n", ident.identity);
printf("\tident.firmware_version = %d\n", ident.firmware_version);
printf(" WDIOC_GETSUPPORT: ident.options = 0x%x\n", ident.options);
还可进一步测试能力字段:
if (ident.options & WDIOF_KEEPALIVEPING)
printf("\tKeep alive ping reply.\n");
if (ident.options & WDIOF_SETTIMEOUT)
printf("\tCan set/get the timeout.\n");
5.4 设置和获取超时与预超时
- 设置超时 :使用
WDIOC_SETTIMEOUTioctl,前提是看门狗信息中设置了WDIOF_SETTIMEOUT标志,且驱动提供了.set_timeout回调。示例代码如下:
int timeout = 45;
ioctl(fd, WDIOC_SETTIMEOUT, &timeout);
printf(" The timeout was set to %d seconds\n", timeout);
- 获取超时 :使用
WDIOC_GETTIMEOUTioctl,示例代码如下:
int timeout;
ioctl(fd, WDIOC_GETTIMEOUT, &timeout);
printf(" The timeout is %d seconds\n", timeout);
- 设置预超时 :使用
WDIOC_SETPRETIMEOUTioctl,看门狗驱动应设置WDIOF_PRETIMEOUT标志并提供.set_pretimeout回调。示例代码如下:
pretimeout = 10;
ioctl(fd, WDIOC_SETPRETIMEOUT, &pretimeout);
若预超时值为0或大于当前超时值,将返回 -EINVAL 错误。
5.5 获取剩余时间
使用 WDIOC_GETTIMELEFT ioctl 可检查看门狗计数器在重置前剩余的时间,前提是看门狗驱动支持该功能并提供了 .get_timeleft() 回调。示例代码如下:
int timeleft;
ioctl(fd, WDIOC_GETTIMELEFT, &timeleft);
printf(" The remaining timeout is %d seconds\n", timeleft);
5.6 获取(启动/重启)状态
有两个ioctl命令可用于获取状态: WDIOC_GETSTATUS 和 WDIOC_GETBOOTSTATUS 。处理方式取决于驱动实现,有两种类型的驱动:
- 旧驱动 :通过杂项设备提供看门狗功能,不使用通用看门狗框架接口,仅支持 WDIOC_GETSTATUS ,部分可能支持两者。 WDIOC_GETSTATUS 返回设备状态寄存器的原始内容, WDIOC_GETBOOTSTATUS 解析原始内容并返回启动状态标志。
- 新驱动 :使用通用看门狗框架, WDIOC_GETBOOTSTATUS 返回 watchdog_device.bootstatus 字段的值, WDIOC_GETSTATUS 根据是否提供 .status 操作来返回相应的值。
以下是内核空间获取状态的代码示例:
static unsigned int watchdog_get_status(struct watchdog_device *wdd)
{
struct watchdog_core_data *wd_data = wdd->wd_data;
unsigned int status;
if (wdd->ops->status)
status = wdd->ops->status(wdd);
else
status = wdd->bootstatus &
(WDIOF_CARDRESET |
WDIOF_OVERHEAT |
WDIOF_FANFAULT |
WDIOF_EXTERN1 |
WDIOF_EXTERN2 |
WDIOF_POWERUNDER |
WDIOF_POWEROVER);
if (test_bit(_WDOG_ALLOW_RELEASE, &wd_data->status))
status |= WDIOF_MAGICCLOSE;
if (test_and_clear_bit(_WDOG_KEEPALIVE, &wd_data->status))
status |= WDIOF_KEEPALIVEPING;
return status;
}
在用户空间可使用以下代码获取状态:
int flags = 0;
ioctl(fd, WDIOC_GETSTATUS, &flags);
// 或 ioctl(fd, WDIOC_GETBOOTSTATUS, &flags);
总结
本文详细介绍了Linux看门狗机制,从内核层面的回调函数、设备注册与注销,到用户空间的各种操作,包括启动、停止、ping信号发送、获取设备信息和状态等。通过合理使用这些功能,可以有效保障系统的稳定性和可靠性。
流程图
graph TD;
A[启动看门狗] --> B[打开 /dev/watchdog 文件];
B --> C{是否成功打开};
C -- 是 --> D[看门狗启动];
C -- 否 --> E[根据错误码输出信息并退出];
D --> F[周期性写入文件或使用 ioctl 发送 ping 信号];
F --> G{是否在超时前发送 ping 信号};
G -- 是 --> F;
G -- 否 --> H[系统重启];
D --> I[停止看门狗];
I --> J[写入魔术字符 V];
J --> K{写入是否成功};
K -- 是 --> L[关闭文件];
K -- 否 --> M[输出错误信息];
L --> N[看门狗停止];
表格
| 操作 | 函数/命令 | 说明 |
|---|---|---|
| 注册设备 | watchdog_register_device() | 向系统注册看门狗设备 |
| 注销设备 | watchdog_unregister_device() | 从系统注销看门狗设备 |
| 托管注册设备 | devm_watchdog_register_device() | 自动处理注销的注册方式 |
| 发送预超时通知 | watchdog_notify_pretimeout() | 将预超时事件传递给调节器 |
| 启动看门狗 | 打开 /dev/watchdog 文件 | 自动启动看门狗 |
| 停止看门狗 | 写入 V 并关闭文件 | 正确停止看门狗的方法 |
| 发送 ping 信号 | 写入文件或 WDIOC_KEEPALIVE ioctl | 保持看门狗活动 |
| 获取设备信息 | WDIOC_GETSUPPORT ioctl | 获取看门狗的能力和标识 |
| 设置超时 | WDIOC_SETTIMEOUT ioctl | 设置看门狗超时时间 |
| 获取超时 | WDIOC_GETTIMEOUT ioctl | 获取当前看门狗超时时间 |
| 设置预超时 | WDIOC_SETPRETIMEOUT ioctl | 设置看门狗预超时时间 |
| 获取剩余时间 | WDIOC_GETTIMELEFT ioctl | 获取看门狗重置前剩余时间 |
| 获取状态 | WDIOC_GETSTATUS 和 WDIOC_GETBOOTSTATUS ioctl | 获取看门狗的启动/重启状态 |
深入解析Linux看门狗机制:从内核到用户空间
6. 不同操作的详细对比与总结
为了更清晰地理解看门狗的各种操作,下面对关键操作进行详细对比总结:
| 操作类型 | 操作方式 | 必要条件 | 示例代码 |
| ---- | ---- | ---- | ---- |
| 注册设备 | watchdog_register_device(struct watchdog_device *wdd) | 处理好 .info 、 .ops 和超时相关字段,分配 watchdog_device 内存 |
struct imx2_wdt_device *wdev;
struct watchdog_device *wdog;
int ret;
wdev = devm_kzalloc(&pdev->dev, sizeof(*wdev), GFP_KERNEL);
wdog = &wdev->wdog;
ret = watchdog_register_device(wdog);
| 注销设备 | watchdog_unregister_device(struct watchdog_device *wdd) | 已注册的看门狗设备 |
struct watchdog_device *wdog = platform_get_drvdata(pdev);
struct imx2_wdt_device *wdev = watchdog_get_drvdata(wdog);
watchdog_unregister_device(wdog);
| 发送预超时通知 | watchdog_notify_pretimeout(struct watchdog_device *wdd) | 启用 CONFIG_WATCHDOG_PRETIMEOUT_GOV 内核配置选项 |
void watchdog_notify_pretimeout(struct watchdog_device *wdd)
| 启动看门狗 | 打开 /dev/watchdog 文件 | 设备存在且有访问权限 |
int fd;
fd = open("/dev/watchdog", O_WRONLY);
if (fd == -1) {
if (errno == ENOENT)
printf(" Watchdog device not enabled.\n" );
else if (errno == EACCES)
printf(" Run watchdog as root.\n" );
else
printf(" Watchdog device open failed %s\n", strerror(errno));
exit(-1);
}
| 停止看门狗 | 写入魔术字符 V 并关闭文件 | 未启用 CONFIG_WATCHDOG_NOWAY_OUT ,驱动设置 WDIOF_MAGIC_CLOSE 标志 |
const char v = 'V';
ret = write(fd, &v, 1);
close(fd);
| 发送 ping 信号 | 写入文件或使用 WDIOC_KEEPALIVE ioctl | 驱动设置 WDIOF_KEEPALIVEPING 标志 |
// 写入文件
write(fd, "x", 1);
// 使用 ioctl
ioctl(fd, WDIOC_KEEPALIVE, 0);
| 获取设备信息 | WDIOC_GETSUPPORT ioctl | 无 |
struct watchdog_info ident;
ioctl(fd, WDIOC_GETSUPPORT, &ident);
| 设置超时 | WDIOC_SETTIMEOUT ioctl | 看门狗信息设置 WDIOF_SETTIMEOUT 标志,驱动提供 .set_timeout 回调 |
int timeout = 45;
ioctl(fd, WDIOC_SETTIMEOUT, &timeout);
| 获取超时 | WDIOC_GETTIMEOUT ioctl | 无 |
int timeout;
ioctl(fd, WDIOC_GETTIMEOUT, &timeout);
| 设置预超时 | WDIOC_SETPRETIMEOUT ioctl | 驱动设置 WDIOF_PRETIMEOUT 标志,提供 .set_pretimeout 回调 |
pretimeout = 10;
ioctl(fd, WDIOC_SETPRETIMEOUT, &pretimeout);
| 获取剩余时间 | WDIOC_GETTIMELEFT ioctl | 驱动提供 .get_timeleft() 回调 |
int timeleft;
ioctl(fd, WDIOC_GETTIMELEFT, &timeleft);
| 获取状态 | WDIOC_GETSTATUS 和 WDIOC_GETBOOTSTATUS ioctl | 无 |
int flags = 0;
ioctl(fd, WDIOC_GETSTATUS, &flags);
// 或 ioctl(fd, WDIOC_GETBOOTSTATUS, &flags);
7. 常见问题及解决方法
在使用Linux看门狗机制时,可能会遇到一些常见问题,以下是这些问题及相应的解决方法:
- 问题1:看门狗无法启动
- 原因 :设备未启用、权限不足或驱动加载失败。
- 解决方法 :检查 CONFIG_GPIO_WATCHDOG 等相关配置选项是否启用,确保以root权限运行,检查驱动文件是否存在且正确加载。
- 问题2:无法停止看门狗
- 原因 :启用了 CONFIG_WATCHDOG_NOWAY_OUT 内核配置选项,或驱动未设置 WDIOF_MAGIC_CLOSE 标志。
- 解决方法 :若不需要此功能,可禁用 CONFIG_WATCHDOG_NOWAY_OUT ;检查驱动代码,确保设置了 WDIOF_MAGIC_CLOSE 标志。
- 问题3:设置超时或预超时失败
- 原因 :看门狗信息未设置相应标志,或驱动未提供相应回调。
- 解决方法 :检查看门狗信息结构,确保设置了 WDIOF_SETTIMEOUT 或 WDIOF_PRETIMEOUT 标志;检查驱动代码,确保提供了 .set_timeout 或 .set_pretimeout 回调。
- 问题4:获取设备信息或状态失败
- 原因 :驱动不支持相应的ioctl命令。
- 解决方法 :检查驱动代码,确保支持所需的ioctl命令;若为旧驱动,考虑迁移到新的通用看门狗框架。
8. 实际应用案例
以下是一个简单的实际应用案例,展示如何在一个简单的守护进程中使用看门狗:
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#define WATCHDOG_DEVICE "/dev/watchdog"
int main() {
int fd;
fd = open(WATCHDOG_DEVICE, O_WRONLY);
if (fd == -1) {
if (errno == ENOENT)
printf(" Watchdog device not enabled.\n" );
else if (errno == EACCES)
printf(" Run watchdog as root.\n" );
else
printf(" Watchdog device open failed %s\n", strerror(errno));
return -1;
}
// 周期性发送 ping 信号
while (1) {
if (write(fd, "x", 1) != 1) {
printf(" Send ping signal failed: %s\n", strerror(errno));
break;
}
sleep(10); // 假设超时时间较长,每 10 秒发送一次 ping 信号
}
// 停止看门狗
const char v = 'V';
if (write(fd, &v, 1) != 1) {
printf(" Stopping watchdog ticks failed (%d)...\n", errno);
}
close(fd);
return 0;
}
在这个案例中,程序首先打开看门狗设备文件,然后周期性地向文件写入字符以发送ping信号,最后在合适的时候停止看门狗。
9. 总结与展望
通过对Linux看门狗机制的深入解析,我们了解了从内核到用户空间的各个方面,包括回调函数、设备注册与注销、预超时和调节器处理、GPIO基看门狗以及用户空间的各种操作。合理使用看门狗机制可以有效保障系统的稳定性和可靠性,防止系统因软件故障而长时间无响应。
未来,随着Linux系统的不断发展,看门狗机制可能会进一步优化和扩展。例如,可能会引入更智能的调节器算法,根据系统的实时状态动态调整看门狗的行为;或者提供更友好的用户空间接口,方便开发者进行配置和管理。同时,对于不同类型的硬件平台,看门狗机制也可能会有更针对性的优化,以满足不同场景的需求。
流程图
graph TD;
A[开始] --> B[初始化变量];
B --> C[打开看门狗设备文件];
C --> D{是否成功打开};
D -- 是 --> E[进入循环发送 ping 信号];
D -- 否 --> F[输出错误信息并退出];
E --> G{是否需要停止看门狗};
G -- 否 --> H[发送 ping 信号];
H --> I[休眠一段时间];
I --> E;
G -- 是 --> J[写入魔术字符 V];
J --> K{写入是否成功};
K -- 是 --> L[关闭文件];
K -- 否 --> M[输出错误信息];
L --> N[结束];
M --> N;
表格
| 问题 | 原因 | 解决方法 |
|---|---|---|
| 看门狗无法启动 | 设备未启用、权限不足、驱动加载失败 | 检查配置选项、以root权限运行、检查驱动文件 |
| 无法停止看门狗 | 启用 CONFIG_WATCHDOG_NOWAY_OUT 、驱动未设置标志 | 禁用选项、检查驱动标志 |
| 设置超时或预超时失败 | 未设置标志、驱动未提供回调 | 检查标志、检查驱动回调 |
| 获取设备信息或状态失败 | 驱动不支持 ioctl 命令 | 检查驱动支持、迁移旧驱动 |
超级会员免费看
50

被折叠的 条评论
为什么被折叠?



