Linux Hung Task分析

關鍵詞: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

 

1. hung task背景

處於D狀態,即TASK_UNINTERRUPTIBLE狀態的進程,不能接收kill信號。atom

若是一個進程長期處於D狀態,用戶每每無能爲力。spa

進程處於長期處於D狀態是不正常的,內核設計D狀態目的是爲了讓進程等待IO完成,正常狀況下IO應該會瞬息完成,而後喚醒響應D裝固態進程。線程

即便在異常狀況下,IO處理也有超時機制,原則上不該是進程長期處於D狀態。debug

若是進程長期處於D狀態,一是IO設備損壞,或者是內核中存在bug或機制不合理,致使進程長期處於D狀態,沒法喚醒。設計

針對這種狀況,內核提供了hung task機制用於檢測系統中是否有處於D狀態進程超過120s沒有切換過;若是存在則打印相關警告和堆棧。code

2. hung task基本原理

hung task的實現經過建立khungtaskd內核線程,按期120s喚醒一次;blog

而後遍歷內核全部進程,須要知足兩個條件:進程處於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);---------------------------------------------------設置當前進程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,
};

 

3.3 檢查進程是否hung

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()函數。

 

 4. 對khungtaskd的配置

經過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);
相關文章
相關標籤/搜索