Linux Hung Task分析
关键词:khungtaskd、TASK_UNINTERRUPTIBLE、nvcsw、nivcsw、last_switch_count等等。 ? 经常会遇到内核打印“INFO: task xxx:xxx blocked for more than 120 seconds.”这样的log信息,这是内核的hung task机制在起作用。 hung task机制通过内核线程khungtaskd来实现的,khungtaskd监控TASK_UNINTERRUPTIBLE状态的进程,如果在120s周期内没有切换,就会打印详细信息。 ? 1. hung task背景处于D状态,即TASK_UNINTERRUPTIBLE状态的进程,不能接收kill信号。 如果一个进程长期处于D状态,用户往往无能为力。 进程处于长期处于D状态是不正常的,内核设计D状态目的是为了让进程等待IO完成,正常情况下IO应该会瞬息完成,然后唤醒响应D装固态进程。 即使在异常情况下,IO处理也有超时机制,原则上不应是进程长期处于D状态。 如果进程长期处于D状态,一是IO设备损坏,或者是内核中存在bug或机制不合理,导致进程长期处于D状态,无法唤醒。 针对这种情况,内核提供了hung task机制用于检测系统中是否有处于D状态进程超过120s没有切换过;如果存在则打印相关警告和堆栈。 2. hung task基本原理hung task的实现通过创建khungtaskd内核线程,定期120s唤醒一次; 然后遍历内核所有进程,需要满足两个条件:进程处于TASK_UNINTERRUPTIBLE,并且nvcsw+nivcsw==last_switch_count; 最后打印进程信息和堆栈。 3. hung task代码分析3.1 task_strcut中hung task相关成员在进行hung task分析之前,需要了解struct task_strcut中的state、nvcsw、nivcsw、last_switch_count几个成员含义。 struct task_struct { ... volatile long state; /* -1 unrunnable,0 runnable,>0 stopped */---------------当前进程状态,TASK_UNINTERRUPTIBLE表示进程不会被打断。 ... unsigned long nvcsw,nivcsw; /* context switch counts */--------------------------nvcsw表示进程主动切换次数,nivcsw表示进程被动切换次数,两者之和就是进程总的切换次数。... #ifdef CONFIG_DETECT_HUNG_TASK /* hung task detection */ unsigned long last_switch_count;--------------------------------------------------这个变量只有两个地方修改,一是在新建进程的时候设置初始值last_switch_count=nvcsw+nivcsw。另一个是在khungtaskd中进行更新。 #endif ... }; ? 3.2 khungtaskd线程创建watchdog()是khuangtaskd线程主函数,线程每隔sysctl_hung_task_timeout_secs醒来一次,调用check_hung_uninterruptible_tasks()检查所有进程。 static int watchdog(void *dummy) { unsigned long hung_last_checked = jiffies; set_user_nice(current,0); for ( ; ; ) { unsigned long timeout = sysctl_hung_task_timeout_secs; long t = hung_timeout_jiffies(hung_last_checked,timeout); if (t <= 0) { if (!atomic_xchg(&reset_hung_task,0)) check_hung_uninterruptible_tasks(timeout); hung_last_checked = jiffies; continue; } schedule_timeout_interruptible(t); } return 0; } static int __init hung_task_init(void) { atomic_notifier_chain_register(&panic_notifier_list,&panic_block); watchdog_task = kthread_run(watchdog,NULL,"khungtaskd"); return 0; } subsys_initcall(hung_task_init); panic_block注册到panic_notifier_list通知链表上,如果系统产生panic,那么did_panic就会被置1。 static int hung_task_panic(struct notifier_block *this,unsigned long event,void *ptr) { did_panic = 1; return NOTIFY_DONE; } static struct notifier_block panic_block = { .notifier_call = hung_task_panic,}; ? 3.3 检查进程是否hungcheck_hung_uninterruptible_tasks()遍历内核中所有进程、线程,首先判断状态是否是TASK_UNINTERRUPTIBLE。 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: */ if (test_taint(TAINT_DIE) || did_panic) return; rcu_read_lock(); for_each_process_thread(g,t) { if (!max_count--) goto unlock; if (!--batch_count) { batch_count = HUNG_TASK_BATCHING; if (!rcu_lock_break(g,t)) goto unlock; } /* use "==" to skip the TASK_KILLABLE tasks waiting on NFS */ if (t->state == TASK_UNINTERRUPTIBLE)-------------------------khungtaskd只监控TASK_UNINTERRUPTIBLE状态的进程线程。 check_hung_task(t,timeout); } 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,skip vfork and any other user process that freezer should skip. */ if (unlikely(t->flags & (PF_FROZEN | PF_FREEZER_SKIP))) return; /* * When a freshly created task is scheduled once,changes its state to * TASK_UNINTERRUPTIBLE without having ever been switched out once,it * musn‘t be checked. */ if (unlikely(!switch_count)) return; if (switch_count != t->last_switch_count) {-------------------------如果总切换次数和last_switch_count不等,表示在上次khungtaskd更新last_switch_count之后就发生了进程切换;反之,相等则表示120s时间内没有发生切换。 t->last_switch_count = switch_count;----------------------------更新last_switch_count。 return; } trace_sched_process_hang(t); if (!sysctl_hung_task_warnings && !sysctl_hung_task_panic)----------如果不使能warning和panic,返回。 return; /* * Ok,the task did not get scheduled for more than 2 minutes,* complain: */ if (sysctl_hung_task_warnings) { sysctl_hung_task_warnings--; pr_err("INFO: task %s:%d blocked for more than %ld seconds.n",t->comm,t->pid,timeout); pr_err(" %s %s %.*sn",print_tainted(),init_utsname()->release,(int)strcspn(init_utsname()->version," "),init_utsname()->version); pr_err(""echo 0 > /proc/sys/kernel/hung_task_timeout_secs"" " disables this message.n"); sched_show_task(t);----------------------------------------------显示进程ID、名称、状态以及栈等信息。 debug_show_all_locks();------------------------------------------如果使能debug_locks,则打印进程持有的锁。 } touch_nmi_watchdog(); if (sysctl_hung_task_panic) { trigger_all_cpu_backtrace(); panic("hung_task: blocked tasks"); } } ?下面看一下进程详细信息: void sched_show_task(struct task_struct *p) { unsigned long free = 0; int ppid; unsigned long state = p->state; if (!try_get_task_stack(p)) return; if (state) state = __ffs(state) + 1; printk(KERN_INFO "%-15.15s %c",p->comm,state < sizeof(stat_nam) - 1 ? stat_nam[state] : ‘?‘);------------------进程名称和状态,这里应该是D。 if (state == TASK_RUNNING) printk(KERN_CONT " running task "); #ifdef CONFIG_DEBUG_STACK_USAGE free = stack_not_used(p); #endif ppid = 0; rcu_read_lock(); if (pid_alive(p)) ppid = task_pid_nr(rcu_dereference(p->real_parent)); rcu_read_unlock(); printk(KERN_CONT "%5lu %5d %6d 0x%08lxn",free,task_pid_nr(p),ppid,(unsigned long)task_thread_info(p)->flags);------------------------------free表示栈空闲量;第二个表示线程/进程pid;第三个表示父进程pid;最后一个表示进程的flags。 print_worker_info(KERN_INFO,p); show_stack(p,NULL); put_task_stack(p); } ? ?如下log可以得到recvComm进程,pid为175,父进程为148,当前状态是D;当前hung的栈是read调用,卡在usb_sourceslink_read()函数。 ? ?4. 对khungtaskd的配置通过sysctl或者在/proc/sys/kernel/中进行配置:
还可以通过bootargs对hung后是否panic进行设置。 /* * Should we panic (and reboot,if panic_timeout= is set) when a * hung task is detected: */ unsigned int __read_mostly sysctl_hung_task_panic = CONFIG_BOOTPARAM_HUNG_TASK_PANIC_VALUE; static int __init hung_task_panic_setup(char *str) { int rc = kstrtouint(str,0,&sysctl_hung_task_panic); if (rc) return rc; return 1; } __setup("hung_task_panic=",hung_task_panic_setup); (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |