hung 之 hung task 检测

1. hung task 的含义

hung 本意是 "挂起",hung task 本意即 "任务被挂起,得不到执行"。

但是如果我们对 linux kernel 的任务调度机制有所了解的话,应该会知道 "任务被挂起,得不到执行" 是很正常的事情。

而在 linux kernel 中,hung task 是指长时间处于 D 状态(TASK_UNINTERRUPTIBLE,即 uninterruptible sleep)的进程。

2. 检测 hung task 的机制

linux kernel 检测 hung task 的机制是 khungtaskd。

khungtaskd 是 kthreadd 的子进程。

yudi:/ # ps -A|grep khungtaskd
root            80     2          0      0 watchdog            0 S [khungtaskd]

通过 sysctl 或者 /proc/sys/kernel/hung_task_xxx 节点可以读、写 khungtaskd 配置。

3. khungtaskd

3.1 配置

3.1.1 首先是 hung task 超时时间

缺省的超时时间由宏 DEFAULT_HUNG_TASK_TIMEOUT 配置,默认是 120 秒。

// lib/Kconfig.debug

config DEFAULT_HUNG_TASK_TIMEOUT
        int "Default timeout for hung task detection (in seconds)"
        depends on DETECT_HUNG_TASK
        default 120
        help
          This option controls the default timeout (in seconds) used
          to determine when a task has become non-responsive and should
          be considered hung.

          It can be adjusted at runtime via the kernel.hung_task_timeout_secs
          sysctl or by writing a value to
          /proc/sys/kernel/hung_task_timeout_secs.

          A timeout of 0 disables the check.  The default is two minutes.
          Keeping the default should be fine in most cases.

