static void check_hung_uninterruptible_tasks(unsigned long timeout)
{
int max_count = sysctl_hung_task_check_count;
int batch_count = HUNG_TASK_BATCHING;
struct task_struct *g, *t;
/*
* If the system crashed already then all bets are off,
* do not report extra hung tasks:
*/
// 判断系统是否已经 die、oops 或者 panic 了,若是系统已经 crash 了,就无需再做 hungtask 了。
if (test_taint(TAINT_DIE) || did_panic)
return;
rcu_read_lock();
// 检查进程的列表,寻找 D 状态的任务
do_each_thread(g, t) {
// 判断用户是否设置了检查进程的数量,若是已经达到用户设置的限制,就跳出循环。
if (!max_count--)
goto unlock;
// 判断是否到达批处理的个数,做这个批处理的目的就是因为整个检查是在关抢占的前提下进行的,可能进程列表的进程数很多,为了防止 hungtask 垄断 cpu,所以,做了一个批处理的限制,到达批处理的数量后,就放一下权,给其他的进程运行的机会。
if (!--batch_count) {
batch_count = HUNG_TASK_BATCHING;
rcu_lock_break(g, t);
/* Exit if t or g was unhashed during refresh. */
if (t->state == TASK_DEAD || g->state == TASK_DEAD)
goto unlock;
}
/* use "==" to skip the TASK_KILLABLE tasks waiting on NFS */
// 如果进程处于 D 状态,就开始把相关的信息显示出来了。
if (t->state == TASK_UNINTERRUPTIBLE)
check_hung_task(t, timeout);
} while_each_thread(g, t);
unlock:
rcu_read_unlock();
}
static void check_hung_task(struct task_struct *t, unsigned long timeout)
{
// 统计进程的切换次数 = 主动切换次数 + 被动切换次数
unsigned long switch_count = t->nvcsw + t->nivcsw;
/*
* Ensure the task is not frozen.
* Also, when a freshly created task is scheduled once, changes
* its state to TASK_UNINTERRUPTIBLE without having ever been
* switched out once, it musn
怎么样,经过上面的分析,可以发现其实原理很简单。
R 状态死锁检测
所谓 R 状态死锁:进程长时间(系统默认配置 60 秒)处于 TASK_RUNNING 状态垄断 cpu 而不发生切换,一般情况下是进程关抢占后长时候干活,有时候可能进程关抢占后处于死循环或者睡眠后,这样就造成系统异常。
对于这种死锁的检测机制 linux 提供的机制是 softlockup。主要集中在 softlockup.c 文件中。
首先,系统初始化的时候为每个 cpu 创建一个 watchdog 线程,这个线程是 fifo 的。具体实现如下:
static int __init spawn_softlockup_task(void)
{
void *cpu = (void *)(long)smp_processor_id();
int err;
// 可以通过启动参数禁止 softlockup
if (nosoftlockup)
return 0;
// 下面两个回调函数就是为每个 cpu 创建一个 watchdog 线程
err = cpu_callback(&cpu_nfb, CPU_UP_PREPARE, cpu);
if (err == NOTIFY_BAD) {
BUG();
return 1;
}
cpu_callback(&cpu_nfb, CPU_ONLINE, cpu);
register_cpu_notifier(&cpu_nfb);
atomic_notifier_chain_register(&panic_notifier_list, &panic_block);
return 0;
}
static int __cpuinit
cpu_callback(struct notifier_block *nfb, unsigned long action, void *hcpu)
{
int hotcpu = (unsigned long)hcpu;
struct task_struct *p;
switch (action) {
case CPU_UP_PREPARE:
case CPU_UP_PREPARE_FROZEN:
BUG_ON(per_cpu(softlockup_watchdog, hotcpu));
// 创建 watchdog 内核线程
p = kthread_create(watchdog, hcpu, "watchdog/%d", hotcpu);
if (IS_ERR(p)) {
printk(KERN_ERR "watchdog for %i failed\n", hotcpu);
return NOTIFY_BAD;
}
// 将时间戳清零
per_cpu(softlockup_touch_ts, hotcpu) = 0;
// 设置 watchdog
per_cpu(softlockup_watchdog, hotcpu) = p;
// 绑定 cpu
kthread_bind(p, hotcpu);
break;
case CPU_ONLINE:
case CPU_ONLINE_FROZEN:
// 唤醒 watchdog 这个内核线程 wake_up_process(per_cpu(softlockup_watchdog, hotcpu));
break;
......
return NOTIFY_OK;
}
其次,我们看一下内核线程 watchdog 的实现,如下:
static int watchdog(void *__bind_cpu)
{
// 将 watchdog 设置为 fifo
struct sched_param param = {.sched_priority = MAX_RT_PRIO-1};
sched_setscheduler(current, SCHED_FIFO, ?m);
// 清狗,即 touch 时间戳
/* initialize timestamp */
__touch_softlockup_watchdog();
// 当前进程可以被信号打断
set_current_state(TASK_INTERRUPTIBLE);
/*
* Run briefly once per second to reset the softlockup timestamp.
* If this gets delayed for more than 60 seconds then the
* debug-printout triggers in softlockup_tick().
*/
while (!kthread_should_stop()) {
// 核心实现就是 touch 时间戳,让出 cpu
__touch_softlockup_watchdog();
schedule();
if (kthread_should_stop())
break;
set_current_state(TASK_INTERRUPTIBLE);
}
__set_current_state(TASK_RUNNING);
return 0;
}
void softlockup_tick(void)
{
// 取得当前的 cpu
int this_cpu = smp_processor_id();
// 获得当前 cpu 的时间戳
unsigned long touch_ts = per_cpu(softlockup_touch_ts, this_cpu);
unsigned long print_ts;
// 获得进程当前的寄存器组
struct pt_regs *regs = get_irq_regs();
unsigned long now;
// 如果没有打开 softlockup,就将这个 cpu 对应的时间戳清零
/* Is detection switched off? */
if (!per_cpu(softlockup_watchdog, this_cpu) || softlockup_thresh <= 0) {
/* Be sure we don