树莓派内核默认没有启用看门狗功能,当内核挂死时将进入“死机”状态或kgdb调试状态,并不会自动重启系统。本文为树莓派开启看门狗功能并通过内核线程周期性喂狗,当出现系统崩溃时会自动重启Linux系统。

环境说明:(1)单板:树莓派b

(2)Linux内核:Linux-4.1.15

(3)Bootloader:u-boot-2015.10

源码文件:linux-rpi-4.1.y/drivers/watchdog/bcm2835_wdt.c

1、看门狗驱动源码分析

树莓派的看门狗驱动程序为内核drivers/watchdog/bcm2835_wdt.c文件,该驱动程序实现了开关看门狗和喂狗的功能(不提供喂狗策略),它向内核看门狗子系统注册驱动设备,将喂狗策略移交应用程序,由应用程序打开/dev/watchdogX标准接口并完成周期性喂狗的操作。简单分析一下该驱动程序的源码:

static const struct of_device_id bcm2835_wdt_of_match[] = {{ .compatible = "brcm,bcm2835-pm-wdt", },{},
};
MODULE_DEVICE_TABLE(of, bcm2835_wdt_of_match);static struct platform_driver bcm2835_wdt_driver = {.probe       = bcm2835_wdt_probe,.remove        = bcm2835_wdt_remove,.shutdown = bcm2835_wdt_shutdown,.driver = {.name =        "bcm2835-wdt",.of_match_table = bcm2835_wdt_of_match,},
};
module_platform_driver(bcm2835_wdt_driver);

驱动程序通过platform driver实现,同时支持设备数dtb添加platform device,匹配名称为"brcm,bcm2835-pm-wdt"。

module_param(heartbeat, uint, 0);
MODULE_PARM_DESC(heartbeat, "Initial watchdog heartbeat in seconds");module_param(nowayout, bool, 0);
MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default="__MODULE_STRING(WATCHDOG_NOWAYOUT) ")");

驱动程序提供了两个可调参数,其中heartbeat表示看门狗设置的超时时间,默认为15s;nowayout是一个bool型变量;如若设置了就表示该看门狗一旦开启将不再向应用提供关闭的功能,只能通过不断的喂狗操作来保证系统不会重启。下面分析一下其中的probe初始化函数:

static int bcm2835_wdt_probe(struct platform_device *pdev)
{struct device *dev = &pdev->dev;struct device_node *np = dev->of_node;struct bcm2835_wdt *wdt;int err;wdt = devm_kzalloc(dev, sizeof(struct bcm2835_wdt), GFP_KERNEL);if (!wdt)return -ENOMEM;platform_set_drvdata(pdev, wdt);spin_lock_init(&wdt->lock);wdt->base = of_iomap(np, 0);if (!wdt->base) {dev_err(dev, "Failed to remap watchdog regs");return -ENODEV;}watchdog_set_drvdata(&bcm2835_wdt_wdd, wdt);watchdog_init_timeout(&bcm2835_wdt_wdd, heartbeat, dev);watchdog_set_nowayout(&bcm2835_wdt_wdd, nowayout);err = watchdog_register_device(&bcm2835_wdt_wdd);if (err) {dev_err(dev, "Failed to register watchdog device");iounmap(wdt->base);return err;}dev_info(dev, "Broadcom BCM2835 watchdog timer");return 0;
}

该初始化函数完成以下功能:(1)分配bcm2835_wdt结构体内存空间,动态映射寄存器的虚拟内存空间到wdt->base中,后续通过向该地址空间写入数据即可完成寄存器的操作。(2)初始化看门狗子系统的watchdog_device结构,根据输入参数调整看门狗超时时间和设置status状态标志位。(3)向看门狗子系统注册看门口设备。

其中的watchdog_device结构实例bcm2835_wdt_wdd定义如下:

static struct watchdog_device bcm2835_wdt_wdd = {.info =       &bcm2835_wdt_info,.ops =       &bcm2835_wdt_ops,.min_timeout =    1,.max_timeout =   WDOG_TICKS_TO_SECS(PM_WDOG_TIME_SET),.timeout =    WDOG_TICKS_TO_SECS(PM_WDOG_TIME_SET),
};

