關鍵詞:khungtaskd、TASK_UNINTERRUPTIBLE、nvcsw、nivcsw、last_switch_count等等。函數
常常會遇到內核打印「INFO: task xxx:xxx blocked for more than 120 seconds.」這樣的log信息,這是內核的hung task機制在起做用。ui
hung task機制經過內核線程khungtaskd來實現的,khungtaskd監控TASK_UNINTERRUPTIBLE狀態的進程,若是在120s週期內沒有切換,就會打印詳細信息。this
處於D狀態,即TASK_UNINTERRUPTIBLE狀態的進程,不能接收kill信號。atom
若是一個進程長期處於D狀態,用戶每每無能爲力。spa
進程處於長期處於D狀態是不正常的,內核設計D狀態目的是爲了讓進程等待IO完成,正常狀況下IO應該會瞬息完成,而後喚醒響應D裝固態進程。線程
即便在異常狀況下,IO處理也有超時機制,原則上不該是進程長期處於D狀態。debug
若是進程長期處於D狀態,一是IO設備損壞,或者是內核中存在bug或機制不合理,致使進程長期處於D狀態,沒法喚醒。設計
針對這種狀況,內核提供了hung task機制用於檢測系統中是否有處於D狀態進程超過120s沒有切換過;若是存在則打印相關警告和堆棧。code
hung task的實現經過建立khungtaskd內核線程,按期120s喚醒一次;blog
而後遍歷內核全部進程,須要知足兩個條件:進程處於TASK_UNINTERRUPTIBLE,而且nvcsw+nivcsw==last_switch_count;
最後打印進程信息和堆棧。
在進行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 ... };
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);---------------------------------------------------設置當前進程nice爲0,即普通優先級。 for ( ; ; ) { unsigned long timeout = sysctl_hung_task_timeout_secs;-------------------獲取進程hung時間上限。 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);-----------------------------------------休眠sysctl_hung_task_timeout_secs秒。 } return 0; } static int __init hung_task_init(void) { atomic_notifier_chain_register(&panic_notifier_list, &panic_block);------------註冊panic通知鏈,在panic時執行相關操做。 watchdog_task = kthread_run(watchdog, NULL, "khungtaskd");---------------------建立內核線程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, };
check_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;---------------------------每次遍歷進程數上限1024。 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))--------------------------------防止rcu_read_lock佔用過長時間。釋放rcu,並主動調度。調度回來後檢查響應進程是否還在,不在則退出遍歷,不然繼續。 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) {------------------------------------hung task錯誤打印次數限制,默認爲10次,整個系統運行期間最多打印10次。 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 %.*s\n", 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%08lx\n", 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()函數。
經過sysctl或者在/proc/sys/kernel/中進行配置:
hung_task_panic------------------------是否在檢測到hung後panic,默認值0
hung_task_check_count---------------最大檢查task數量,默認值32768
hung_task_timeout_secs--------------超時時間,默認值120
hung_task_warnings--------------------打印hung warning的次數,默認值10
還能夠經過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);