기능 개요
Linux 커널 5.10.0 버전에서 hungtask 기능은 시스템 내 모든 프로세스 스레드를 주기적으로 점검하여, TASK_UNINTERRUPTIBLE 상태에 지속적으로 머무르는 비정상적인 작업을 탐지합니다. 이 상태가 설정된 시간(기본값: 120초) 이상 지속되면 경고를 출력하거나 시스템을 패닉시킵니다.
초기화 과정
hung_task_init() 함수는 커널 부팅 시 실행되며, khungtaskd라는 백그라운드 내부 스레드를 생성하고, 패닉 발생 시 중복 검사를 방지하기 위해 panic_notifier_list에 등록합니다. 또한 시스템 절전 모드 전후에 감지를 일시 정지/재개할 수 있도록 pm_notifier를 통해 이벤트 핸들러를 등록합니다.
static int __init hung_task_init(void)
{
atomic_notifier_chain_register(&panic_notifier_list, &panic_block);
pm_notifier(hungtask_pm_notify, 0);
watchdog_task = kthread_run(watchdog, NULL, "khungtaskd");
return 0;
}
subsys_initcall(hung_task_init);
감지 스레드 동작 흐름
khungtaskd 스레드는 watchdog() 함수를 반복 실행하며, 설정된 체크 간격(sysctl_hung_task_check_interval_secs)마다 상태를 점검합니다. 단, 다음 조건이 충족될 경우 점검을 건너뜁니다:
reset_hung_task플래그가 활성화된 경우 (외부에서 재설정 요청)- 시스템이 절전 상태일 때 (
hung_detector_suspended가 참)
static int watchdog(void *dummy)
{
unsigned long hung_last_checked = jiffies;
set_user_nice(current, 0);
for ( ; ; ) {
unsigned long timeout = sysctl_hung_task_timeout_secs;
unsigned long interval = sysctl_hung_task_check_interval_secs;
if (interval == 0)
interval = timeout;
interval = min_t(unsigned long, interval, timeout);
long t = hung_timeout_jiffies(hung_last_checked, interval);
if (t <= 0) {
if (!atomic_xchg(&reset_hung_task, 0) && !hung_detector_suspended)
check_hung_uninterruptible_tasks(timeout);
hung_last_checked = jiffies;
continue;
}
schedule_timeout_interruptible(t);
}
return 0;
}
작업 상태 점검 로직
check_hung_uninterruptible_tasks() 함수는 RCU 잠금을 안전하게 유지하면서 모든 스레드를 순회하며 TASK_UNINTERRUPTIBLE 상태인 항목만 대상으로 합니다. 최대 sysctl_hung_task_check_count개의 스레드까지만 점검하여 시스템 성능 저하를 방지합니다.
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 (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;
}
if (t->state == TASK_UNINTERRUPTIBLE)
check_hung_task(t, timeout);
}
unlock:
rcu_read_unlock();
if (hung_task_show_lock)
debug_show_all_locks();
if (hung_task_show_all_bt) {
hung_task_show_all_bt = false;
trigger_all_cpu_backtrace();
}
if (hung_task_call_panic)
panic("hung_task: blocked tasks");
}
비정상 상태 판단 기준
check_hung_task()는 특정 스레드의 컨텍스트 스위치 횟수 변화 여부를 기반으로 판단합니다. 만약 현재 스위치 카운트와 이전 값이 동일하고, 그 이후 시간이 timeout 초를 초과하면, 해당 스레드는 장시간 블록 상태라고 판단됩니다.
- 주동 스위치: I/O 대기, 세마포어/락 대기,
schedule()호출 등 - 수동 스위치: 시간 슬라이스 소진, 다른 프로세스에 의해 강제 중단됨
static void check_hung_task(struct task_struct *t, unsigned long timeout)
{
unsigned long switch_count = t->nvcsw + t->nivcsw;
if (unlikely(t->flags & (PF_FROZEN | PF_FREEZER_SKIP)))
return;
if (unlikely(!switch_count))
return;
if (switch_count != t->last_switch_count) {
t->last_switch_count = switch_count;
t->last_switch_time = jiffies;
return;
}
if (time_is_after_jiffies(t->last_switch_time + timeout * HZ))
return;
trace_sched_process_hang(t);
if (sysctl_hung_task_panic) {
console_verbose();
hung_task_show_lock = true;
hung_task_call_panic = true;
}
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();
}
보조 기능 및 처리
경고 메시지 출력, 데드락 정보 표시, 전체 CPU 백트레이스 수집 등의 기능은 hung_task_show_lock, hung_task_show_all_bt 플래그를 통해 제어됩니다. 최종적으로 sysctl_hung_task_panic가 켜져 있으면 시스템을 패닉 시켜 오류를 강제 종료합니다.
또한 touch_nmi_watchdog() 호출을 통해 인터럽트 없는 시스템 감시자(예: NMI 워치독)의 타임아웃을 리셋함으로써, 실제 하드웨어 고장과 혼동되는 상황을 피합니다.