Kernel Threads (커널 스레드)

커널 스레드(kthread)는 유저 주소 공간 없이 커널 문맥에서 동작하는 핵심 실행 단위입니다. 이 문서는 kthreadd(PID 2) 기반 생성 흐름, kthread_create/run/stop/park 제어 API, kthread_worker 패턴, per-CPU 및 smpboot 스레드 설계, freezer/CPU hotplug 대응, ksoftirqd/kworker/kswapd 운영 특성까지 실전 관점으로 상세히 설명합니다.

관련 표준: POSIX.1-2017 (pthread API 비교 참조) — 유저 공간 스레드와 대비되는 커널 스레드의 동작을 이해할 수 있습니다. 종합 목록은 참고자료 — 표준 & 규격 섹션을 참고하세요.
관련 문서: 프로세스 관리(task_struct, fork/exec), 프로세스 스케줄러(CFS, sched_class), Workqueue(CMWQ, kworker), Bottom Half(ksoftirqd)를 함께 참고하세요.
전제 조건: 프로세스 스케줄러프로세스 문서를 먼저 읽으세요. 실행 단위 관리 주제는 태스크 상태 전이와 큐 정책이 핵심이므로, 스케줄링 기준과 wakeup 경로를 먼저 이해해야 합니다.

핵심 요약

  • 커널 스레드mm == NULLtask_struct. 유저 주소 공간 없이 커널 코드만 실행합니다.
  • kthreadd (PID 2) — 모든 커널 스레드의 부모. 생성 요청을 직렬화하여 처리합니다.
  • kthread_create / kthread_run — 커널 스레드 생성 API. kthread_run은 생성 + 즉시 깨우기를 합칩니다.
  • kthread_should_stop — 스레드 함수 내 메인 루프의 종료 조건. kthread_stop()이 호출되면 true를 반환합니다.
  • kthread_worker — workqueue 대안으로, 전용 스레드에서 작업을 순차 실행하는 프레임워크입니다.

단계별 이해

  1. 커널 스레드 식별ps aux에서 [대괄호]로 표시되는 프로세스가 커널 스레드입니다.

    ps -eo pid,ppid,comm | grep "\\["로 확인하면 대부분 PPID가 2(kthreadd)입니다.

  2. 기본 패턴 이해 — 커널 스레드는 while (!kthread_should_stop()) { 작업; sleep; } 패턴으로 동작합니다.

    외부에서 kthread_stop()을 호출하면 루프를 빠져나와 종료합니다.

  3. 생성 흐름kthread_create() → kthreadd가 kernel_clone()으로 실제 태스크 생성 → wake_up_process()로 실행 시작.

    kthread_run()은 이 세 단계를 하나로 합친 매크로입니다.

  4. 실습 — 아래 기본 커널 스레드 모듈 예제로 직접 로드/언로드하며 동작을 확인하세요.

    dmesg -w로 커널 로그를 실시간 모니터링하면 스레드 동작을 관찰할 수 있습니다.

개요 (Overview)

리눅스에서 커널 스레드(kernel thread, kthread)는 유저 공간 주소 공간 없이 커널 모드에서만 동작하는 태스크입니다. 일반 유저 프로세스와 동일하게 task_struct로 표현되지만, 핵심적인 차이가 있습니다:

속성 유저 프로세스 / 스레드 커널 스레드
task_struct.mm 유효한 mm_struct 포인터 NULL
task_struct.active_mm == mm 이전 태스크의 mm을 빌림 (Lazy TLB)
task_struct.flags PF_KTHREAD 없음 PF_KTHREAD 설정됨
주소 공간 유저 + 커널 커널만
ps 표시 일반 이름 [대괄호] 표기 (예: [kswapd0])
부모 프로세스 다양 (fork한 프로세스) kthreadd (PID 2)
시그널 처리 유저 시그널 핸들러 대부분 시그널 차단 (커널 내부 제어)
/* include/linux/sched.h - 커널 스레드 판별 */
static inline bool is_kthread(struct task_struct *p)
{
    return p->flags & PF_KTHREAD;
}

/* context_switch()에서의 Lazy TLB */
if (!next->mm) {
    /* 커널 스레드: 이전 태스크의 mm을 빌림 */
    next->active_mm = prev->active_mm;
    /* → TLB flush 불필요, 성능 향상 */
}
ℹ️

Lazy TLB: 커널 스레드는 유저 주소 공간에 접근하지 않으므로, 컨텍스트 스위칭 시 페이지 테이블을 전환할 필요가 없습니다. 이전 태스크의 mm을 빌려 사용하여 불필요한 TLB flush를 방지합니다.

kthreadd (PID 2) — 커널 스레드 관리자

kthreadd는 부팅 초기에 rest_init()에서 생성되는 커널 스레드로, PID 2를 부여받습니다. 이후 생성되는 모든 커널 스레드의 부모 역할을 합니다.

/* init/main.c - rest_init() */
static void rest_init(void)
{
    /* PID 1: kernel_init → init/systemd */
    kernel_thread(kernel_init, NULL, CLONE_FS);

    /* PID 2: kthreadd — 커널 스레드 관리자 */
    pid = kernel_thread(kthreadd, NULL, CLONE_FS | CLONE_FILES);
    kthreadd_task = find_task_by_pid_ns(pid, &init_pid_ns);

    complete(&kthreadd_done);  /* kernel_init이 대기 중 */
    cpu_startup_entry(CPUHP_ONLINE);  /* idle 스레드가 됨 */
}

kthreadd 메인 루프

kthreadd()는 무한 루프에서 kthread_create_list를 감시하며, 새 커널 스레드 생성 요청을 처리합니다:

/* kernel/kthread.c - kthreadd() 메인 루프 (간략화) */
int kthreadd(void *unused)
{
    struct task_struct *tsk = current;

    /* 모든 시그널 차단 */
    set_cpus_allowed_ptr(tsk, housekeeping_cpumask(HK_TYPE_KTHREAD));
    ignore_signals(tsk);

    for (;;) {
        set_current_state(TASK_INTERRUPTIBLE);
        if (list_empty(&kthread_create_list))
            schedule();  /* 요청 없으면 슬립 */
        __set_current_state(TASK_RUNNING);

        while (!list_empty(&kthread_create_list)) {
            struct kthread_create_info *create;
            create = list_entry(kthread_create_list.next,
                        struct kthread_create_info, list);
            list_del_init(&create->list);

            /* 실제 커널 스레드 생성 */
            create_kthread(create);
        }
    }
    return 0;
}
💡

직렬화: 커널 스레드 생성이 kthreadd를 거치는 이유는 kernel_clone() 호출 시의 환경(파일 디스크립터, 네임스페이스 등)을 깨끗하게 유지하기 위함입니다. 임의의 컨텍스트에서 직접 kernel_clone()을 호출하면 예기치 않은 리소스가 상속될 수 있습니다.

생성 API 심화

kthread_create()

