看门狗驱动与应用:硬件看门狗喂狗、死机自动重启机制实现
看门狗驱动与应用:硬件看门狗喂狗、死机自动重启机制实现
你的工控板死机了,你人还在被窝里——这就是没有看门狗的代价。
一、看门狗到底是什么
看门狗(Watchdog Timer,WDT)本质上是一个硬件倒计时器。启动后它开始从预设值递减,减到零就触发一个硬件复位信号(RESET),让整个系统重新来过。
形象地说:你养了一条狗,每隔一段时间必须喂它一次(喂狗),否则它就咬你一口(系统复位)。如果你程序跑飞了、死锁了、内核panic了——没人喂狗了,狗就咬,系统重启。
这玩意在工控领域是保命的存在。无人值守的设备跑在野外,死机了没人按重启键,没有看门狗就只能等维护人员千里迢迢赶过去。有了看门狗,系统自己就能从死机中恢复。
1.1 看门狗的分类
| 类型 | 说明 | 特点 |
|---|---|---|
| 硬件看门狗 | SoC内部或外部芯片独立计时 | 不受CPU/OS影响,最可靠 |
| 软件看门狗 | 内核软定时器实现 | 依赖内核调度,内核挂了就没用 |
工控场景必须用硬件看门狗,软件看门狗只能作为辅助。
二、Linux内核看门狗子系统
Linux内核对看门狗做了标准化封装,统一通过/dev/watchdog字符设备节点暴露给用户空间。
2.1 设备节点
# 查看看门狗设备ls-l/dev/watchdog*# crw------- 1 root root 10, 130 Jan 1 00:00 /dev/watchdog# crw------- 1 root root 245, 0 Jan 1 00:00 /dev/watchdog0/dev/watchdog是默认别名,/dev/watchdog0是实际设备。打开设备的那一刻,看门狗就被激活了——注意,不是等你写数据才启动,而是open就启动。
2.2 关键ioctl命令
#include<linux/watchdog.h>WDIOC_GETSUPPORT// 获取看门狗支持的功能WDIOC_GETTIMEOUT// 获取当前超时时间(秒)WDIOC_SETTIMEOUT// 设置超时时间WDIOC_KEEPALIVE// 喂狗(等价于write)WDIOC_GETTIMELEFT// 查看剩余时间三、设备树DTS配置
以瑞芯微RK3568为例,看门狗节点在设备树中配置:
/* arch/arm64/boot/dts/rockchip/rk3568.dtsi */ watchdog@fe600000 { compatible = "rockchip,stop-wdt"; reg = <0x0 0xfe600000 0x0 0x1000>; clocks = <&cru TCLK_WDT_NS>; interrupts = <GIC_SPI 149 IRQ_TYPE_LEVEL_HIGH>; status = "okay"; }; /* 你的板级DTS中可以覆盖配置 */ &watchdog { status = "okay"; /* timeout-sec 可以在驱动中读取设置默认超时 */ };不同平台的看门狗节点名称和地址不同,但套路一样:指定compatible、reg、clocks,然后status设为okay。内核启动后,驱动注册看门狗设备,/dev/watchdog0就出现了。
四、用户层喂狗实现
4.1 最朴素的喂狗方式
#include<stdio.h>#include<stdlib.h>#include<fcntl.h>#include<unistd.h>#include<sys/ioctl.h>#include<linux/watchdog.h>intmain(void){intfd=open("/dev/watchdog",O_WRONLY);if(fd<0){perror("open watchdog failed");return-1;}/* 设置超时时间为10秒 */inttimeout=10;ioctl(fd,WDIOC_SETTIMEOUT,&timeout);printf("Watchdog timeout set to %d seconds\n",timeout);/* 主循环:每3秒喂一次狗 */while(1){write(fd,"1",1);/* 写入任意数据即可刷新定时器 */// ioctl(fd, WDIOC_KEEPALIVE, NULL); /* 等价写法 */printf("Fed the dog...\n");sleep(3);}close(fd);return0;}write和ioctl(WDIOC_KEEPALIVE)效果一样,都是刷新倒计时。喂狗间隔必须小于超时时间,建议间隔不超过超时的1/3。
4.2 线程化喂狗方案
实际工控程序中,主线程要干正事(处理业务逻辑),不能被喂狗阻塞。标准做法是开一个独立线程专门喂狗:
#include<stdio.h>#include<stdlib.h>#include<string.h>#include<fcntl.h>#include<unistd.h>#include<pthread.h>#include<sys/ioctl.h>#include<linux/watchdog.h>staticintwdt_fd=-1;staticvolatileintkeep_running=1;/* 喂狗线程 */void*watchdog_feeder(void*arg){intfeed_interval=*(int*)arg;while(keep_running){if(wdt_fd>=0){ioctl(wdt_fd,WDIOC_KEEPALIVE,NULL);}sleep(feed_interval);}returnNULL;}intmain(void){wdt_fd=open("/dev/watchdog",O_WRONLY);if(wdt_fd<0){perror("open watchdog failed");return-1;}/* 超时15秒,每5秒喂一次 */inttimeout=15;ioctl(wdt_fd,WDIOC_SETTIMEOUT,&timeout);intfeed_interval=5;pthread_tfeeder_tid;pthread_create(&feeder_tid,NULL,watchdog_feeder,&feed_interval);/* ===== 主业务逻辑 ===== */printf("Main business logic started...\n");while(keep_running){/* 模拟业务处理 */do_business_work();sleep(1);}/* 优雅退出:关闭看门狗 */keep_running=0;pthread_join(feeder_tid,NULL);/* Magic Close:写入'V'字符,安全关闭看门狗 */write(wdt_fd,"V",1);close(wdt_fd);return0;}五、Magic Close机制
这里有个关键细节:正常close设备文件不会关闭看门狗。看门狗的设计哲学是"宁可误杀,不可漏杀"。如果你只是close了fd,看门狗照样在跑,超时后照样复位。
要真正关闭看门狗,必须在close之前写入大写字母'V':
write(fd,"V",1);/* Magic Close */close(fd);这个'V'代表"V"ictory(也有说是Verbose),内核看门狗驱动收到这个字符后,会标记此设备为"被安全关闭",然后停止计时器。
什么时候需要Magic Close?
- 正常关机/重启前(否则关机过程中看门狗超时会强制复位,可能导致文件系统损坏)
- 应用正常退出时
- 调试阶段需要临时关闭看门狗
六、systemd集成看门狗
如果你的系统用systemd管理服务,systemd自带看门狗集成,不需要自己写喂狗代码。
6.1 服务单元配置
# /etc/systemd/system/myapp.service [Unit] Description=My Industrial Control App After=network.target [Service] Type=notify ExecStart=/usr/bin/myapp WatchdogSec=15s Restart=always RestartSec=5s [Install] WantedBy=multi-user.targetWatchdogSec=15s表示systemd会给这个服务设一个15秒的看门狗。服务必须在15秒内调用sd_notify告诉systemd"我还活着",否则systemd认为服务卡死,强制重启。
6.2 应用侧通知代码
#include<systemd/sd-daemon.h>intmain(void){/* 告诉systemd服务已启动 */sd_notify(0,"READY=1");while(1){do_business_work();/* 定期通知systemd:我还活着 */sd_notify(0,"WATCHDOG=1");sleep(5);}return0;}sd_notify底层通过unix socket发消息给systemd,比直接操作/dev/watchdog多了一层保护:如果进程完全卡死(连sd_notify都调不了),systemd会重启服务;如果整个系统卡死(systemd自身都跑不了),硬件看门狗兜底复位。
七、超时时间怎么选
| 场景 | 建议超时 | 原因 |
|---|---|---|
| 高实时采集 | 1~3s | 故障快速恢复,数据丢失少 |
| 通用工控 | 10~15s | 给系统足够重启时间,兼顾响应 |
| 嵌入式低功耗 | 30~60s | 唤醒周期长,避免频繁复位 |
选超时时间的核心原则:大于系统正常最忙时刻的喂狗间隔,小于可接受的故障恢复时间。
八、常见踩坑
坑1:喂狗线程被阻塞
最经典的问题。喂狗线程里调了一个会阻塞的函数(比如某个锁、某个慢速IO),结果喂狗间隔超时,系统被误复位。
解决:喂狗线程只做喂狗这一件事,不获取任何锁,不调用任何可能阻塞的函数。
坑2:关机前没关看门狗
正常执行reboot命令时,系统需要几秒到几十秒来同步文件系统、停止服务。如果看门狗超时时间只有5秒,关机过程中就触发复位了——轻则文件系统不一致,重则rootfs损坏。
解决:在系统关机脚本中(或systemd的ExecStop中)先做Magic Close,再执行关机。
# /etc/rc6.d/ 关机脚本中加入echo'V'>/dev/watchdog坑3:多进程同时打开看门狗
多个进程都open了/dev/watchdog,一个进程退出时做了Magic Close,把看门狗关了,另一个进程还以为自己在喂狗。
解决:只有一个进程负责喂狗,通常是主控程序或专门的看门狗守护进程。
看门狗是工控系统最后一道防线。设计时遵循"简单、独立、不可阻塞"原则,关键时刻它能帮你把设备从死亡线上拉回来。