这里的ops结构为驱动程序向子系统注册的驱动控制函数结构,子系统在在接收到用户的Ioctl和write控制指令后会调用该注册函数。由于子系统向用户开放了调整看门狗超时时间的设置接口,这里定义了最大和最小的超时时间限制(min_timeout和max_timeout),在调整timeout值时会进行保护判断。

static struct watchdog_ops bcm2835_wdt_ops = {.owner = THIS_MODULE,.start =   bcm2835_wdt_start,.stop =      bcm2835_wdt_stop,.set_timeout =    bcm2835_wdt_set_timeout,.get_timeleft =    bcm2835_wdt_get_timeleft,
};

树莓派的驱动程序向子系统提供了以上4种接口(其他接口未实现),其中start接口表示启动看门狗和喂狗操作,stop接口表示关闭看门狗,set_timeout表示调整看门狗超时时间,get_timeleft表示查询距离看门狗超时还剩多少时间,各个函数的具体实现基本就是设置芯片的寄存器,就不仔细分析了。

bcm2835_wdt_probe函数在完成看门狗的注册之后,在文件系统的/dev目录下就会生成watchdog0设备文件,后面应用程序就可以通过操作它来实现看门狗的控制了。Linux内核的看门狗子系统实现在drivers/watchdog/目录下的watchdog_core.c和watchdog_dev.c文件中,其中watchdog_core.c实现了子系统的初始化以及提供了面向驱动的注册接口函数watchdog_register_device等;watchdog_dev.c实现了字符设备的初始化和注册,同时提供了设备文件控制接口watchdog_fops:

static const struct file_operations watchdog_fops = {.owner     = THIS_MODULE,.write       = watchdog_write,.unlocked_ioctl   = watchdog_ioctl,.open     = watchdog_open,.release   = watchdog_release,
};

可以看到这里实现了open、close、write和ioctl的控制接口,另外在watchdog.h中定义了Ioctl的标准控制定义:

#define  WDIOC_GETSUPPORT    _IOR(WATCHDOG_IOCTL_BASE, 0, struct watchdog_info)  //获取看门狗info信息
#define WDIOC_GETSTATUS     _IOR(WATCHDOG_IOCTL_BASE, 1, int)                   //查询看门狗status状态信息
#define WDIOC_GETBOOTSTATUS _IOR(WATCHDOG_IOCTL_BASE, 2, int)                   //查询看门狗bootstatus信息
......
#define WDIOC_SETOPTIONS    _IOR(WATCHDOG_IOCTL_BASE, 4, int)                   //设置开关看门狗
#define WDIOC_KEEPALIVE     _IOR(WATCHDOG_IOCTL_BASE, 5, int)                   //喂狗
#define WDIOC_SETTIMEOUT        _IOWR(WATCHDOG_IOCTL_BASE, 6, int)              //设置看门狗超时时间
#define WDIOC_GETTIMEOUT        _IOR(WATCHDOG_IOCTL_BASE, 7, int)               //查询看门狗超时时间
......
#define WDIOC_GETTIMELEFT   _IOR(WATCHDOG_IOCTL_BASE, 10, int)                  //查询看门狗距离超时的剩余时间

这些ioctl控制由watchdog_ioctl函数负责处理,它会转接调用驱动程序中注册的ops函数接口。关于看门狗子系统中驱动的注册以及用户控制调用流程为典型的Linux驱动子系统架构,同前博文 《构建Linux内核驱动demo子系统示例》中分析总结的驱动子系统实现如出一辙(不同之处在于没有实现sys和proc接口),本文不再详细分析,具体看以参见watchdog_core.c和watchdog_dev.c中的源码。

2、看门狗驱动源码修改

由于看门狗驱动程序并没有实现喂狗策略,因此要启用看门狗可以通过编写应用程序,在应用层创建一个守护进程实现周期喂狗操作。但本文并不通过该方式实现,而是修改该驱动程序,在内核中创建一个内核线程来实现喂狗的动作。在驱动源码中添加以下3个函数:

static void bcm2835_wdt_set_prio(unsigned int policy, unsigned int prio)
{struct sched_param param = { .sched_priority = prio };sched_setscheduler(current, policy, ¶m);
}static int bcm2835_wdt_kthread(void *data)
{struct watchdog_device *wdog = (struct watchdog_device *)data;/* 调整内核线程的调度策略和优先级 */bcm2835_wdt_set_prio(SCHED_FIFO, MAX_RT_PRIO - 1);while(1) {if (kthread_should_stop()) {bcm2835_wdt_stop(wdog);break;}if (bcm2835_wdt_wdd.timeout != 0) {(void)bcm2835_wdt_start(&bcm2835_wdt_wdd);msleep((bcm2835_wdt_wdd.timeout >> 2)*1000);         dev_dbg(wdog->dev, "BCM2835 ping watchdog");} else {msleep(1000);}}return 0;
}static void bcm2835_wdt_start_kthread(struct watchdog_device *wdog)
{/* 启动看门狗并创建内核线程执行喂狗操作 */mutex_lock(&bcm2835_wdt_lock);if (kwdt_task == NULL) {kwdt_task = kthread_run(bcm2835_wdt_kthread, &bcm2835_wdt_wdd, "bcm2835_kwdt");if (IS_ERR(kwdt_task)) {dev_err(wdog->dev, "Failed to Create BCM2835 kernel watchdog thread");kwdt_task = NULL;}dev_info(wdog->dev, "Create BCM2835 kernel watchdog thread OK!"" TimeOut = %d sec", bcm2835_wdt_wdd.timeout);}mutex_unlock(&bcm2835_wdt_lock);
}

其中bcm2835_wdt_start_kthread函数用于创建内核喂狗守护进程bcm2835_kwdt,该线程执行函数bcm2835_wdt_kthread,它首先调整自身的调度策略为FIFO策略(软实时),并提高进程的优先级,这样可以使该进程在系统较为繁忙时也能确保其调度性,然后在该函数周期的实现喂狗动作,喂狗的时间间隔为超时时间的1/4。如果线程需要销毁会先执行关闭看门狗的动作,防止系统重启。

在看门狗启动函数bcm2835_wdt_start中添加如下:

static int bcm2835_wdt_start(struct watchdog_device *wdog)
{/* 喂狗操作 */....../* added by zhangyi 2016.5.7 */bcm2835_wdt_start_kthread(wdog);/* * */return 0;
}

这里添加启动内核线程的函数,如此可以通过"echo xx > /dev/watchdog0"启动该内核线程,最后在驱动的release函数中增加释放程序:

static int bcm2835_wdt_remove(struct platform_device *pdev)
{struct bcm2835_wdt *wdt = platform_get_drvdata(pdev);/* added by zhangyi 2016.5.7 */mutex_lock(&bcm2835_wdt_lock);if (kwdt_task) {kthread_stop(kwdt_task);kwdt_task = NULL;}mutex_unlock(&bcm2835_wdt_lock);/* * */watchdog_unregister_device(&bcm2835_wdt_wdd);iounmap(wdt->base);return 0;
}

这样在内核卸载该驱动程序时会销毁喂狗内核线程并关闭看门狗。

3、看门狗实验效果

(1)编译以上修改后的看门狗驱动程序,将生成的模块.ko文件拷贝到树莓派的根文件系统的/lib/modules/4.1.15/kernel/drivers/watchdog/目录下(若不需要udev自动加载则任意目录皆可)。