下面是本地 Android 设备的 DEFAULT_HUNG_TASK_TIMEOUT 值,也是默认值 120(单位是秒

yudi:/ # zcat /proc/config.gz |grep T_HUNG_TASK_TIMEOUT
CONFIG_DEFAULT_HUNG_TASK_TIMEOUT=120

表示 hungtask 超时时间的变量是 sysctl_hung_task_timeout_secs。

/*
 * Zero means infinite timeout - no checking done:
 */
unsigned long __read_mostly sysctl_hung_task_timeout_secs = CONFIG_DEFAULT_HUNG_TASK_TIMEOUT;

sysctl_hung_task_timeout_secs 可以通过 sysctl 或者写 /proc/sys/kernel/hung_task_timeout_secs 节点来改变。

本地 Android 设备的 /proc/sys/kernel/hung_task_timeout_secs 值是 0。

yudi:/ # cat /proc/sys/kernel/hung_task_timeout_secs
0

 Android 设备的 /proc/sys/kernel/hung_task_timeout_secs 值为 0,应该是 init.rc 中的默认配置。

// system/core/rootdir/init.rc

97 on init
...
302     write /proc/sys/kernel/panic_on_oops 1
303     write /proc/sys/kernel/hung_task_timeout_secs 0
304     write /proc/cpu/alignment 4

llkd.rc 也会配置 hung_task_timeout_secs 的值,不过是在 khungtask.enable 使能时设置。

本地 Android 设备并未使能 khungtask.enable。

// system/core/llkd/llkd.rc

24 # Configure [khungtaskd]
25 on property:khungtask.enable=true
26     write /proc/sys/kernel/hung_task_timeout_secs ${ro.khungtask.timeout:-720}
27     write /proc/sys/kernel/hung_task_warnings 65535
28     write /proc/sys/kernel/hung_task_check_count 65535
29     write /proc/sys/kernel/hung_task_panic 1
30 
31 on property:khungtask.enable=false
32     write /proc/sys/kernel/hung_task_panic 0
yudi:/ # getprop |grep khungtask.enable
[khungtask.enable]: [false]

3.1.2 然后是 check 周期

表示 check 周期的变量是 sysctl_hung_task_check_interval_secs,缺省值是 0。

sysctl_hung_task_check_interval_secs 可以通过 sysctl 或者写 /proc/sys/kernel/hung_task_check_interval_secs 节点来改变。

本地 Android 设备的 /proc/sys/kernel/hung_task_check_interval_secs 值是也 0

yudi:/ # cat /proc/sys/kernel/hung_task_check_interval_secs
0

3.1.3 检查到 hung task 时,是否触发 panic

变量:sysctl_hung_task_panic

缺省值由宏 CONFIG_BOOTPARAM_HUNG_TASK_PANIC_VALUE 设置。

/*
 * 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;

本地 Android 设备的 CONFIG_BOOTPARAM_HUNG_TASK_PANIC_VALUE 值为 0。

yudi:/ # zcat /proc/config.gz |grep CONFIG_BOOTPARAM_HUNG_TASK_PANIC_VALUE
CONFIG_BOOTPARAM_HUNG_TASK_PANIC_VALUE=0

sysctl_hung_task_panic 可以由 sysctl 或 /proc/sys/kernel/hung_task_panic 读、写。

本地 Android 设备的 /proc/sys/kernel/hung_task_panic 值也是 0。

yudi:/ # cat /proc/sys/kernel/hung_task_panic
0

3.1.4 hung task 的最大警告次数

变量:sysctl_hung_task_warnings

缺省值是 10。

int __read_mostly sysctl_hung_task_warnings = 10;

sysctl_hung_task_warnings 可以由 sysctl 或 /proc/sys/kernel/hung_task_warnings 读、写。

本地 Android 设备的 /proc/sys/kernel/hung_task_warnings 值也是 10。

yudi:/ # cat /proc/sys/kernel/hung_task_warnings
10

每检测到一次 hung task,就会将 sysctl_hung_task_warnings 值减一,sysctl_hung_task_warnings 为 0 后不再警告。

sysctl_hung_task_warnings 和 sysctl_hung_task_panic 可以同时开启。

3.1.5 hung task 警告时是否打印每个 CPU 上正在运行 task 的调用栈

变量:sysctl_hung_task_all_cpu_backtrace

缺省值是 0.

#ifdef CONFIG_SMP
/*
 * Should we dump all CPUs backtraces in a hung task event?
 * Defaults to 0, can be changed via sysctl.
 */
unsigned int __read_mostly sysctl_hung_task_all_cpu_backtrace;
#endif /* CONFIG_SMP */

sysctl_hung_task_all_cpu_backtrace 可以由 sysctl 或 /proc/sys/kernel/hung_task_all_cpu_backtrace 读、写。

本地 Android 设备的 /proc/sys/kernel/hung_task_all_cpu_backtrace 值也是 0。

yudi:/ # cat /proc/sys/kernel/hung_task_all_cpu_backtrace
0

注意,sysctl_hung_task_all_cpu_backtrace 要跟 sysctl_hung_task_warnings 结合使用,即只有在 hung task 警告使能时,sysctl_hung_task_all_cpu_backtrace 才有效。

3.2 huang task 检查

huang task 检查的核心是 check_hung_task 方法。

// kernel/hung_task.c

static void check_hung_task(struct task_struct *t, unsigned long timeout)
{
        // 目标 task 状态切换次数
        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.
         */
         // 如果目标 task 状态切换次数为 0,也忽略(说明该 task 应该是刚创建的)
        if (unlikely(!switch_count))
                return;

        // 如果目标 task 状态切换次数相比上一次 check 时发生了变化,也忽略(说明 task 唤醒过了,不是一直是 D 状态)
        if (switch_count != t->last_switch_count) {
                t->last_switch_count = switch_count;
                t->last_switch_time = jiffies;
                return;
        }
        // time_is_after_jiffies 检查目标时间是否在当前时间之后,
        // 这里即表示检查当前是否还没到 hungtask 超时时间,如果没到超时的话,也忽略
        if (time_is_after_jiffies(t->last_switch_time + timeout * HZ))
                return;

        // 到这里就表示目标 task 已经被确定为 hungtask 了!
        trace_sched_process_hang(t);

        // 如果 sysctl_hung_task_panic 使能了,则将变量 hung_task_show_lock 和 hung_task_call_panic 置为 true。
        // 1. 设置变量 hung_task_show_lock 为 true,会在后面(check_hung_task 方法的调用函数中)控制打印所有 task 的持锁信息
        // 2. 设置变量 hung_task_call_panic 为 true,会在后面(check_hung_task 方法的调用函数中)控制触发 panic
        if (sysctl_hung_task_panic) {
                console_verbose();   // 设置向控制台输出日志的等级
                hung_task_show_lock = true;
                hung_task_call_panic = true;
        }

        /*
         * Ok, the task did not get scheduled for more than 2 minutes,
         * complain:
         */
        // sysctl_hung_task_warnings 表示 hungtask 警告次数!
        // 如果 sysctl_hung_task_warnings 使能了(数值 > 0),则
        // 1. 将 sysctl_hung_task_warnings 减 1
        // 2. 打印 error log,记录 hungtask 的 task 名、task pid,以及持续了多长时间的 block 状态
        // 3. 打印 error log,提示用户可以通过 echo 0 > /proc/sys/kernel/hung_task_timeout_secs 关闭 hungtask 检查
        // 4. 设置变量 hung_task_show_lock 值为 true
        // 5. 调用 sched_show_task 方法打印 hungtask 任务的状态、内核态调用栈、调度信息(调度策略等)
        // 6. 如果 sysctl_hung_task_all_cpu_backtrace 使能,则设置变量 hung_task_show_all_bt 值为 true,这个变量会在后面(check_hung_task 方法的调用函数中)控制打印所有 cpu 上正在执行的任务的调用栈
        if (sysctl_hung_task_warnings) {
                if (sysctl_hung_task_warnings > 0)
                        sysctl_hung_task_warnings--;
                pr_err("INFO: task %s:%d blocked for more than %ld seconds.\n",
                       t->comm, t->pid, (jiffies - t->last_switch_time) / HZ);
                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);
                hung_task_show_lock = true;

                if (sysctl_hung_task_all_cpu_backtrace)
                        hung_task_show_all_bt = true;
        }

        touch_nmi_watchdog();
}

下面是一些需要注意的点。

1. time_is_after_jiffies 方法的作用是检查目标时间是否在当前时间之后。

