大家好,我是王鸽,从事嵌入式久了各种问题都遇到过,比如概率设备重启,死机等,尤其是极小的概率性问题,这种需要不断修改测试验证,如果没有找到问题那是相当头疼的。Linux系统中如果重启是看门狗引起,怎么知道?其实在 Linux 系统中,要判断系统复位是否由看门狗触发,核心思路是利用硬件 / 内核的 “复位原因记录机制” —— 看门狗复位后会留下专属 “痕迹”,我们只需读取这些痕迹即可定位原因。以下是从「硬件层→内核层→用户层」的完整排查方法,覆盖嵌入式 / 工业场景的主流方案:
一、核心原理:看门狗复位会留下 “复位原因标识”
几乎所有带硬件看门狗的 SOC(如 STM32、IMX6/8、RK3399、AM335x),都会在复位状态寄存器(Reset Status Register, RSR) 中记录复位原因:
- 看门狗复位:对应寄存器的
WDOG_RST位会置 1; - 其他复位:如上电复位(POR)、手动复位(RST 按键)、软件复位(
reboot)、掉电复位,对应不同的寄存器位。
Linux 的核心排查逻辑就是:读取该寄存器值 → 解析复位原因 → 记录 / 展示给用户。
二、方法 1:直接读取硬件复位状态寄存器(最准确)
这是嵌入式场景的 “终极方案”,直接从 SOC 硬件寄存器读取复位原因,不受软件层干扰。
步骤 1:找到 SOC 的复位状态寄存器信息
需查阅 SOC 的数据手册,找到 “复位原因寄存器” 的物理地址和位定义:
- 复位状态寄存器
SRC_SRSR1(物理地址0x020C4004),BIT2表示看门狗复位; - 复位状态寄存器
RCC_RSR(物理地址0x50000000),BIT3表示独立看门狗(IWDG)复位,BIT4表示窗口看门狗(WWDG)复位; - 复位状态寄存器
GRF_SOC_CON0(物理地址0xFF770000),BIT18表示看门狗复位。
步骤 2:在 Linux 中读取寄存器(2 种方式)
方式 A:驱动 / 内核模块读取(推荐,可自动化记录)
#include<linux/module.h>#include<linux/io.h>// 以IMX6ULL为例,替换为你的SOC寄存器地址和位定义#define RST_STATUS_REG 0x020C4004 // 复位状态寄存器物理地址#define WDOG_RST_BIT (1 << 2) // 看门狗复位标识位static void __iomem *rst_reg;staticint __init wdt_rst_check_init(void){ u32 rst_status; // 1. 映射物理寄存器到内核虚拟地址 rst_reg = ioremap(RST_STATUS_REG, 4); if (!rst_reg) { pr_err("ioremap failed\n"); return -ENOMEM; } // 2. 读取复位状态寄存器 rst_status = readl(rst_reg); pr_info("reset status register value: 0x%08x\n", rst_status); // 3. 解析是否为看门狗复位 if (rst_status & WDOG_RST_BIT) { pr_crit("SYSTEM RESET BY WATCHDOG!\n"); // 可选:将复位原因写入文件系统,供用户态读取 // echo "watchdog_reset" > /tmp/reset_reason } else { pr_info("reset reason: not watchdog (0x%08x)\n", rst_status); } // 4. 清除复位状态寄存器(部分SOC需手动清除,否则下次读取仍为旧值) writel(rst_status, rst_reg); // 写原值清除(不同SOC清除方式不同) iounmap(rst_reg); return 0;}staticvoid __exit wdt_rst_check_exit(void){ pr_info("module exit\n");}module_init(wdt_rst_check_init);module_exit(wdt_rst_check_exit);MODULE_LICENSE("GPL");
方式 B:用户态直接读取(调试用)
通过devmem命令读取物理寄存器(需 root,且内核开启CONFIG_DEVMEM):
# 以IMX6ULL为例,读取复位状态寄存器devmem 0x020C4004 32# 输出示例:0x00000004 → 二进制第2位置1 → 看门狗复位# 输出0x00000001 → 上电复位(POR)
这个需要看数据手册了。
三、方法 2:利用 Linux 内核日志 / 设备树(简化方案)
1. 内核启动日志(dmesg)
多数 SOC 的 Linux 内核会在启动时解析复位原因,并打印到 dmesg 中:
# 查看内核启动日志中的复位原因dmesg | grep -i "reset\|watchdog\|wdt"
# 典型输出(看门狗复位):# [ 0.000000] Reset cause: WDOG (Watchdog)# [ 0.000000] imx6ull-pinctrl 20e0000.iomuxc: reset cause: watchdog reset# 非看门狗复位输出:# [ 0.000000] Reset cause: POR (Power On Reset)# [ 0.000000] Reset cause: RST_BTN (External Reset)
2. 设备树适配(自动解析)
若 SOC 的驱动已适配复位原因解析,可直接读取设备树导出的节点:
# 查看复位原因(部分系统支持)cat /sys/devices/platform/soc/reset-cause# 输出:watchdog / por / reset_button 等
四、方法 3:软件层面标记(兜底方案)
若硬件寄存器无法读取(如无手册 / 驱动不支持),可通过 “文件标记” 间接判断:
1. 原理
- 系统正常启动时,创建一个 “正常启动标记文件”(如
/tmp/normal_boot); - 看门狗复位是 “异常复位”,系统重启后不会执行正常启动流程,该文件不会被创建;
2. 实现(添加到开机自启脚本/etc/rc.local)
#!/bin/sh# 1. 检查是否为看门狗复位(无正常启动标记)if [ ! -f /tmp/normal_boot ]; then echo "WATCHDOG RESET DETECTED! $(date)" >> /var/log/wdt_reset.logelse rm -f /tmp/normal_boot # 清除旧标记fi
# 2. 创建正常启动标记(系统启动完成后执行)
# 3. 启动喂狗线程(可选)
五、关键注意点(避坑)
- 复位状态寄存器需及时清除多数 SOC 的复位状态寄存器是 “只读且置 1 后保持”,若不清除,下次读取会误判复位原因;
- 软件复位不会触发看门狗位通过
reboot/init 6重启系统,复位寄存器中看门狗位不会置 1,可区分 “主动重启” 和 “看门狗复位”; - 硬件看门狗 vs 软件看门狗软件看门狗复位本质是内核触发的软件重启,部分 SOC 不会标记为 “看门狗复位”,需结合日志判断;
- 多看门狗场景若系统有多个看门狗(如独立 WDT 芯片 + SOC 内置 WDT),需确认每个看门狗对应的复位寄存器位;
- 日志持久化将复位原因写入持久化存储(如 SD 卡 / Flash),避免重启后日志丢失(嵌入式系统
/tmp是内存文件系统,重启即失)。
总结
核心排查流程:
- 优先看
dmesg | grep reset → 快速定位; - 若日志无信息,用
devmem读取复位寄存器 → 精准判断;
实战举一个例子,针对RK3399平台,如果出现出现看门狗复位情况。
一、RK3399 复位原因核心硬件信息(必看)
RK3399 的复位原因由GRF(General Register Files)寄存器 记录,核心寄存器和位定义如下:
| | | |
|---|
| 0xFF770000 | | |
| | | |
| | | |
| | | reboot |
| 0xFF770070 | | |
注:RK3399 有两个看门狗(WDT0/WDT1),默认使用 WDT1(地址0xFF1A0000),复位后都会置位 GRF_SOC_CON0 的 BIT18。
二、方法 1:直接读取硬件寄存器(精准判断,推荐生产环境)
1. 调试阶段:用devmem命令快速读取(root 权限)
# 1. 读取复位原因寄存器(GRF_SOC_CON0)devmem 0xFF770000 32# 2. 解析结果(重点看BIT18是否置1):# 输出 0x00040000 → 二进制BIT18=1 → 看门狗复位# 输出 0x00000001 → BIT0=1 → 上电复位# 输出 0x00000002 → BIT1=1 → 外部按键复位# 输出 0x00000004 → BIT2=1 → 软件复位(reboot)# 3. (可选)读取看门狗使能状态(验证看门狗是否开启)devmem 0xFF770070 32# 输出 BIT0=1 → 看门狗已使能;BIT0=0 → 未使能
2. 代码层面:内核模块 / 应用层读取(自动化记录)
方式 A:内核模块(开机自动解析,推荐)
#include<linux/module.h>#include<linux/io.h>#include<linux/kernel.h>// RK3399 复位原因寄存器定义#define RK3399_GRF_BASE 0xFF770000#define RK3399_GRF_SOC_CON0 0x0000 // GRF_SOC_CON0 偏移#define RK3399_WDT_RST_BIT (1 << 18) // 看门狗复位位#define RK3399_POR_RST_BIT (1 << 0) // 上电复位位#define RK3399_EXT_RST_BIT (1 << 1) // 外部复位位#define RK3399_SOFT_RST_BIT (1 << 2) // 软件复位位static void __iomem *grf_base;staticint __init rk3399_reset_check_init(void){ u32 rst_status; // 1. 映射GRF寄存器到内核虚拟地址 grf_base = ioremap(RK3399_GRF_BASE, 0x1000); // 映射4KB空间 if (IS_ERR(grf_base)) { pr_err("ioremap GRF failed: %ld\n", PTR_ERR(grf_base)); return PTR_ERR(grf_base); } // 2. 读取复位状态寄存器 rst_status = readl(grf_base + RK3399_GRF_SOC_CON0); pr_info("RK3399 reset status: 0x%08x\n", rst_status); // 3. 解析复位原因 if (rst_status & RK3399_WDT_RST_BIT) { pr_crit("=== RK3399 RESET BY WATCHDOG ===\n"); // 可选:写入持久化存储(如/var/log/wdt_reset.log) // 注意:嵌入式系统需确保/var是持久化分区(非tmpfs) const char *log = "WDT_RESET " __DATE__ " " __TIME__ "\n"; struct file *fp = filp_open("/var/log/wdt_reset.log", O_WRONLY|O_APPEND|O_CREAT, 0644); if (fp && !IS_ERR(fp)) { kernel_write(fp, log, strlen(log), &fp->f_pos); filp_close(fp, NULL); } } else if (rst_status & RK3399_POR_RST_BIT) { pr_info("RK3399 reset reason: Power On Reset (POR)\n"); } else if (rst_status & RK3399_EXT_RST_BIT) { pr_info("RK3399 reset reason: External Reset Button\n"); } else if (rst_status & RK3399_SOFT_RST_BIT) { pr_info("RK3399 reset reason: Software Reset (reboot)\n"); } else { pr_info("RK3399 reset reason: Unknown (0x%08x)\n", rst_status); } // 4. 清除复位状态(RK3399需写掩码清除,避免下次误判) writel(rst_status, grf_base + RK3399_GRF_SOC_CON0); // 写原值清除 iounmap(grf_base); return 0;}staticvoid __exit rk3399_reset_check_exit(void){ pr_info("RK3399 reset check module exit\n");}module_init(rk3399_reset_check_init);module_exit(rk3399_reset_check_exit);MODULE_LICENSE("GPL");MODULE_DESCRIPTION("RK3399 Watchdog Reset Check Module");
方式 B:用户态 C 代码(调试用)
#include<stdio.h>#include<fcntl.h>#include<sys/mman.h>#include<unistd.h>#define GRF_BASE 0xFF770000#define GRF_SIZE 0x1000#define GRF_SOC_CON0 0x0000#define WDT_RST_BIT (1 << 18)intmain(){ int fd; void *grf_map; u32 *rst_reg; u32 rst_status; // 1. 打开/dev/mem(需root,内核开启CONFIG_DEVMEM) fd = open("/dev/mem", O_RDWR | O_SYNC); if (fd < 0) { perror("open /dev/mem failed"); return -1; } // 2. 映射GRF寄存器 grf_map = mmap(NULL, GRF_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, GRF_BASE); if (grf_map == MAP_FAILED) { perror("mmap failed"); close(fd); return -1; } // 3. 读取复位状态 rst_reg = (u32 *)(grf_map + GRF_SOC_CON0); rst_status = *rst_reg; printf("RK3399 reset status: 0x%08x\n", rst_status); // 4. 解析 if (rst_status & WDT_RST_BIT) { printf("=== RK3399 reset by WATCHDOG ===\n"); } else { printf("RK3399 reset not by watchdog\n"); } // 5. 释放资源 munmap(grf_map, GRF_SIZE); close(fd); return 0;}
编译运行:
gcc rk3399_wdt_check.c -o rk3399_wdt_check sudo ./rk3399_wdt_check
三、方法 2:读取 RK3399 内核启动日志(最简方案)
RK3399 的 Linux 内核(4.4+/5.10+)已内置复位原因解析,直接查看dmesg即可:
# 过滤复位相关日志dmesg | grep -i "reset\|wdt\|watchdog"# 看门狗复位典型输出:# [ 0.000000] Rockchip reset reason: watchdog reset# [ 0.000000] rk3399-grf ff770000.grf: reset reason: 0x40000 (WDT)# 非看门狗复位输出:# [ 0.000000] Rockchip reset reason: power on reset# [ 0.000000] Rockchip reset reason: external reset
四、方法 3:RK3399 设备树适配(自动导出复位原因)
若你的 RK3399 设备树已启用rockchip-reset-reason驱动,可直接读取系统节点:
# 查看复位原因(部分定制内核支持)cat /sys/devices/platform/ff770000.grf/reset_reason# 输出:watchdog / por / external / software
五、RK3399 看门狗复位排查关键注意点
- 寄存器清除时机RK3399 的 GRF_SOC_CON0 复位位是 “只读且锁存”,必须在读取后手动清除(写原值),否则下次重启仍会显示旧的复位原因;
- 看门狗硬件适配RK3399 的 WDT1(
0xFF1A0000)是系统看门狗,WDT0(0xFF1B0000)为备用,需确认驱动使用的是哪个看门狗; - 持久化日志RK3399 嵌入式系统默认
/var是 tmpfs(内存盘),需将复位日志写入/mnt/flash(Flash 分区)或 SD 卡,避免重启丢失; - 软件看门狗区别若使用内核
soft_wdt模块,复位后 GRF_SOC_CON0 的 WDT 位不会置 1,需结合dmesg | grep soft_wdt判断; - 内核配置依赖使用
devmem//dev/mem需内核开启CONFIG_DEVMEM=y,否则会提示权限拒绝。
好的关于看看门狗复位这块大概到这里。
谢谢阅读!