본문 바로가기

Core BSP 분석/리눅스 커널 핵심 분석

CONFIG_DETECT_HUNG_TASK

#ifdef CONFIG_DETECT_HUNG_TASK
extern unsigned int  sysctl_hung_task_panic;
extern unsigned long sysctl_hung_task_check_count;
extern unsigned long sysctl_hung_task_timeout_secs;
extern unsigned long sysctl_hung_task_warnings;
extern int proc_dohung_task_timeout_secs(struct ctl_table *table, int write,
                     void __user *buffer,
                     size_t *lenp, loff_t *ppos);
#else
/* Avoid need for ifdefs elsewhere in the code */
enum { sysctl_hung_task_timeout_secs = 0 };
#endif


static struct ctl_table kern_table[] = {
...
#ifdef CONFIG_DETECT_HUNG_TASK
    {
        .procname    = "hung_task_panic",
        .data        = &sysctl_hung_task_panic,
        .maxlen        = sizeof(int),
        .mode        = 0644,
        .proc_handler    = proc_dointvec_minmax,
        .extra1        = &zero,
        .extra2        = &one,
    },
    {
        .procname    = "hung_task_check_count",
        .data        = &sysctl_hung_task_check_count,
        .maxlen        = sizeof(unsigned long),
        .mode        = 0644,
        .proc_handler    = proc_doulongvec_minmax,
    },
    {
        .procname    = "hung_task_timeout_secs",
        .data        = &sysctl_hung_task_timeout_secs,
        .maxlen        = sizeof(unsigned long),
        .mode        = 0644,
        .proc_handler    = proc_dohung_task_timeout_secs,
    },
    {
        .procname    = "hung_task_warnings",
        .data        = &sysctl_hung_task_warnings,
        .maxlen        = sizeof(unsigned long),
        .mode        = 0644,
        .proc_handler    = proc_doulongvec_minmax,
    },
#endif

static int copy_mm(unsigned long clone_flags, struct task_struct *tsk)
{
    struct mm_struct *mm, *oldmm;
    int retval;

    tsk->min_flt = tsk->maj_flt = 0;
    tsk->nvcsw = tsk->nivcsw = 0;
#ifdef CONFIG_DETECT_HUNG_TASK
    tsk->last_switch_count = tsk->nvcsw + tsk->nivcsw;
#endif
..
}

/*
 * Detect Hung Task
 *
 * kernel/hung_task.c - kernel thread for detecting tasks stuck in D state
 *
 */

#include <linux/mm.h>
#include <linux/cpu.h>
#include <linux/nmi.h>
#include <linux/init.h>
#include <linux/delay.h>
#include <linux/freezer.h>
#include <linux/kthread.h>
#include <linux/lockdep.h>
#include <linux/export.h>
#include <linux/sysctl.h>

/*
 * The number of tasks checked:
 */
unsigned long __read_mostly sysctl_hung_task_check_count = PID_MAX_LIMIT;

/*
 * Limit number of tasks checked in a batch.
 *
 * This value controls the preemptibility of khungtaskd since preemption
 * is disabled during the critical section. It also controls the size of
 * the RCU grace period. So it needs to be upper-bound.
 */
#define HUNG_TASK_BATCHING 1024

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

unsigned long __read_mostly sysctl_hung_task_warnings = 10;

static int __read_mostly did_panic;

static struct task_struct *watchdog_task;

/*
 * 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)
{
    sysctl_hung_task_panic = simple_strtoul(str, NULL, 0);

    return 1;
}
__setup("hung_task_panic=", hung_task_panic_setup);

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,
};

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) {
        t->last_switch_count = switch_count;
        return;
    }
    if (!sysctl_hung_task_warnings)
        return;
    sysctl_hung_task_warnings--;

    /*
     * Ok, the task did not get scheduled for more than 2 minutes,
     * complain:
     */
    printk(KERN_ERR "INFO: task %s:%d blocked for more than "
            "%ld seconds.\n", t->comm, t->pid, timeout);
    printk(KERN_ERR "\"echo 0 > /proc/sys/kernel/hung_task_timeout_secs\""
            " disables this message.\n");
    sched_show_task(t);
    debug_show_held_locks(t);

    touch_nmi_watchdog();

    if (sysctl_hung_task_panic) {
        trigger_all_cpu_backtrace();
        panic("hung_task: blocked tasks");
    }
}

/*
 * To avoid extending the RCU grace period for an unbounded amount of time,
 * periodically exit the critical section and enter a new one.
 *
 * For preemptible RCU it is sufficient to call rcu_read_unlock in order
 * to exit the grace period. For classic RCU, a reschedule is required.
 */
static bool rcu_lock_break(struct task_struct *g, struct task_struct *t)
{
    bool can_cont;

    get_task_struct(g);
    get_task_struct(t);
    rcu_read_unlock();
    cond_resched();
    rcu_read_lock();
    can_cont = pid_alive(g) && pid_alive(t);
    put_task_struct(t);
    put_task_struct(g);

    return can_cont;
}

/*
 * 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;
    int batch_count = HUNG_TASK_BATCHING;
    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();
    do_each_thread(g, t) {
        if (!max_count--)
            goto unlock;
        if (!--batch_count) {
            batch_count = HUNG_TASK_BATCHING;
            if (!rcu_lock_break(g, t))
                goto unlock;
        }
        /* use "==" to skip the TASK_KILLABLE tasks waiting on NFS */
        if (t->state == TASK_UNINTERRUPTIBLE)
            check_hung_task(t, timeout);
    } while_each_thread(g, t);
 unlock:
    rcu_read_unlock();
}

static unsigned long timeout_jiffies(unsigned long timeout)
{
    /* timeout of 0 will disable the watchdog */
    return timeout ? timeout * HZ : MAX_SCHEDULE_TIMEOUT;
}

/*
 * Process updating of timeout sysctl
 */
int proc_dohung_task_timeout_secs(struct ctl_table *table, int write,
                  void __user *buffer,
                  size_t *lenp, loff_t *ppos)
{
    int ret;

    ret = proc_doulongvec_minmax(table, write, buffer, lenp, ppos);

    if (ret || !write)
        goto out;

    wake_up_process(watchdog_task);

 out:
    return ret;
}

/*
 * kthread which checks for tasks stuck in D state
 */
static int watchdog(void *dummy)
{
    set_user_nice(current, 0);

    for ( ; ; ) {
        unsigned long timeout = sysctl_hung_task_timeout_secs;

        while (schedule_timeout_interruptible(timeout_jiffies(timeout)))
            timeout = sysctl_hung_task_timeout_secs;

        check_hung_uninterruptible_tasks(timeout);
    }

    return 0;
}

static int __init hung_task_init(void)
{
    atomic_notifier_chain_register(&panic_notifier_list, &panic_block);
    watchdog_task = kthread_run(watchdog, NULL, "khungtaskd");

    return 0;
}

module_init(hung_task_init);

'Core BSP 분석 > 리눅스 커널 핵심 분석' 카테고리의 다른 글

stack_overflow_check  (0) 2023.05.06
Debug config  (0) 2023.05.06
gdt_page  (0) 2023.05.06
process state  (0) 2023.05.06
cmm: thread dump  (0) 2023.05.05