(2)修改dts文件arch/arm/boot/dts/bcm2708_common.dtsi,将其中的watchdog部分修改如下:

                watchdog: watchdog@7e100000 {compatible = "brcm,bcm2835-pm-wdt";reg = <0x7e100000 0x28>;timeout-sec = <15>;    //added by zhangyi 2016.5.7//status = "disabled";};

默认dts是不使能watchdog的,这里将他启用,同时设置超时时间为15s(在驱动程序的bcm2835_wdt_probe->watchdog_init_timeout()函数中可能会用到)。修改完成后重新编译dtb文件并拷贝到U盘中。

(3)启动看门狗

启动树莓派进入Linux系统后,可以发现在看门狗驱动模块已经顺利加载了:

root@apple:~# lsmod
Module                  Size  Used by
...
bcm2835_wdt             4142  0 
...

内核启动日志输出如下:

[   12.868529] bcm2835-wdt 20100000.watchdog: Broadcom BCM2835 watchdog timer

下面启动看门狗内核线程,超时时间为15s

root@apple:~# echo 0 > /dev/watchdog0
root@apple:~# dmesg -c

[  958.846139] watchdog watchdog0: Create BCM2835 kernel watchdog thread OK! TimeOut = 15 sec

(4)手动触发看门狗复位

接下来为了验证看门狗的功能,这里手动触发内核kernel panic,使得内核进程无法在调度,从而无法执行喂狗,最终系统重启。

root@apple:~# echo c > /proc/sysrq-trigger 

[ 1299.165014] sysrq: SysRq : Trigger a crash
[ 1299.169352] Unable to handle kernel NULL pointer dereference at virtual address 00000000
[ 1299.177544] pgd = c5ee8000
[ 1299.180279] [00000000] *pgd=05e50831, *pte=00000000, *ppte=00000000
[ 1299.186665] Internal error: Oops: 817 [#1] ARM

Entering kdb (current=0xc5838da0, pid 507) Oops: (null)
due to oops @ 0xc0318624
CPU: 0 PID: 507 Comm: bash Tainted: G           O    4.1.15 #5
Hardware name: BCM2708
task: c5838da0 ti: c5ed0000 task.ti: c5ed0000
PC is at sysrq_handle_crash+0x28/0x34
LR is at __handle_sysrq+0x9c/0x16c
pc : [<c0318624>]    lr : [<c0318edc>]    psr: 60000013
sp : c5ed1e60  ip : c5ed1e70  fp : c5ed1e6c
r10: 00000002  r9 : c5ed0000  r8 : 00000000
r7 : 00000005  r6 : 00000063  r5 : c0bc6bc4  r4 : c0c0cb48
r3 : 00000000  r2 : 00000001  r1 : c0c32590  r0 : 00000063
Flags: nZCv  IRQs on  FIQs on  Mode SVC_32  ISA ARM  Segment user
Control: 00c5387d  Table: 05ee8008  DAC: 00000015
CPU: 0 PID: 507 Comm: bash Tainted: G           O    4.1.15 #5
Hardware name: BCM2708
[<c0016660>] (unwind_backtrace) from [<c0013524>] (show_stack+0x20/0x24)
[<c0013524>] (show_stack) from [<c05263c4>] (dump_stack+0x20/0x28)
[<c05263c4>] (dump_stack) from [<c0010ae4>] (show_regs+0x1c/0x20)
[<c0010ae4>] (show_regs) from [<c00939f0>] (kdb_main_loop+0x33c/0x740)
[<c00939f0>] (kdb_main_loop) from [<c00963e8>] (kdb_stub+0x18c/0x3cc)
[<c00963e8>] (kdb_stub) from [<c008ccfc>] (kgdb_handle_exception+0x27c/0x7c0)
more>

U-Boot 2015.10 (Jan 02 2016 - 10:49:06 +0800)

DRAM:  128 MiB
RPI Model B rev2
MMC:   bcm2835_sdhci: 0
reading uboot.env
In:    serial
Out:   lcd
Err:   lcd
Net:   Net Initialization Skipped
No ethernet found.
Hit any key to stop autoboot:  0

可以看到在触发了panic以后的15s后,系统自动重启,重新加载u-boot并引导Linux系统启动,至此树莓派的看门狗功能添加完成。

最后,本文中仅作为一种简单的实现方式,亦可根据需求实现看门狗在用户主进程中或硬件中断处理程序中执行喂狗策略(中断中可监测业务进程的执行状态并判断是否需要喂狗),实现方法和种类很多,原理一样。

树莓派启用看门狗watchdog相关推荐

  1. Android 中的看门狗—Watchdog

     Android 中的看门狗-Watchdog 一.SystemServer 进程中初始化.启动看门狗............................................... ...

  2. 什么是看门狗(watchdog)?看门狗有什么作用?

    什么是看门狗(watchdog) 看门狗,又叫 watchdog timer,是一个定时器电路, 一般有一个输入,叫喂狗,一个输出到MCU的RST端,MCU正常工作的时候,每隔一端时间输出一个信号到喂 ...

  3. RTOS 系统篇-看门狗 WatchDog 2

    RTOS 系统篇-看门狗 WatchDog 2 概述 上一节在 RTOS 系统篇-看门狗 WatchDog[不喂狗就咬你] 讲解了 看门狗的基本原理,以及任务.中断中触发看门狗的原因.解决方法. 本小 ...

  4. 什么是看门狗(watchdog)

    什么是看门狗(watchdog) 看门狗,又叫 watchdog timer,是一个定时器电路, 一般有一个输入,叫喂狗,一个输出到MCU的RST端,MCU正常工作的时候,每隔一端时间输出一个信号到喂 ...

  5. python看门狗(watchdog)、多线程、实现文件夹实时监听、日志输出、备份

    python看门狗(watchdog).多线程.实现文件夹实时监听.日志输出.备份 代码展示 import _thread from watchdog.observers import Observe ...

  6. RTOS 系统篇-看门狗 WatchDog[不喂狗就咬你]

    RTOS 系统篇-看门狗 WatchDog[不喂狗就咬你] 概述 程序设计完成后,要开始考虑系统整体的稳定性了. 在设备上线后,可能因为程序设计不合理.硬件设计有 bug,电气干扰.静电噪声.电源故障 ...

  7. 新唐Nuvoton NUC972 看门狗WatchDog(WDT)使用注意事项

      由于新唐系台湾厂商,NUC972 ARM MPU资料不是足够完善,笔者认为,一般ARM处理器的TRM手册至少要3000页以上才能够足以详细描述各种寄存器在不同情况下的功能描述.新唐的TRM只有区区 ...

  8. linux看门狗树莓派,给树莓派安装看门狗

    你还在担心树莓派死机吗?给他装个看门狗吧! 什么是看门狗程序? 看门狗程序就是一个健康监控程序,每间隔一定时间(默认10秒)检查一次是否在设定的健康工作范围内,如果超过即启动硬件复位程序,让设备重新启 ...

  9. Linux 软件看门狗 watchdog

    Linux 自带了一个 watchdog 的实现,用于监视系统的运行,包括一个内核 watchdog module 和一个用户空间的 watchdog 程序.内核 watchdog 模块通过 /dev ...

最新文章

  1. 那个一年发了4篇Cell的研究生,后来怎么样了?
  2. 11岁美国男孩用玩具熊“黑”了国际网络安全大会
  3. MFC六大核心机制之三:动态创建
  4. android - 常用知识点以及代码片段(不断更新)
  5. redis mysql原理_MYSQL MONGODB REDIS 同步原理以及高可用性对比
  6. 组装微型计算机时 下列哪些部件,2016年9月计算机三级网络技术考试试题及答案...
  7. 动力环境监控系统论文_浅析建设智能化动力环境监控系统维护水平论文
  8. pdf页眉页脚设置步骤
  9. oracle data guard三种,Oracle Dataguard三种保护模式概述
  10. java add two numbers_两数相加(Add Two Numbers)
  11. ServiceFabric极简文档-1.2 硬件环境.md
  12. CMU15213 Intro to Computer Systems学习笔记(14) Exceptional Control Flow: Signals and Nonlocal Jumps
  13. Visual Studio Code 快速生成HTML结构
  14. 多线程并发面试题合集
  15. java adsl 拨号_Java实现ADSL拨号上网
  16. 区块链中的“双花”问题
  17. SSL与数字证书,Htpps
  18. JAVA核心知识点--使用org.json.JSONObject处理Json数据
  19. 使用FLuke福禄克MicroScanner2 POE(MS-POE)检测以太网供电
  20. uni-app 封装js方、页面的生命周期、数据双向绑定、封装组件

热门文章

  1. 王可欣 作业三 统计分析报告
  2. 快速设置Scrapy随机的IP代理
  3. 浅谈百度360T盘大战
  4. java socket发送定长报文_定长消息报文的组包与解包简单封装(Java实现)
  5. CBK携手横山书院成功举办《文化中国》第九届秋季讲坛
  6. 弱小目标检测跟踪算法研究(4) 基于双边滤波(BF)的红外弱小目标检测之背景抑制
  7. 互联网+、工业4.0和人工智能,主要的区别是什么?
  8. 射频通信基础:三种接收机的介绍和比较
  9. 即时通讯工具(IM)的网络营销价值
  10. linux上查看cap文件,如何使用tcpdump在Mac OS X上读取.cap数据包捕获文件 | MOS86