zoukankan      html  css  js  c++  java
  • 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);---------------------------------------------------设置当前进程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.
    ",
                t->comm, t->pid, timeout);
            pr_err("      %s %s %.*s
    ",
                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.
    ");
            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
    ", 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);
  • 相关阅读:
    java调用批处理后关闭窗口
    MD5Util校验大型文件
    Spring注解无法注入Session监听器解决办法
    验证码插件kaptcha的jsp使用
    JavaScript 模式化窗口使用方法
    HttpSessionListener中获取Spring中的Bean
    Spring 注解AOP 入门
    uploadify上传小札记【jsp版本】【亲测有效】
    ffmpeg视频转换及截图
    SpringMVC视图的创建
  • 原文地址:https://www.cnblogs.com/arnoldlu/p/10529621.html
Copyright © 2011-2022 走看看