前言
Watchdog 是 Linux 系统一个很重要的机制,其目的是监测系统运行的情况,一旦出现锁死,死机的情况,能及时重启机器(取决于设置策略),并收集crash dump.
watchdog,顾名思义,看门狗。这就说明,有一个被watch的对象,和一个watch它的程序。
无论是内核watchdog,还是userland watchdog,其基本思路都是:
1. 假定某一个对象的状态能表征系统运行是否健康(比如interrupt的次数,比如/dev/watchdog的时间戳);
2. 启动一个watchdog程序,定期(通过内部或者外部时钟触发)来观测这个对象,来判定系统是否健康,并采取相应动作。
Watchdog 有好几种不同的实现机制,最近我在公司鼓捣了一下各种机制的原理,并在实验室机器上面进行了实验操作,总结了以下两种机制:
1. kernel watchdog
2. Userland watchdog
下面,我们分别阐述他们的机制。
kernel watchdog
kernel watchdog 目的
当我们引入这么一个Watchdog的时候,首先关心的是它是做什么用的。简单的说,kernel watchdog是用来检测Lockup 的。
所谓lockup,是指某段内核代码占着CPU不放。Lockup严重的情况下会导致整个系统失去响应。Lockup有几个特点:
首先只有内核代码才能引起lockup,因为用户代码是可以被抢占的,不可能形成lockup(只有一种情况例外,就是SCHED_FIFO优先级为99的实时进程即使在用户态也可能使[watchdog/x]内核线程抢不到CPU而形成soft lockup)
其次内核代码必须处于禁止内核抢占的状态(preemption disabled),因为Linux是可抢占式的内核,只在某些特定的代码区才禁止抢占(例如spinlock),在这些代码区才有可能形成lockup。
Lockup分为两种:soft lockup 和 hard lockup,它们的区别是 hard lockup 发生在CPU屏蔽中断的情况下。而soft lockup则是单个CPU被一直占用的情况(中断仍然可以响应)。
这里我们先要介绍一下NMI。
NMI
NMI,即非可屏蔽中断。即使在内核代码中设置了屏蔽所有中断的时候,NMI也是不可以被屏蔽的。
中断分为可屏蔽中断和非可屏蔽中断。
其中,可屏蔽中断包含时钟中断,外设中断(比如键盘中断,I/O设备中断,等等),当我们处理中断处理程序的时候,在中断处理程序top half时候,在不允许嵌套的情况下,需要关闭中断。
但NMI就不一样了,即便在关闭中断的情况下,他也能被响应。触发NMI的条件一般都是ECC error之类的硬件Error。但NMI也给我们提供了一种机制,在系统中断被误关闭的情况下,依然能通过中断处理程序来执行一些紧急操作,比如kernel panic。
这里涉及到了3个东西:kernel线程,时钟中断,NMI中断(不可屏蔽中断)。
这3个东西具有不一样的优先级,依次是kernel线程 < 时钟中断 < NMI中断。其中,kernel 线程是可以被调度的,同时也是可以被中断随时打断的。
接下来,我们分别看什么是soft lockup,什么是hard lockup.
SoftLockup
Soft lockup是指CPU被内核代码占据,以至于无法执行其它进程。检测soft lockup的原理是给每个CPU分配一个定时执行的内核线程[watchdog/x],如果该线程在设定的期限内没有得到执行的话就意味着发生了soft lockup,[watchdog/x]是SCHED_FIFO实时进程,优先级为最高的99,拥有优先运行的特权。
SoftLockup 示例代码
以下是一段Soft lockup的示例代码,通过一直占用某个CPU,而达到soft lockup的目的:
#include<linux/kernel.h>
#include<linux/module.h>
#include<linux/kthread.h>
struct task_struct *task0;
static spinlock_t spinlock;
int val;
int task(void *arg)
{
printk(KERN_INFO "%s:%d\n",__func__,__LINE__);
/* To generate panic uncomment following */
/* panic("softlockup: hung tasks"); */
while(!kthread_should_stop()) {
printk(KERN_INFO "%s:%d\n",__func__,__LINE__);
spin_lock(&spinlock);
/* busy loop in critical section */
while(1) {
printk(KERN_INFO "%s:%d\n",__func__,__LINE__);
}
spin_unlock(&spinlock);
}
return val;
}
static int softlockup_init(void)
{
printk(KERN_INFO "%s:%d\n",__func__,__LINE__);
val = 1;
spin_lock_init(&spinlock);
task0 = kthread_run(&task,(void *)val,"softlockup_thread");
set_cpus_allowed_ptr(task0, cpumask_of(0));
return 0;
}
static void softlockup_exit(void)
{
printk(KERN_INFO "%s:%d\n",__func__,__LINE__);
kthread_stop(task0);
}
module_init(softlockup_init);
module_exit(softlockup_exit);
上述代码是从某个网站上找到的,它通过spinlock()实现关抢占,使得该CPU上的[watchdog/x]无法被调度。另外,通过set_cpus_allowed_ptr()将该线程绑定到特定的CPU上去。
SoftLockup 检测机制
SoftLockup 检测首先需要对每一个CPU core注册叫做watchdog的kernel线程。即[watchdog/0],[watchdog/1],[watchdog/2