2. sched_show_task 方法的作用是打印目标 task 的状态、内核态调用栈、调度信息(调度策略等)

3. console_verbose 方法设置向控制台输出日志的等级

4. 变量 hung_task_show_lock 控制打印所有 task 的持锁信息;

变量 hung_task_call_panic 控制触发 panic;

变量 hung_task_show_all_bt 控制打印所有 cpu 上正在执行的任务的调用栈。

这些信息都是在 check_hung_task 的调用者 check_hung_uninterruptible_tasks 函数中打印的。

sysctl_hung_task_panic 使能时,会使能 hung_task_show_lock 和 hung_task_call_panic;

sysctl_hung_task_warnings 使能时,会使能 hung_task_show_lock,可能会使能 hung_task_show_all_bt。

5. 为什么每次 check_hung_task 最后都要执行一次 touch_nmi_watchdog ?

check_hung_task 对一个目标 task 进行 hungtask 检查,也算是耗时动作;遍历所有 task 调用 check_hung_task 的话耗时很长,所以每次检查完一个 task 就调用一次 touch_nmi_watchdog 忽略 hardlockup。

类似的还有 lockdep 模块的 debug_show_all_locks 方法,它在遍历所有 task 打印持锁信息时,每次遍历都会执行 touch_nmi_watchdog 忽略 hardlockup 检查,以及执行 touch_all_softlockup_watchdogs 忽略 softlockup 检查。

3.3 打印 hungtask debug 信息

// kernel/hung_task.c

/*
 * Check whether a TASK_UNINTERRUPTIBLE does not get woken up for
 * a really long time (120 seconds). If that happens, print out
 * a warning.
 */
static void check_hung_uninterruptible_tasks(unsigned long timeout)
{
        int max_count = sysctl_hung_task_check_count;
        unsigned long last_break = jiffies;
        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;

        hung_task_show_lock = false;
        rcu_read_lock();
        for_each_process_thread(g, t) {
                if (!max_count--)
                        goto unlock;
                if (time_after(jiffies, last_break + HUNG_TASK_LOCK_BREAK)) {
                        if (!rcu_lock_break(g, t))
                                goto unlock;
                        last_break = jiffies;
                }
                /* use "==" to skip the TASK_KILLABLE tasks waiting on NFS */
                if (t->state == TASK_UNINTERRUPTIBLE)
                        check_hung_task(t, timeout);
        }
 unlock:
        rcu_read_unlock();
        // 打印所有 task 的持锁信息
        if (hung_task_show_lock)
                debug_show_all_locks();

        // 打印各 CPU 上正在运行 task 的调用栈
        if (hung_task_show_all_bt) {
                hung_task_show_all_bt = false;
                trigger_all_cpu_backtrace();
        }

        // 触发 panic
        if (hung_task_call_panic)
                panic("hung_task: blocked tasks");
}

需要注意,lockdep 模块的 debug_show_all_locks 方法会打印所有 task 的持锁信息。

3.4 enhance

大米对  hung task 检查做了 enhance,有机会再专门讲。

yudi:/ # ls -l /proc/sys/hung_task_enh/
total 0
-rw-r--r-- 1 root root 0 2024-07-10 11:10 global_detect_mode
-rw-r--r-- 1 root root 0 2024-07-10 11:10 max_iowait_task_cnt
-rw-r--r-- 1 root root 0 2024-07-10 11:10 max_iowait_timeout_cnt
-rw-r--r-- 1 root root 0 2024-07-10 11:10 per_task_detect_mode
-rw-r--r-- 1 root root 0 2024-07-10 11:10 read_pid

相关推荐

  1. hung hung task 检测

    2024-07-17 04:42:07       20 阅读
  2. Pytorch项目,肺癌检测项目

    2024-07-17 04:42:07       53 阅读

最近更新

  1. docker php8.1+nginx base 镜像 dockerfile 配置

    2024-07-17 04:42:07       66 阅读
  2. Could not load dynamic library ‘cudart64_100.dll‘

    2024-07-17 04:42:07       70 阅读
  3. 在Django里面运行非项目文件

    2024-07-17 04:42:07       57 阅读
  4. Python语言-面向对象

    2024-07-17 04:42:07       68 阅读

热门阅读

  1. jdk21 future 异步线程 等待

    2024-07-17 04:42:07       21 阅读
  2. ubuntu使用vcan做本地测试

    2024-07-17 04:42:07       24 阅读
  3. ARP协议

    2024-07-17 04:42:07       25 阅读
  4. 基于Go1.19的站点模板爬虫

    2024-07-17 04:42:07       24 阅读
  5. 刷题Day54|99. 岛屿数量、100. 岛屿的最大面积

    2024-07-17 04:42:07       26 阅读
  6. 日耗100和100W投手思维的区别

    2024-07-17 04:42:07       20 阅读
  7. C语言经典程序100案例

    2024-07-17 04:42:07       18 阅读
  8. 【数据结构】顺序表

    2024-07-17 04:42:07       19 阅读
  9. 类和对象(2

    2024-07-17 04:42:07       28 阅读