HungTask检测
基于msm-4.4
一、简介
处于D状态的进程不能接收信号,kill不掉,又可能导致永远卡住,因此才需要检测它。检测逻辑是若一个任务持续120秒都是D状态,则认为是hung住了,然后根据配置决定是否进行打印和panic。会打印出D状态hung住的任务的内核栈,若使能了Lockdep还会打印其持有的所有内核锁。
Kconfig 配置文件 lib/Kconfig.debug,配置与使能:CONFIG_DETECT_HUNG_TASK
config DETECT_HUNG_TASK bool "Detect Hung Tasks" depends on DEBUG_KERNEL default LOCKUP_DETECTOR help 在此处选择 Y 即可启用内核检测“hung tasks”的功能。这些错误会导致任务无限期地卡在不可中断的“D”状态。 检测到hung住的任务时,内核会打印当前堆栈跟踪(您应该报告),但该任务仍将保持不可中断状态。如果启用了 lockdep,则持有的所有锁也将被报告。此功能的开销几乎可以忽略不计。
同文件中所有相关配置:
CONFIG_DETECT_HUNG_TASK: 使能hungtask检测功能。
CONFIG_DEFAULT_HUNG_TASK_TIMEOUT: 此宏的值配置hung task的默认超时时间(单位秒),默认值是120. 此超时时间还可以通过 /proc/sys/kernel/hung_task_timeout_secs 文件进行配置。若设置为0则关闭hungtask检测功能。
CONFIG_BOOTPARAM_HUNG_TASK_PANIC: 当检测到hungtask时,配置是否panic.
CONFIG_BOOTPARAM_HUNG_TASK_PANIC_VALUE: 其值依赖于 CONFIG_BOOTPARAM_HUNG_TASK_PANIC 是否使能,若使能则为1否则为0,会默认赋值给 sysctl_hung_task_panic 变量,当检测到 hung task 时进行panic。
实现文件: kernel/hung_task.c
二、相关结构
1. struct task_struct
struct task_struct { unsigned long last_switch_count; };
last_switch_count: 记录上次检测时此D状态任务的切换次数。若过120秒后再次检测还是D状态,且切换次数没变化,就认为是一直卡在这个D状态了,就认为是 HangTask 了。
二、实现
1. 初始化
static int __init hung_task_init(void) { /* 注册通知,当内核panic时在 panic()中回调,回调函数中只是将 did_panic=1 */ atomic_notifier_chain_register(&panic_notifier_list, &panic_block); /* 运行内核线程 khungtaskd, 其执行方法体为 watchdog() CFS-120 默认不绑核 */ watchdog_task = kthread_run(watchdog, NULL, "khungtaskd"); return 0; } subsys_initcall(hung_task_init);
2. 检测逻辑
2.1 watchdog()
static int watchdog(void *dummy) { /* 设置当前 khungtaskd 内核线程的nice为0,即普通优先级,为了不影响业务运行 */ set_user_nice(current, 0); for ( ; ; ) { /* 轮询周期可通过 /proc/sys/kernel/hung_task_timeout_secs 控制,默认为 120s */ unsigned long timeout = sysctl_hung_task_timeout_secs; /* 先休眠120秒后再醒过来,若timeout为0就永久休眠不检测了 */ while (schedule_timeout_interruptible(timeout_jiffies(timeout))) timeout = sysctl_hung_task_timeout_secs; //120 /* * 原子地将 reset_hung_task=0. 并返回 reset_hung_task 的旧值,若为1就放弃此轮检测, * 检索整个内核,没有设1的位置,其值恒为0。 */ if (atomic_xchg(&reset_hung_task, 0)) continue; /* 醒来后执行实际的检测操作 */ check_hung_uninterruptible_tasks(timeout); } return 0; }
2.2 check_hung_uninterruptible_tasks()
/* 检查 TASK_UNINTERRUPTIBLE 是否在很长一段时间(120 秒)内未被唤醒。*/ static void check_hung_uninterruptible_tasks(unsigned long timeout) { int max_count = sysctl_hung_task_check_count; //默认值是 PID_MAX_LIMIT=4194304 unsigned long last_break = jiffies; struct task_struct *g, *t; /* 如果系统已经崩溃,就不要报告hung tasks. did_panic 在 panic() 中的回调中设置为1. */ if (test_taint(TAINT_DIE) || did_panic) return; hung_task_cnt = 0; rcu_read_lock(); /* 遍历系统中的所有线程 */ for_each_process_thread(g, t) { /* 可以配置每次RCU持锁遍历的任务个数, 默认不起作用 */ if (!max_count--) goto unlock; /* 默认 HZ/10 即0.1秒,即还有一个周期100ms的释放RCU锁的操作 */ if (time_after(jiffies, last_break + HUNG_TASK_LOCK_BREAK)) { /* 释放一下RCU锁并主动将自己以被抢占的状态切走,以免较长时间阻塞RCU宽限期 */ if (!rcu_lock_break(g, t)) goto unlock; /* 更新时间,以便下轮100ms后检测 */ last_break = jiffies; } /* 只检测纯D状态,不包括 TASK_KILLABLE 和 TASK_IDLE #### */ if (t->state == TASK_UNINTERRUPTIBLE) check_hung_task(t, timeout); } unlock: rcu_read_unlock(); }
2.3 check_hung_task()
/* 进来之前,已经将 hung_task_cnt=0 了。对系统中的每一个D状态的任务都进行调用 */ static void check_hung_task(struct task_struct *t, unsigned long timeout) { /* 进程上下文切换计数,以此来判断该进程是否发生过调度 */ unsigned long switch_count = t->nvcsw + t->nivcsw; /* * 确保任务未被冻结。此外,跳过 vfork 和 freezer 需要跳过的用户进程。 * freezer1.0任务被冻结后长时间处于D状态可能会误报。 */ if (unlikely(t->flags & (PF_FROZEN | PF_FREEZER_SKIP))) return; /* 跳过新建的还一次都没切换过的任务 */ if (unlikely(!switch_count)) return; /* 虽然间隔120秒的两次检测都是D状态,但是中途任务有切换过,也不能说明长时间卡D状态,退出对此任务的继续检测 */ if (switch_count != t->last_switch_count) { t->last_switch_count = switch_count; return; } /* 只打印了 t->comm 和 t->pid */ trace_sched_process_hang(t); /* * hung_task_cnt 是每次检测时设置为0(周期120s),只有D状态的任务才会进到这个函数,即每轮检测最多只允 * 许报2个任务. #### */ hung_task_cnt++; if (hung_task_cnt < 3) return; /* * 若通过 /proc/sys/kernel/hung_task_warnings(默认是3) 若允许报警告的次数用完了,#### * 即使检测到长时间D状态的任务也不报警告,这个配置每次重启机器最大允许报的次数。#### * TODO: 这里可以加个tain标志,以免报告次数用完了,发现不了问题了。 */ if (!sysctl_hung_task_warnings) return; if (sysctl_hung_task_warnings > 0) sysctl_hung_task_warnings--; /* 下面开始打印持续120s都是D状态的任务了 */ pr_err("hung_task_cnt:%d\n", hung_task_cnt); //此轮检测报的第几个长时间D状态的任务 pr_err("INFO: task %s:%d blocked for more than %ld seconds.\n", t->comm, t->pid, timeout); //默认timeout=120s 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"); /* 打印这个休眠状态的任务的堆栈,实测即使t运行在其它cpu上,也能打印出来(比较奇怪, 打印curr打印不出来) */ sched_show_task(t); /* 若使能了 Lockdep(一般默认不使能), 会打印进程持有的所有内核锁 #### */ debug_show_held_locks(t); /* 防止在此过程中触发 nmi_watchdog */ touch_nmi_watchdog(); /* 检测如果配置了panic则直接触发panic, 可通过 /proc/sys/kernel/hung_task_panic 进行配置 */ if (sysctl_hung_task_panic) { /* 向所有的 oneline cpus 发送 IPI_CPU_BACKTRACE 中断,然后忙等这些cpu执行完栈回溯。然后在每个CPU上执行 show_regs(regs) 获取栈回溯 */ trigger_all_cpu_backtrace(); panic("hung_task: blocked tasks"); } }
三、调试接口
1. 启动参数
1.1 hung_task_panic
static int __init hung_task_panic_setup(char *str) { kstrtouint(str, 0, &sysctl_hung_task_panic); } __setup("hung_task_panic=", hung_task_panic_setup);
用于决定检测到 hung task 后是否报panic.
2. 文件节点
2.1 /proc/sys/kernel/hung_task_check_count
用于配置 sysctl_hung_task_check_count, 表示每轮检测遍历的最大任务数量。这个功能没实现好,若配置的较小可能会导致有些任务始终遍历不到。需要保持默认值不要改。
2.2 /proc/sys/kernel/hung_task_panic
用于配置 sysctl_hung_task_panic 的值,表示检测到 hung task 后是否触发panic重启。
2.3 /proc/sys/kernel/hung_task_timeout_secs
用于配置 sysctl_hung_task_timeout_secs 的值,表示检测时间间隔,默认120秒,若持续这个时间都恒为D状态
2.4 /proc/sys/kernel/hung_task_warnings
用于配置 sysctl_hung_task_warnings 的值,默认是3,它是个计数值,表示每次重启机器最多支持检测多少个hung task,若是次数用完了,即使检测到也不再有任何动作了。
posted on 2025-07-17 16:00 Hello-World3 阅读(49) 评论(0) 收藏 举报
浙公网安备 33010602011771号