37、深入解析Linux看门狗机制:从内核到用户空间

深入解析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_SETTIMEOUT ioctl,前提是看门狗信息中设置了 WDIOF_SETTIMEOUT 标志,且驱动提供了 .set_timeout 回调。示例代码如下:
int timeout = 45;
ioctl(fd, WDIOC_SETTIMEOUT, &timeout);
printf(" The timeout was set to %d seconds\n", timeout);
  • 获取超时 :使用 WDIOC_GETTIMEOUT ioctl,示例代码如下:
int timeout;
ioctl(fd, WDIOC_GETTIMEOUT, &timeout);
printf(" The timeout is %d seconds\n", timeout);
  • 设置预超时 :使用 WDIOC_SETPRETIMEOUT ioctl,看门狗驱动应设置 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 命令 检查驱动支持、迁移旧驱动
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值