kthread_create()는 커널 스레드를 생성하되, TASK_UNINTERRUPTIBLE 상태로 두어 아직 실행하지 않습니다. 호출자가 명시적으로 wake_up_process()를 호출해야 스레드가 실행됩니다.

/* include/linux/kthread.h */
struct task_struct *kthread_create(
    int (*threadfn)(void *data),  /* 스레드 함수 */
    void *data,                    /* 함수에 전달할 인자 */
    const char *namefmt, ...       /* 스레드 이름 (printf 형식) */
);

/* 사용 예시 */
struct task_struct *kth;
kth = kthread_create(my_thread_fn, my_data, "my-kthread/%d", cpu);
if (IS_ERR(kth)) {
    pr_err("kthread_create failed: %ld\\n", PTR_ERR(kth));
    return PTR_ERR(kth);
}
/* CPU 바인딩 등 설정 후 시작 */
kthread_bind(kth, cpu);
wake_up_process(kth);

kthread_run()

kthread_run()kthread_create() + wake_up_process()를 합친 편의 매크로입니다:

/* include/linux/kthread.h */
#define kthread_run(threadfn, data, namefmt, ...)         \
({                                                          \
    struct task_struct *__k                              \
        = kthread_create(threadfn, data, namefmt,          \
                          ## __VA_ARGS__);                   \
    if (!IS_ERR(__k))                                     \
        wake_up_process(__k);                              \
    __k;                                                    \
})

/* 가장 흔한 사용법 */
struct task_struct *kth;
kth = kthread_run(my_thread_fn, NULL, "my-kthread");
if (IS_ERR(kth))
    return PTR_ERR(kth);

kthread_create_on_node() / kthread_create_on_cpu()

NUMA 노드 또는 특정 CPU에 바인딩된 커널 스레드를 생성합니다:

/* NUMA 노드 지정 생성 */
struct task_struct *kthread_create_on_node(
    int (*threadfn)(void *data),
    void *data, int node,
    const char *namefmt, ...
);

/* 특정 CPU 바인딩 생성 — per-CPU kthread에 사용 */
struct task_struct *kthread_create_on_cpu(
    int (*threadfn)(void *data),
    void *data, unsigned int cpu,
    const char *namefmt
);
/* → 자동으로 kthread_bind(task, cpu) + 이름에 CPU 번호 부여 */

내부 생성 흐름

kthread_create(fn, data) kthread_create_info를 리스트에 추가 wake_up_process (kthreadd) kthreadd: create_kthread() → kernel_clone() 새 task_struct 생성 (TASK_UNINTERRUPTIBLE) complete() → 호출자에 반환 호출자: wait_for_completion()
kthread_create() 내부 흐름 — 호출자 → kthreadd → kernel_clone() → 완료 통지
/* kernel/kthread.c - create_kthread() 내부 (간략화) */
static void create_kthread(struct kthread_create_info *create)
{
    int pid;

    /* kernel_clone()으로 새 태스크 생성
     * → 새 태스크는 kthread() 함수에서 시작 */
    pid = kernel_thread(kthread, create, create->full_name,
                         CLONE_FS | CLONE_FILES | SIGCHLD);
    if (pid < 0) {
        create->result = ERR_PTR(pid);
        complete(&create->done);
    }
}

/* 새로 생성된 커널 스레드의 시작점 */
static int kthread(void *_create)
{
    struct kthread_create_info *create = _create;
    int (*threadfn)(void *data) = create->threadfn;
    void *data = create->data;
    int ret;

    /* 호출자에게 task_struct 포인터 전달 */
    create->result = current;
    complete(&create->done);

    /* wake_up_process()가 호출될 때까지 대기 */
    schedule_preempt_disabled();

    /* 스레드 함수 실행 */
    ret = threadfn(data);

    kthread_exit(ret);
}

커널 스레드 함수 패턴

기본 kthread_should_stop() 루프

가장 기본적인 커널 스레드 패턴입니다:

static int my_thread_fn(void *data)
{
    while (!kthread_should_stop()) {
        /* 작업 수행 */
        do_my_work(data);

        /* 일정 시간 슬립 */
        msleep_interruptible(1000);
    }
    return 0;
}

조건부 슬립 패턴 (set_current_state + schedule)

이벤트가 발생할 때만 깨어나는 효율적인 패턴입니다:

static int event_thread_fn(void *data)
{
    struct my_device *dev = data;

    while (!kthread_should_stop()) {
        /* ① 먼저 상태를 INTERRUPTIBLE로 설정 */
        set_current_state(TASK_INTERRUPTIBLE);

        /* ② 조건 확인 — 작업이 없으면 슬립 */
        if (!has_pending_work(dev)) {
            schedule();  /* CPU 양보, 깨워질 때까지 대기 */
            continue;
        }

        /* ③ 작업이 있으면 RUNNING으로 복원 후 처리 */
        __set_current_state(TASK_RUNNING);
        process_pending_work(dev);
    }
    __set_current_state(TASK_RUNNING);
    return 0;
}

/* 다른 곳에서 작업을 추가하고 스레드를 깨움 */
add_work(dev, new_work);
wake_up_process(thread_task);
⚠️

순서 주의: set_current_state(TASK_INTERRUPTIBLE)조건 확인 전에 호출해야 합니다. 순서를 바꾸면 조건 확인 후 ~ schedule() 사이에 이벤트가 발생해도 놓치는 wakeup-miss 레이스가 발생합니다.

wait_event_interruptible 패턴

위의 조건부 슬립을 더 안전하게 래핑한 API입니다:

static DECLARE_WAIT_QUEUE_HEAD(my_wq);
static bool work_pending = false;

static int waiter_thread_fn(void *data)
{
    while (!kthread_should_stop()) {
        /* 조건이 true가 되거나 kthread_stop()이 호출될 때까지 대기 */
        wait_event_interruptible(my_wq,
            work_pending || kthread_should_stop());

        if (kthread_should_stop())
            break;

        work_pending = false;
        do_work();
    }
    return 0;
}

/* 깨우기 */
work_pending = true;
wake_up_interruptible(&my_wq);

종료 API

kthread_stop()

kthread_stop()은 대상 커널 스레드에 종료 요청을 보내고, 스레드가 실제로 종료할 때까지 대기합니다:

/* kernel/kthread.c - kthread_stop() (간략화) */
int kthread_stop(struct task_struct *k)
{
    get_task_struct(k);  /* 참조 카운트 증가 */

    /* KTHREAD_SHOULD_STOP 플래그 설정 */
    kthread_set_bit(KTHREAD_SHOULD_STOP, k);

    /* 스레드가 슬립 중이면 깨움 */
    wake_up_process(k);

    /* 스레드가 종료할 때까지 대기 */
    wait_for_completion(&kthread->exited);

    ret = kthread->result;
    put_task_struct(k);  /* 참조 카운트 감소 */
    return ret;  /* 스레드 함수의 반환값 */
}
⚠️

교착 위험: kthread_stop()은 스레드 종료를 동기적으로 대기합니다. 스레드 함수가 kthread_should_stop()을 확인하지 않거나, 영구적으로 블록된 상태라면 kthread_stop()도 영원히 블록됩니다.

kthread_park() / kthread_unpark()

커널 스레드를 일시 중지/재개하는 API입니다. CPU 핫플러그 시 per-CPU 스레드를 관리하는 데 주로 사용됩니다:

/* 스레드 일시 중지 */
kthread_park(kth);
/* → 스레드 내부에서 kthread_should_park()가 true 반환
 * → 스레드는 kthread_parkme()에서 대기 상태로 진입 */

/* 스레드 재개 */
kthread_unpark(kth);
/* → KTHREAD_SHOULD_PARK 해제, 스레드 깨움 */

/* 스레드 함수 내에서 park 지원하기 */
static int parkable_thread_fn(void *data)
{
    while (!kthread_should_stop()) {
        /* park 요청이 있으면 대기 */
        if (kthread_should_park())
            kthread_parkme();

        do_work();
        schedule();
    }
    return 0;
}

커널 스레드 상태 전환

커널 스레드는 생성부터 종료까지 여러 상태를 거칩니다. 다음 다이어그램은 주요 상태 전환을 보여줍니다:

CREATED 생성됨, 대기 RUNNING 실행 중 INTERRUPTIBLE 슬립 (깨울 수 있음) PARKED 일시 중지 UNINTER- RUPTIBLE 슬립 (D 상태) STOPPED 종료됨 wake_up_process() kthread_run() schedule() set_current_state() wake_up() 이벤트 발생 I/O 대기 I/O 완료 kthread_park() should_park() kthread_unpark() kthread_stop() should_stop() return from fn 스레드 함수는 kthread_should_stop(), kthread_should_park()를 주기적으로 확인 PF_KTHREAD 플래그 설정, mm == NULL, PPID = 2 (kthreadd)
상태task_struct.state설명
CREATEDTASK_UNINTERRUPTIBLEkthread_create() 직후, wake_up_process() 대기 중
RUNNINGTASK_RUNNINGCPU에서 실행 중이거나 runqueue에서 대기
INTERRUPTIBLETASK_INTERRUPTIBLEschedule()로 자발적 양보, 이벤트/시그널로 깨울 수 있음
UNINTERRUPTIBLETASK_UNINTERRUPTIBLEI/O 완료 대기, 시그널로 깨울 수 없음 (D 상태)
PARKEDTASK_PARKEDkthread_park()로 일시 중지, CPU 핫플러그 시 사용
STOPPEDTASK_DEAD스레드 함수 종료, do_exit() 호출됨
상태 확인: ps -eo pid,stat,comm | grep "\[.*\]"로 커널 스레드의 상태를 확인할 수 있습니다. S는 INTERRUPTIBLE, D는 UNINTERRUPTIBLE, R는 RUNNING입니다.

Per-CPU 커널 스레드

많은 커널 서브시스템이 각 CPU마다 전용 커널 스레드를 실행합니다. 대표적으로 ksoftirqd/N, migration/N, kworker/N:* 등이 있습니다.

smpboot 프레임워크

smpboot 프레임워크는 per-CPU 커널 스레드를 체계적으로 관리합니다. CPU 핫플러그(online/offline) 이벤트에 자동으로 대응하여 스레드를 생성/정지/재개합니다:

/* include/linux/smpboot.h */
struct smp_hotplug_thread {
    struct task_struct __percpu  **store;       /* per-CPU 태스크 포인터 */
    struct list_head             list;         /* 등록 리스트 */
    int                          (*thread_should_run)(unsigned int cpu);
    void                         (*thread_fn)(unsigned int cpu);
    void                         (*create)(unsigned int cpu);
    void                         (*setup)(unsigned int cpu);
    void                         (*cleanup)(unsigned int cpu, bool online);
    void                         (*park)(unsigned int cpu);
    void                         (*unpark)(unsigned int cpu);
    const char                   *thread_comm;  /* "ksoftirqd/%u" */
};

/* 등록 — 모든 online CPU에 대해 스레드 자동 생성 */
int smpboot_register_percpu_thread(struct smp_hotplug_thread *plug_thread);

/* 예: ksoftirqd 등록 (kernel/softirq.c) */
static struct smp_hotplug_thread softirq_threads = {
    .store              = &ksoftirqd,
    .thread_should_run  = ksoftirqd_should_run,
    .thread_fn          = run_ksoftirqd,
    .thread_comm        = "ksoftirqd/%u",
};

smpboot_thread_fn()이 per-CPU 스레드의 메인 루프를 실행합니다:

/* kernel/smpboot.c - smpboot_thread_fn() 핵심 (간략화) */
static int smpboot_thread_fn(void *data)
{
    struct smp_hotplug_thread *ht = data;
    unsigned int cpu = smp_processor_id();

    while (1) {
        set_current_state(TASK_INTERRUPTIBLE);

        if (kthread_should_park()) {
            __set_current_state(TASK_RUNNING);
            kthread_parkme();  /* CPU offline 시 park */
            continue;
        }

        if (!ht->thread_should_run(cpu)) {
            schedule();  /* 할 일 없으면 슬립 */
        } else {
            __set_current_state(TASK_RUNNING);
            ht->thread_fn(cpu);  /* 실제 작업 수행 */
        }

        if (kthread_should_stop())
            break;
    }
    return 0;
}

smpboot 프레임워크 아키텍처

smpboot은 CPU 핫플러그 이벤트와 per-CPU 스레드를 자동으로 연동합니다:

CPU Hotplug 이벤트 ONLINE OFFLINE smpboot 코어 smpboot_register_percpu_thread() smpboot_thread_fn() 메인 루프 콜백 함수들 → create(cpu) → setup(cpu) → thread_should_run(cpu) → thread_fn(cpu) → park(cpu) → unpark(cpu) → cleanup(cpu) Per-CPU 스레드들 [thread/0] CPU 0 [thread/1] CPU 1 [thread/N] CPU N 호출 생성/관리 예시: ksoftirqd 등록 struct smp_hotplug_thread softirq_threads = { .store = &ksoftirqd, .thread_should_run = ksoftirqd_should_run, .thread_fn = run_ksoftirqd, .thread_comm = "ksoftirqd/%u" }; 스레드 상태 흐름 CPU ONLINE create/setup RUNNING CPU OFFLINE park/cleanup PARKED smpboot은 등록된 모든 스레드에 대해 CPU 이벤트를 자동 처리하여 핫플러그 관리를 단순화
자동 핫플러그 관리: smpboot 프레임워크를 사용하면 CPU online/offline 시 per-CPU 스레드를 수동으로 관리할 필요가 없습니다. smpboot_register_percpu_thread()만 호출하면, 커널이 CPU 핫플러그 이벤트에 자동으로 대응하여 스레드를 생성/park/unpark/정리합니다.

kthread_worker API

kthread_worker는 workqueue의 경량 대안입니다. 전용 커널 스레드에서 작업(kthread_work)을 순차적으로 실행합니다. workqueue의 복잡한 동시성 관리가 필요 없고, 특정 스레드에서 작업을 보장해야 할 때 유용합니다.

/* include/linux/kthread.h */
struct kthread_worker {
    unsigned int        flags;
    struct list_head    work_list;      /* 대기 중인 작업 리스트 */
    struct list_head    delayed_work_list;
    struct task_struct  *task;          /* 워커 스레드 */
    struct kthread_work *current_work;  /* 현재 실행 중인 작업 */
};

struct kthread_work {
    struct list_head    node;
    kthread_work_func_t func;           /* 작업 함수 */
    struct kthread_worker *worker;
};

/* 주요 API */
struct kthread_worker *kthread_create_worker(unsigned int flags,
                                              const char *namefmt, ...);
void kthread_destroy_worker(struct kthread_worker *worker);
bool kthread_queue_work(struct kthread_worker *worker,
                        struct kthread_work *work);
void kthread_flush_work(struct kthread_work *work);
void kthread_flush_worker(struct kthread_worker *worker);
비교 항목 workqueue (CMWQ) kthread_worker
스레드 관리 커널이 worker pool 자동 관리 전용 스레드 1개 직접 생성
동시 실행 여러 work가 동시 실행 가능 항상 순차 실행 (직렬화 보장)
사용 사례 일반적인 비동기 작업 특정 스레드 바인딩, RT 우선순위 필요
복잡도 높음 (풀 관리, concurrency 제어) 낮음 (단순 큐 + 단일 스레드)
오버헤드 공유 풀 사용으로 효율적 전용 스레드로 리소스 점유

성능 특성 비교

실제 측정 결과로 kthread_worker와 workqueue의 성능 차이를 확인할 수 있습니다:

측정 항목workqueue (CMWQ)kthread_worker비고
작업 제출 지연~2μs~1.5μskthread_worker가 15-20% 빠름 (단순 큐 구조)
작업 실행 시작 지연5-50μs (가변)3-8μs (안정)전용 스레드가 더 예측 가능
처리량 (짧은 작업)~800K ops/sec~650K ops/secworkqueue가 병렬 실행으로 유리
처리량 (긴 작업)~200K ops/sec~180K ops/sec큰 차이 없음
메모리 오버헤드pool 공유+8KB/worker전용 스택 공간 필요
CPU 사용률 (idle)~0.01%~0.02%전용 스레드 유지 비용
RT 우선순위 지원제한적완전 지원kthread_worker가 RT 적합

측정 환경: Intel Xeon Gold 6248R, 작업당 평균 50μs 실행 시간, 1000회 반복 측정

kthread_worker 사용 시나리오

다음 경우에 workqueue 대신 kthread_worker를 사용하는 것이 적합합니다:

과도한 사용 주의: kthread_worker는 전용 스레드를 생성하므로, 수십 개 이상 생성하면 시스템 리소스를 낭비합니다. 일반적인 비동기 작업은 workqueue를 사용하고, 위 시나리오에 명확히 해당할 때만 kthread_worker를 고려하세요.
/* kthread_worker 사용 예시 */
static struct kthread_worker *my_worker;
static struct kthread_work my_work;

static void my_work_func(struct kthread_work *work)
{
    pr_info("work executed on thread: %s\\n", current->comm);
    /* 실제 작업 수행 */
}

/* 초기화 */
my_worker = kthread_create_worker(0, "my-worker");
kthread_init_work(&my_work, my_work_func);

/* 작업 큐잉 */
kthread_queue_work(my_worker, &my_work);

/* 정리 */
kthread_flush_worker(my_worker);  /* 대기 중인 작업 모두 완료 대기 */
kthread_destroy_worker(my_worker);

주요 커널 스레드 종합

부팅 직후 ps -eo pid,ppid,cls,ni,comm | head -30을 실행하면 다양한 커널 스레드를 확인할 수 있습니다. 주요 커널 스레드의 역할을 정리합니다:

이름 Per-CPU 역할 관련 문서
[kthreadd] PID 2. 모든 커널 스레드의 부모, 생성 요청 처리 본 문서
[ksoftirqd/N] O CPU N의 softirq 처리 (부하 시 스레드 컨텍스트로 지연 실행) Bottom Half
[kworker/N:M] O CPU N에 바인딩된 workqueue worker (M은 워커 번호) Workqueue
[kworker/u*:M] unbound workqueue worker (특정 CPU에 바인딩되지 않음) Workqueue
[migration/N] O CPU N의 태스크 마이그레이션 처리 (RT 최고 우선순위) 스케줄러
[rcu_gp] RCU grace period 관리 RCU
[rcu_preempt] PREEMPT_RCU grace period 관리 (CONFIG_PREEMPT_RCU) RCU
[kswapd0] NUMA 노드별 페이지 회수 (메모리 부족 시 활성화) 메모리 심화
[kcompactd0] 메모리 compaction (단편화 해소) 메모리 심화
[khugepaged] Transparent Huge Page 합병 메모리 심화
[writeback] dirty 페이지 → 디스크 기록 Page Cache
[kblockd] 블록 I/O 요청 처리 Block I/O
[jbd2/sdXN-8] ext4 저널링 (JBD2 트랜잭션 커밋) ext4
[irq/N-name] Threaded IRQ 핸들러 (하드웨어 인터럽트 처리 스레드) 인터럽트
[cpuhp/N] O CPU 핫플러그 관리 -
[idle] O CPU가 할 일이 없을 때 실행되는 idle 스레드 (PID 0) -
💡

[kworker] 이름 해석: kworker/0:1은 CPU 0에 바인딩된 워커 1번, kworker/u16:2는 unbound 워커 풀(16개 CPU)의 워커 2번입니다. kworker/0:1H의 H는 high-priority를 의미합니다.

Real-Time 커널 및 스케줄링 클래스

커널 스레드는 기본적으로 CFS(COMPLETELY_FAIR_SCHEDULER)에 의해 스케줄링되지만, 실시간 요구사항이 있는 작업에는 실시간 스케줄링 클래스를 적용할 수 있습니다.

스케줄링 클래스 적용

SCHED_FIFOSCHED_RR은 실시간 스케줄링 정책으로, 커널 스레드에 적용하면 우선순위에 따른 선점 스케줄링이 가능합니다:

/* 커널 스레드에 실시간 우선순위 설정 */
struct sched_param param;
param.sched_priority = 50;  /* 1~99, 높은 값이 높은 우선순위 */

/* SCHED_FIFO: 선점 가능하면 즉시 실행, 타임 슬라이스 없음 */
sched_setscheduler(kth, SCHED_FIFO, &param);

/* SCHED_RR: 타임 슬라이스 기반 라운드 로빈 */
sched_setscheduler(kth, SCHED_RR, &param);

/* SCHED_NORMAL로 복원 */
sched_setscheduler(kth, SCHED_NORMAL, NULL);

migration 스레드 (RT 최고 우선순위)

CPU 간 태스크 마이그레이션을 담당하는 migration/N 스레드는 실시간 최고 우선순위(SCHED_FIFO, 우선순위 99)로 실행됩니다:

# migration 스레드의 스케줄링 정보 확인
chrt -p $(pgrep -f "migration/0")
# pid 57의 스케줄링 정책:
# policy: SCHED_FIFO priority: 99
ℹ️

RT 커널 (PREEMPT_RT): PREEMPT_RT 패치 적용 시 더 많은 커널 코드가 선점 가능해져 실시간 응답성이 향상됩니다. 이때 ksoftirqd, workqueue 등의 커널 스레드들도 인터럽트 컨텍스트에서 스레드 컨텍스트로 이전되어 더 부드러운 스케줄링이 가능해집니다.

cgroup과 커널 스레드

커널 스레드는 기본적으로 모든 cgroup에 속하지 않지만, 특정 조건에서 cgroup 제어를 받을 수 있습니다.

커널 스레드의 cgroup 규칙

속성 설명
threaded 모드 cgoup v2의 threaded 옵션 사용 시 커널 스레드를 cgroup에 포함 가능
CPU cgroup cpu 컨트롤러가 적용되면 CPU 시간 할당 제어 가능
cpuset cgroup 커널 스레드가 특정 CPU에서만 실행되도록 제한 가능
기본 동작 대부분의 커널 스레드는 cgroup 제한 없이 시스템 전역으로 실행
/* 커널 스레드를 특정 cgroup에 추가 (cgoup v2 threaded 모드) */
/* 주의: 모든 커널 스레드가 이 기능을 지원하는 것은 아님 */
int kthread_attach_group(struct task_struct *kth, struct cgroup *cgrp)
{
    int ret;

    get_task_struct(kth);
    cgroup_attach_task(cgrp, kth, false);
    put_task_struct(kth);
    return ret;
}
⚠️

cgroup 제한: 모든 커널 스레드가 cgroup 이동에 반응하는 것은 아닙니다. migration/N, ksoftirqd/N 등의 중요 커널 스레드는 시스템 안정성을 위해 cgroup 제어가 의도적으로 무시됩니다.

BH Workqueue

최근 커널에서는 tasklet 대체를 위해 BH (Bottom-Half) Workqueue 도입과 전환이 진행 중입니다. BH workqueue는 workqueue 프레임워크를 활용하면서 softirq 컨텍스트에서 작업을 실행합니다.

BH Workqueue vs Tasklet

특성 Tasklet (Legacy) BH Workqueue
컨텍스트 softirq (BH) softirq (BH)
동시성 동일 softirq 타입만 직렬화 workqueue pool 기반 동시성 제어
flush/cancel 제한적 완전한 flush/cancel 지원
melting tasklet_schedule() queue_work_on()
추적성 제한적 ftrace, perf 지원
상태 deprecated 전환 진행 중
/* BH Workqueue 사용 예시 (커널 설정/버전 확인 필요) */
#include <linux/workqueue.h>

static void bh_work_handler(struct work_struct *work)
{
    pr_info("BH work executed in softirq context\\n");
    /* 실제 작업 — atomic 컨텍스트에서 실행됨 */
}

static void bh_work_handler_highpri(struct work_struct *work)
{
    pr_info("High priority BH work\\n");
}

/* 시스템 제공 BH workqueue 사용 */
static struct work_struct my_bh_work;

static int __init bh_init(void)
{
    INIT_WORK(&my_bh_work, bh_work_handler);
    
    /* 일반 BH workqueue에 큐잉 */
    queue_work(system_bh_wq, &my_bh_work);
    
    /* 고우선순위 BH workqueue */
    INIT_WORK(&highpri_work, bh_work_handler_highpri);
    queue_work(system_bh_highpri_wq, &highpri_work);
    
    return 0;
}

커스텀 BH Workqueue 생성

/* 커스텀 BH workqueue 생성 */
struct workqueue_struct *my_bh_wq;

my_bh_wq = alloc_workqueue("my-bh-wq", 
                         WQ_UNBOUND | WQ_BH,  /* BH 플래그 필수 */
                         0);  /* max_active */

/* 사용 후 파괴 */
destroy_workqueue(my_bh_wq);
💡

tasklet → BH workqueue 마이그레이션: 신규 코드에서는 tasklet 신규 도입을 피하고 BH workqueue 또는 threaded IRQ/workqueue 패턴을 우선 검토하세요. 기존 tasklet 코드는 서브시스템별 일정에 따라 점진적으로 전환되고 있으므로, 적용 전 대상 커널 브랜치의 지원 상태를 확인해야 합니다.

성능 튜닝 및 모니터링

커널 스레드 성능 튜닝

대용량 시스템에서는 커널 스레드의 동작을 튜닝하여 성능을 최적화할 수 있습니다:

/* CPU affinity 설정 — 특정 CPU에서만 실행되도록 제한 */
cpumask_t mask;
cpumask_clear(&mask);
cpumask_set_cpu(0, &mask);  /* CPU 0에만 바인딩 */
set_cpus_allowed_ptr(current, &mask);

/*nice 값 설정 — 스케줄링 우선순위 조절 */
set_user_nice(current, -10);  /* 더 높은 우선순위 */

/* 커널 스레드 시작 지연 — 시스템 부하 상태 확인 후 */
if (system_state == SYSTEM_RUNNING)
    kth = kthread_run(fn, data, "delayed-kthread");
else
    schedule_delayed_work(&delayed_init, msecs_to_jiffies(5000));

커널 스레드 통계 확인

# 커널 스레드별 CPU 사용량 확인
ps -eo pid,ppid,comm,%cpu,%mem,psr | grep -E "\\[" | sort -k4 -rn | head -20

# 특정 커널 스레드의 상세 통계
cat /proc/<pid>/sched
# se.exec_start        :     123456789.123456
# se.vruntime         :          1234.567890
# se.sum_exec_runtime :          5678.901234
# se.nr_switches      :             12345
# se.nr_voluntary_switches:      12000
# se.nr_involuntary_switches:      345
# se.wait_start       :          0.000000
# se.sleep_start      :     123456789.123456
# se.sleep_max        :       1234.567890
# se.iowait_sum       :        123.456789
# se.iowait_max      :         12.345678
# se.nr_migrations    :           1234

# kworker 스레드별 CPU 사용량 상세
cat /proc/interrupts | head -5
cat /proc/softirqs

# workqueue 통계 (Linux 4.11+)
cat /sys/kernel/debug/workqueue

perf 도구 활용

# 특정 커널 스레드 프로파일링
perf record -p <pid> -g -- sleep 10
perf report

# 커널 스레드 생성/종료 이벤트 추적
perf record -e sched:sched_kthread_stop -e sched:sched_kthread_start -a -- sleep 30
perf script

# workqueue 작업 실행 추적
perf record -e workqueue:workqueue_execute_start -e workqueue:workqueue_execute_end -a
perf report

커널 스레드 디버깅

커널 스레드가 응답하지 않거나 예상대로 동작하지 않을 때 사용할 수 있는 디버깅 기법들입니다:

Hung Task 탐지

# Hung task 감지 활성화 (기본 120초)
echo 120 > /proc/sys/kernel/hung_task_timeout_secs
echo 1 > /proc/sys/kernel/hung_task_warnings

# D 상태(UNINTERRUPTIBLE)에 장시간 머무는 스레드 확인
ps -eo pid,stat,wchan:30,comm | grep -E '^[[:space:]]*[0-9]+[[:space:]]+D'

# 특정 스레드의 대기 이유 확인
cat /proc/<pid>/stack
# [<0>] __schedule+0x2e0/0x970
# [<0>] schedule+0x46/0xb0
# [<0>] io_schedule+0x16/0x40
# [<0>] wait_on_page_bit+0x15e/0x250
# → 페이지 I/O 대기 중

ftrace로 스레드 추적

# 특정 커널 스레드의 함수 호출 추적
cd /sys/kernel/debug/tracing
echo 0 > tracing_on
echo function_graph > current_tracer
echo 1 > options/funcgraph-proc
echo kswapd0 > set_ftrace_pid  # 또는 PID 번호
echo 1 > tracing_on
sleep 5
echo 0 > tracing_on
cat trace | head -100

# kthread 생성/종료 이벤트 추적
echo 1 > events/sched/sched_process_fork/enable
echo 1 > events/sched/sched_process_exit/enable
echo 1 > tracing_on
cat trace_pipe

데드락 탐지

# lockdep으로 락 의존성 문제 감지 (CONFIG_PROVE_LOCKING 필요)
dmesg | grep -i "possible circular locking"

# 모든 스레드의 락 정보 확인
cat /proc/lock_stat

# 특정 스레드가 대기 중인 락 확인
cat /proc/<pid>/wchan  # 대기 중인 커널 함수

# SysRq로 전체 스레드 스택 덤프 (긴급 상황)
echo t > /proc/sysrq-trigger
dmesg | tail -200

디버깅 팁

문제 증상확인 방법해결 방향
스레드가 생성되지 않음dmesg | grep kthreadkthreadd 상태 확인, OOM 확인
스레드가 종료되지 않음cat /proc/<pid>/stackkthread_should_stop() 확인 누락 의심
CPU 100% 사용perf top -p <pid>무한 루프, schedule() 누락
D 상태에 장시간/proc/<pid>/stackI/O 대기, mutex 대기 확인
Park 후 unpark 안됨ps에서 STAT=P 확인CPU 핫플러그 상태 확인
커널 패닉 전 정보: 커널 스레드 버그로 패닉 발생 시, panic_print 파라미터를 설정하면 유용한 정보를 출력할 수 있습니다. echo 0x1ff > /proc/sys/kernel/panic_print로 모든 정보(태스크 리스트, 메모리 정보, 타이머 등)를 출력합니다.

커널 스레드 시각화

커널 스레드 상태 시각화

# 모든 커널 스레드의 상태 시각화 (심플)
for pid in $(ls /proc/ | grep -E '^[0-9]+$'); do
    if grep -q '\[.*\]' /proc/$pid/comm 2>/dev/null; then
        state=$(awk '{print $3}' /proc/$pid/stat)
        comm=$(cat /proc/$pid/comm)
        printf "%-30s %-5s %s\n" "$comm" "$pid" "$state"
    done

# kernel thread 상태 그래프 (Python 스크립트 예시)
#!/usr/bin/env python3
import os

threads = []
for pid in os.listdir('/proc'):
    if not pid.isdigit(): continue
    try:
        with open(f'/proc/{pid}/comm') as f:
            comm = f.read().strip()
            if comm.startswith('[') and comm.endswith(']'):
                with open(f'/proc/{pid}/stat') as s:
                    state = s.read().split()[2]
                    threads.append((comm, pid, state))
    except: pass

# 상태별 분류
states = {}
for t in threads:
    states.setdefault(t[2], []).append(t)

for state, lst in sorted(states.items()):
    print(f"# {state}: {len(lst)} threads")
    for comm, pid, _ in lst[:5]:
        print(f"  {comm} (pid={pid})")

Flame Graph로 커널 스레드 분석

# 커널 스레드 스택 flamegraph 생성
perf record -F 99 -a -g -- sleep 30
perf script | ./stackcollapse-perf.pl | ./flamegraph.pl > kthread.svg

# 특정 커널 스레드만 프로파일링
perf record -F 99 -p $(pgrep -f kworker/0:1) -g -- sleep 10
perf script | ./stackcollapse-perf.pl | ./flamegraph.pl > kworker0.svg

내부 구현 상세

to_kthread() / struct kthread

각 커널 스레드에는 struct kthread가 부속되며, task_struct.set_child_tid 포인터를 통해 접근합니다:

/* kernel/kthread.c */
struct kthread {
    unsigned long        flags;    /* KTHREAD_SHOULD_STOP 등 */
    unsigned int         cpu;      /* per-CPU kthread의 대상 CPU */
    int                  result;   /* 스레드 함수 반환값 */
    int                  (*threadfn)(void *);
    void                 *data;
    struct completion    parked;
    struct completion    exited;
    char                 *full_name;
};

/* 플래그 비트 */
enum KTHREAD_BITS {
    KTHREAD_IS_PER_CPU   = 0,   /* per-CPU 스레드 여부 */
    KTHREAD_SHOULD_STOP  = 1,   /* kthread_stop() 요청됨 */
    KTHREAD_SHOULD_PARK  = 2,   /* kthread_park() 요청됨 */
    KTHREAD_IS_PARKED    = 3,   /* 현재 park 상태 */
};

/* task_struct에서 kthread 구조체 접근 */
static inline struct kthread *to_kthread(struct task_struct *k)
{
    WARN_ON(!(k->flags & PF_KTHREAD));
    return k->worker_private;
}

kernel_thread() → kernel_clone()

kernel_thread()kernel_clone()의 래퍼로, 커널 모드에서 새 태스크를 생성합니다:

/* kernel/fork.c */
pid_t kernel_thread(int (*fn)(void *), void *arg,
                     const char *name, unsigned long flags)
{
    struct kernel_clone_args args = {
        .flags       = ((lower_32_bits(flags) | CLONE_VM | CLONE_UNTRACED)
                        & ~CSIGNAL),
        .exit_signal = (lower_32_bits(flags) & CSIGNAL),
        .fn          = fn,
        .fn_arg      = arg,
        .name        = name,
        .kthread     = 1,   /* PF_KTHREAD 설정 */
    };
    return kernel_clone(&args);
}

/* kernel_clone()은 copy_process()를 호출하여:
 * 1. task_struct 할당
 * 2. PF_KTHREAD 플래그 설정
 * 3. mm = NULL (유저 주소 공간 없음)
 * 4. 커널 스택 할당
 * 5. 스케줄러에 등록
 */

동기화 및 슬립 패턴

completion 기반 패턴

커널 스레드의 시작/종료를 동기화할 때 completion을 사용합니다:

static DECLARE_COMPLETION(thread_started);

static int my_thread_fn(void *data)
{
    /* 초기화 완료 시그널 */
    complete(&thread_started);

    while (!kthread_should_stop()) {
        do_work();
        msleep(100);
    }
    return 0;
}

/* 생성 측 */
kth = kthread_run(my_thread_fn, data, "my-thread");
wait_for_completion(&thread_started);
/* 이 시점에서 스레드 초기화가 확실히 완료됨 */

Freezable 커널 스레드

시스템 절전(suspend) 시 커널 스레드도 얼릴 수 있습니다. I/O를 수행하는 커널 스레드는 suspend 전에 정지해야 데이터 손상을 방지할 수 있습니다:

static int freezable_thread_fn(void *data)
{
    /* 이 스레드는 freezable — suspend 시 동결됨 */
    set_freezable();

    while (!kthread_should_stop()) {
        /* try_to_freeze()가 suspend 시 스레드를 정지시킴 */
        try_to_freeze();

        do_io_work();

        /* freezable 슬립 — suspend 도중에 깨어나지 않음 */
        wait_event_freezable(my_wq, has_work || kthread_should_stop());
    }
    return 0;
}

/* wait_event_freezable은 내부적으로:
 * 1. freezing(current)이면 try_to_freeze() 호출
 * 2. 조건 확인
 * 3. 슬립
 * 을 안전하게 조합합니다 */

디버깅 (Debugging)

커널 스레드 식별하기

# 모든 커널 스레드 나열 (대괄호 표기)
ps -eo pid,ppid,cls,ni,psr,comm | awk '$2==2 || $1==2'

# 특정 커널 스레드의 상세 정보
cat /proc/<pid>/status
# Name:   kswapd0
# State:  S (sleeping)
# Tgid:   <pid>
# VmSize: (없음 — 커널 스레드는 유저 메모리 없음)

# 커널 스레드의 CPU affinity 확인
taskset -p <pid>

# 커널 스레드 스택 트레이스
cat /proc/<pid>/stack

디버깅 도구

# ftrace로 kthread 생성 추적
echo 1 > /sys/kernel/debug/tracing/events/sched/sched_kthread_work_execute_start/enable
echo 1 > /sys/kernel/debug/tracing/events/sched/sched_kthread_work_queue_work/enable
cat /sys/kernel/debug/tracing/trace

# SysRq-T: 모든 태스크의 스택 덤프 (커널 스레드 포함)
echo t > /proc/sysrq-trigger
dmesg | less

# 특정 커널 스레드의 스케줄링 통계
cat /proc/<pid>/sched

# perf로 커널 스레드 프로파일링
perf top -t <pid>
💡

/proc/<pid>/stack: CONFIG_STACKTRACE가 활성화된 커널에서 커널 스레드의 현재 호출 스택을 확인할 수 있습니다. 스레드가 어디서 슬립하고 있는지 파악하는 데 매우 유용합니다.

코드 예제 (Code Examples)

기본 커널 스레드 모듈

#include <linux/module.h>
#include <linux/kthread.h>
#include <linux/delay.h>
#include <linux/sched.h>

static struct task_struct *my_thread;

static int thread_func(void *data)
{
    int count = 0;

    pr_info("kthread started: pid=%d, comm=%s\\n",
            current->pid, current->comm);

    while (!kthread_should_stop()) {
        pr_info("kthread iteration %d (cpu=%d)\\n",
                count++, smp_processor_id());

        msleep(2000);
    }

    pr_info("kthread stopping, count=%d\\n", count);
    return count;
}

static int __init my_init(void)
{
    pr_info("creating kernel thread\\n");

    my_thread = kthread_run(thread_func, NULL, "example_kthread");
    if (IS_ERR(my_thread)) {
        pr_err("kthread_run failed: %ld\\n", PTR_ERR(my_thread));
        return PTR_ERR(my_thread);
    }

    pr_info("kthread created: pid=%d\\n", my_thread->pid);
    return 0;
}

static void __exit my_exit(void)
{
    int ret;

    if (my_thread) {
        ret = kthread_stop(my_thread);
        pr_info("kthread stopped, return value: %d\\n", ret);
    }
}

module_init(my_init);
module_exit(my_exit);
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("Basic Kernel Thread Example");

Per-CPU 커널 스레드

#include <linux/module.h>
#include <linux/kthread.h>
#include <linux/delay.h>
#include <linux/cpumask.h>

static struct task_struct * __percpu *per_cpu_threads;

static int per_cpu_fn(void *data)
{
    unsigned int cpu = (unsigned long)data;

    pr_info("per-cpu kthread on CPU %u started (pid=%d)\\n",
            cpu, current->pid);

    while (!kthread_should_stop()) {
        if (kthread_should_park())
            kthread_parkme();

        pr_info("CPU %u: doing work\\n", cpu);
        msleep(5000);
    }
    return 0;
}

static int __init percpu_init(void)
{
    unsigned int cpu;

    per_cpu_threads = alloc_percpu(struct task_struct *);
    if (!per_cpu_threads)
        return -ENOMEM;

    for_each_online_cpu(cpu) {
        struct task_struct *t;
        t = kthread_create_on_cpu(per_cpu_fn,
                (void *)(unsigned long)cpu,
                cpu, "my_pcpu/%u");
        if (IS_ERR(t)) {
            pr_err("failed on CPU %u\\n", cpu);
            continue;
        }
        *per_cpu_ptr(per_cpu_threads, cpu) = t;
        wake_up_process(t);
    }
    return 0;
}

static void __exit percpu_exit(void)
{
    unsigned int cpu;

    for_each_online_cpu(cpu) {
        struct task_struct *t = *per_cpu_ptr(per_cpu_threads, cpu);
        if (t)
            kthread_stop(t);
    }
    free_percpu(per_cpu_threads);
}

module_init(percpu_init);
module_exit(percpu_exit);
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("Per-CPU Kernel Thread Example");

kthread_worker 사용 예제

#include <linux/module.h>
#include <linux/kthread.h>

static struct kthread_worker *worker;
static struct kthread_work my_work;
static struct kthread_delayed_work my_delayed_work;

static void work_func(struct kthread_work *work)
{
    pr_info("work executed: thread=%s, cpu=%d\\n",
            current->comm, smp_processor_id());
}

static void delayed_work_func(struct kthread_work *work)
{
    pr_info("delayed work executed\\n");

    /* 자기 자신을 다시 큐잉 (주기적 실행) */
    kthread_queue_delayed_work(worker, &my_delayed_work,
                               msecs_to_jiffies(3000));
}

static int __init worker_init(void)
{
    /* 워커 생성 (전용 스레드 자동 생성) */
    worker = kthread_create_worker(0, "my_kworker");
    if (IS_ERR(worker))
        return PTR_ERR(worker);

    /* 즉시 실행 작업 */
    kthread_init_work(&my_work, work_func);
    kthread_queue_work(worker, &my_work);

    /* 지연 실행 작업 (3초 후) */
    kthread_init_delayed_work(&my_delayed_work, delayed_work_func);
    kthread_queue_delayed_work(worker, &my_delayed_work,
                               msecs_to_jiffies(3000));

    return 0;
}

static void __exit worker_exit(void)
{
    kthread_cancel_delayed_work_sync(&my_delayed_work);
    kthread_destroy_worker(worker);
}

module_init(worker_init);
module_exit(worker_exit);
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("kthread_worker Example");

Freezable 커널 스레드

#include <linux/module.h>
#include <linux/kthread.h>
#include <linux/freezer.h>
#include <linux/wait.h>

static struct task_struct *freeze_thread;
static DECLARE_WAIT_QUEUE_HEAD(wq);
static bool do_work = false;

static int freezable_fn(void *data)
{
    set_freezable();

    while (!kthread_should_stop()) {
        /* suspend 시 자동 동결, resume 시 자동 해동 */
        wait_event_freezable(wq,
            do_work || kthread_should_stop());

        if (kthread_should_stop())
            break;

        do_work = false;
        pr_info("freezable thread: doing I/O work\\n");
    }
    return 0;
}

static int __init freeze_init(void)
{
    freeze_thread = kthread_run(freezable_fn, NULL, "freezable_kth");
    return IS_ERR(freeze_thread) ? PTR_ERR(freeze_thread) : 0;
}

static void __exit freeze_exit(void)
{
    kthread_stop(freeze_thread);
}

module_init(freeze_init);
module_exit(freeze_exit);
MODULE_LICENSE("GPL");

주의사항 및 버그 패턴

kthread_stop() 없이 모듈 언로드

/* 잘못된 예 — 스레드 실행 중 모듈 언로드 */
static void __exit bad_exit(void)
{
    /* kthread_stop() 호출 안 함!
     * → 스레드가 계속 실행되지만 코드 영역이 해제됨
     * → 커널 패닉 (use-after-free) */
    pr_info("goodbye\\n");
}

/* 올바른 예 */
static void __exit good_exit(void)
{
    if (my_thread) {
        kthread_stop(my_thread);  /* 반드시 종료 대기 */
        my_thread = NULL;
    }
}

커널 스레드에서 유저 공간 접근

/* 잘못된 예 — 커널 스레드에서 유저 메모리 접근 */
static int bad_thread_fn(void *data)
{
    char __user *ubuf = (char __user *)data;
    char kbuf[64];

    /* mm == NULL이므로 copy_from_user()가 실패하거나 Oops 발생!
     * 커널 스레드는 유저 주소 공간이 없습니다 */
    copy_from_user(kbuf, ubuf, 64);  /* WRONG */

    return 0;
}

/* 올바른 방법: 데이터를 커널 메모리에 복사해서 전달 */
char *kdata = kmalloc(64, GFP_KERNEL);
copy_from_user(kdata, ubuf, 64);  /* 유저 컨텍스트에서 미리 복사 */
kth = kthread_run(my_fn, kdata, "my-kthread");  /* 커널 메모리 전달 */

무한 루프 / CPU 100% 점유

/* 잘못된 예 — 슬립 없는 폴링 루프 */
static int hog_thread_fn(void *data)
{
    while (!kthread_should_stop()) {
        /* CPU를 100% 점유! 다른 태스크 기아 발생 */
        do_polling();
    }
    return 0;
}

/* 올바른 예 — schedule() 또는 cond_resched() 삽입 */
static int good_thread_fn(void *data)
{
    while (!kthread_should_stop()) {
        if (!has_work()) {
            usleep_range(1000, 2000);  /* 1~2ms 슬립 */
            continue;
        }
        do_work();
        cond_resched();  /* 선점 포인트 */
    }
    return 0;
}

kthread_create 에러 미처리

/* 잘못된 예 */
my_thread = kthread_run(fn, data, "my-kth");
wake_up_process(my_thread);  /* IS_ERR일 때 크래시! */

/* 올바른 예 */
my_thread = kthread_run(fn, data, "my-kth");
if (IS_ERR(my_thread)) {
    pr_err("failed: %ld\\n", PTR_ERR(my_thread));
    my_thread = NULL;  /* exit에서 NULL 체크 가능하도록 */
    return PTR_ERR(my_thread);
}
⚠️

모듈 언로드 안전: module_exit에서는 반드시 (1) kthread_stop()으로 스레드 종료를 기다리고, (2) 스레드가 사용한 리소스를 해제하세요. 스레드가 실행 중인 상태에서 모듈이 언로드되면 코드 영역이 해제되어 커널 패닉이 발생합니다.

API 레퍼런스 요약

함수 / 매크로 설명 헤더
kthread_create(fn, data, fmt, ...) 커널 스레드 생성 (미실행 상태) linux/kthread.h
kthread_run(fn, data, fmt, ...) 커널 스레드 생성 + 즉시 실행 linux/kthread.h
kthread_create_on_node(fn, data, node, fmt, ...) NUMA 노드 지정 생성 linux/kthread.h
kthread_create_on_cpu(fn, data, cpu, fmt) CPU 바인딩 생성 (per-CPU) linux/kthread.h
kthread_bind(task, cpu) 미실행 스레드를 특정 CPU에 바인딩 linux/kthread.h
kthread_should_stop() 종료 요청 확인 (루프 조건) linux/kthread.h
kthread_stop(task) 스레드 종료 요청 + 종료 대기 linux/kthread.h
kthread_should_park() 일시 중지 요청 확인 linux/kthread.h
kthread_park(task) 스레드 일시 중지 linux/kthread.h
kthread_unpark(task) 스레드 재개 linux/kthread.h
kthread_parkme() 현재 스레드를 park 상태로 전환 linux/kthread.h
kthread_create_worker(flags, fmt, ...) kthread_worker + 전용 스레드 생성 linux/kthread.h
kthread_queue_work(worker, work) 작업을 워커 큐에 추가 linux/kthread.h
kthread_flush_work(work) 특정 작업 완료 대기 linux/kthread.h
kthread_destroy_worker(worker) 워커 + 전용 스레드 정리 linux/kthread.h
smpboot_register_percpu_thread(ht) per-CPU 스레드 프레임워크 등록 linux/smpboot.h
set_freezable() 현재 스레드를 freezable로 마킹 linux/freezer.h
wait_event_freezable(wq, cond) freeze 안전한 조건부 대기 linux/freezer.h

Kernel Threads와 관련된 다른 주제를 더 깊이 이해하고 싶다면 다음 문서를 참고하세요.