树莓派启用看门狗watchdog

树莓派内核默认没有启用看门狗功能,当内核挂死时将进入“死机”状态或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: [email protected] {
                        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系统后,可以发现在看门狗驱动模块已经顺利加载了:

[email protected]:~# lsmod

Module                  Size  Used by

...

bcm2835_wdt             4142  0

...

内核启动日志输出如下:

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

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

[email protected]:~# echo 0 > /dev/watchdog0

[email protected]:~# dmesg -c

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

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

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

[email protected]:~# 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系统启动,至此树莓派的看门狗功能添加完成。

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

时间: 2024-10-13 23:54:18

树莓派启用看门狗watchdog的相关文章

给树莓派安装看门狗的两种方法[转]

树莓派的CPU是保护有硬件看门狗的,可以通过安装模块和值守程序来实现看门狗防止树莓派死机. 安装方法一:watchdog.sh的源码: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 #!/usr/bin/env bash echo "[+] Activating Temperature Sensor" modprobe bcm2708_wdog echo "bcm2708_wdog" >> /etc/modules

Linux 软件看门狗 watchdog —— 开门放狗、定期喂狗、狗咬人了

Linux 自带了一个 watchdog 的实现,用于监视系统的运行,包括一个内核 watchdog module 和一个用户空间的 watchdog 程序. 内核 watchdog 模块通过 /dev/watchdog 这个字符设备与用户空间通信.用户空间程序一旦打开 /dev/watchdog 设备(俗称“开门放狗”),就会导致在内核中启动一个1分钟的定时器(系统默认时间),此后,用户空间程序需要保证在1分钟之内向这个设备写入数据(俗称“定期喂狗”),每次写操作会导致重新设定定时器.如果用户

Linux 软件看门狗 watchdog

Linux 自带了一个 watchdog 的实现,用于监视系统的运行,包括一个内核 watchdog module 和一个用户空间的 watchdog 程序.内核 watchdog 模块通过 /dev/watchdog 这个字符设备与用户空间通信.用户空间程序一旦打开 /dev/watchdog 设备(俗称"开门放狗"),就会导致在内核中启动一个1分钟的定时器(系统默认时间),此后,用户空间程序需要保证在1分钟之内向这个设备写入数据(俗称"定期喂狗"),每次写操作会

Linux 软件看门狗 watchdog 喂狗

Linux 自带了一个 watchdog 的实现,用于监视系统的运行,包括一个内核 watchdog module 和一个用户空间的 watchdog程序.内核 watchdog 模块通过 /dev/watchdog 这个字符设备与用户空间通信.用户空间程序一旦打开 /dev/watchdog 设备(俗称"开门放狗"),就会导致在内核中启动一个1分钟的定时器(系统默认时间),此后,用户空间程序需要保证在1分钟之内向这个设备写入数据(俗称"定期喂狗"),每次写操作会导

【分享】iTOP-iMX6UL开发板驱动看门狗 watchdog 以及 Linux-c 测试例程

iTOP-iMX6UL开发板看门狗测试例程,iTOP-iMX6UL 开发板的看门狗驱动默认已经配置,可以直接使用测试例程. 版本 V1.1:1.格式修改:2.例程修改完善,其中增加喂狗代码.1 看门狗内核驱动看门狗的驱动已经默认配置,iMX6UL 的 watchdog 看门狗驱动源码是"drivers/watchdog/watchdog.c".如下图所示,选择"Device Drivers --->",输入回车. 如下图所示,在"Device Dri

软件看门狗--别让你地程序无响应(使用未公开API函数IsHungAppWindow,知识点较全)

正文一.概述一些重要的程序,必须让它一直跑着:而且还要时时关心它的状态——不能让它出现死锁现象.当然,如果一个主程序会出现死锁,肯定是设计或者编程上的失误.我们首要做的事是,把这个Bug揪出来.但如果时间紧迫,这个Bug又“飘忽不定”,那么,我们还是先写一个软件“看门狗”,暂时应一下急吧. “看门狗”的需求描述:“看门狗”的运行不出现界面窗口,具有一定的隐蔽性:定时判断目标进程是否运行在当前系统中,如果没有则启动目标进程:判断目标进程是否“没有响应”,如果是则终止目标进程:如果目标进程“没有响应

树莓派 Raspbian 软件源更改 看门狗启用

1.替换脚本 下面脚本请直接复制到终端执行!! 适用于raspbian-stretch(基于Debian9) sudo -s echo -e "deb http://mirrors.ustc.edu.cn/raspbian/raspbian/ stretch main contrib non-free rpi \n deb-src http://mirrors.ustc.edu.cn/raspbian/raspbian/ stretch main contrib non-free rpi&quo

开启树莓派的硬件看门狗功能

前段时间挖矿,将CGMiner设置为开机启动,由于各种问题系统不是很稳定,时不时的就停了L 网上查了一下,树莓派居然自带硬件的看门狗功能,立即尝试了一把,基本上能够解决我的问题 ? 原理很简单: 看门狗(Watchdog)需要不停的喂骨头(heartbeat) 如果在规定的时间内都没有喂(默认15秒),系统自动重启 ? 不过,一秒钟要吃一块骨头,还要不停的吃,还真是吃不饱的狗呀,呵呵~~~ ? 下面是开启该功能的脚本 // 开启模块 sudo modprobe bcm2708_wdog sudo

基于S3C2440的嵌入式Linux驱动——看门狗(watchdog)驱动解读

本文将介绍看门狗驱动的实现. 目标平台:TQ2440 CPU:s3c2440 内核版本:2.6.30 1. 看门狗概述 看门狗其实就是一个定时器,当该定时器溢出前必须对看门狗进行"喂狗",如果不这样做,定时器溢出后则将复位CPU. 因此,看门狗通常用于对处于异常状态的CPU进行复位. 具体的概念请自行百度. 2. S3C2440看门狗 s3c2440的看门狗的原理框图如下: 可以看出,看门狗定时器的频率由PCLK提供,其预分频器最大取值为255+1:另外,通过MUX,可以进一步降低频率