Workqueue 서브시스템 심화 (CMWQ)

리눅스 커널의 핵심 비동기 실행 인프라인 Workqueue(CMWQ) 서브시스템을 심층 분석합니다. worker pool 아키텍처, work_struct/delayed_work 자료구조, 커스텀 워크큐 생성, 동시성 관리, 디버깅 기법, 그리고 드라이버에서의 실전 활용 패턴까지 포괄적으로 다룹니다.

이 페이지는 Workqueue 서브시스템을 독립적으로 심층 분석합니다. Bottom Half 전반(softirq, tasklet 포함)에 대한 개요는 Bottom Half 심화 페이지를, 인터럽트 기초는 인터럽트 페이지를 참고하세요.

전제 조건: Bottom Half 심화(softirq/tasklet/workqueue 비교)와 프로세스 관리(커널 스레드 개념)를 먼저 읽으세요.
일상 비유: Workqueue는 공유 사무실의 프리랜서 풀과 같습니다. 할 일(work_struct)을 게시판(큐)에 올려놓으면, 대기 중인 프리랜서(worker 스레드)가 가져가서 처리합니다. 일이 많으면 프리랜서를 더 고용하고(동적 생성), 일이 없으면 대기시킵니다(idle). CMWQ(Concurrency Managed Workqueue)는 이 인력 풀을 자동으로 관리하는 시스템입니다.

핵심 요약

  • work_struct — 지연 실행할 작업을 나타내는 구조체. 콜백 함수 포인터를 포함합니다.
  • worker pool — per-CPU 또는 unbound worker 스레드 풀. CMWQ가 동적으로 관리합니다.
  • system_wq — 기본 시스템 워크큐. schedule_work()로 간편하게 작업을 예약합니다.
  • 슬립 가능 — softirq/tasklet과 달리 workqueue는 프로세스 컨텍스트에서 실행되어 슬립할 수 있습니다.

단계별 이해

  1. work 생성INIT_WORK(&my_work, my_handler)로 작업을 초기화합니다.

    핸들러 함수는 void handler(struct work_struct *work) 시그니처입니다.

  2. work 예약schedule_work(&my_work)으로 시스템 워크큐에 넣거나, queue_work(my_wq, &my_work)로 커스텀 큐에 넣습니다.

    schedule_delayed_work()로 일정 시간 후에 실행되도록 예약할 수도 있습니다.

  3. 실행 과정 — worker 스레드가 큐에서 work를 꺼내 핸들러를 실행합니다.

    CMWQ가 동시성 수준을 자동 관리하여 CPU 사용을 최적화합니다.

  4. 확인ps aux | grep kworker로 현재 활성 worker 스레드를 확인합니다.

    /sys/kernel/debug/workqueue/에서 워크큐 통계를 볼 수 있습니다.

Workqueue 개요

Workqueue는 커널에서 작업을 프로세스 컨텍스트로 지연 실행(deferred execution)하기 위한 범용 인프라입니다. 인터럽트 핸들러나 softirq에서 수행하기에 부적합한 작업 -- 슬립 가능한(sleepable) 연산, 잠금(lock) 획득, 사용자 공간 접근, 시간이 오래 걸리는 처리 등 -- 을 안전하게 위임할 수 있습니다.

역사: keventd에서 CMWQ까지

Workqueue의 발전은 커널의 비동기 처리 요구사항 증가와 직접 연결됩니다:

시기메커니즘특징한계
2.5 이전task queue (keventd)단일 스레드, 간단한 API병렬 처리 불가, 확장성 부족
2.5 ~ 2.6.36기존 WorkqueuePer-CPU worker 스레드워크큐마다 Per-CPU 스레드 생성 (메모리 낭비, PID 공간 소모)
2.6.36+CMWQ (Concurrency Managed)공유 worker pool, 동시성 자동 관리현재 표준

기존 workqueue의 핵심 문제는 create_workqueue()가 CPU 수만큼 커널 스레드를 생성했다는 점입니다. 64-CPU 시스템에서 30개의 워크큐가 있으면 1,920개의 커널 스레드가 만들어졌고, 대부분은 유휴 상태로 메모리만 소모했습니다. CMWQ는 이 문제를 공유 worker pool로 해결했습니다.

💡

핵심 설계 원칙: CMWQ는 "워크큐는 작업의 속성(attribute)을 정의하고, worker pool은 실행을 담당한다"는 분리 원칙을 따릅니다. 워크큐는 더 이상 자체 스레드를 소유하지 않으며, 적절한 worker pool에 작업을 위임합니다.

Workqueue vs 다른 Bottom Half 메커니즘

특성SoftirqTaskletWorkqueue
실행 컨텍스트인터럽트인터럽트프로세스
슬립 가능불가불가가능
동일 work 병렬 실행가능 (Per-CPU)불가 (직렬화)설정에 따라 다름
지연 실행불가불가가능 (delayed_work)
CPU 친화성현재 CPU현재 CPU설정 가능
사용 대상커널 서브시스템드라이버 (레거시)드라이버, 서브시스템

CMWQ 아키텍처

CMWQ의 핵심은 워크큐와 worker pool을 분리한 것입니다. 워크큐는 작업의 속성(우선순위, CPU 바인딩 여부 등)을 정의하고, worker pool은 실제 실행 인프라를 제공합니다.

Worker Pool 유형

시스템에는 두 가지 유형의 worker pool이 존재합니다:

/*
 * Worker Pool 구조:
 *
 * ┌─────────────────────────────────────────────────────────────────┐
 * │                     Per-CPU Worker Pools                        │
 * │                                                                 │
 * │  CPU 0                CPU 1                CPU N                │
 * │  ┌──────────────┐    ┌──────────────┐    ┌──────────────┐      │
 * │  │ pool (nice 0)│    │ pool (nice 0)│    │ pool (nice 0)│      │
 * │  │  worker 0    │    │  worker 0    │    │  worker 0    │      │
 * │  │  worker 1    │    │  worker 1    │    │  worker 1    │      │
 * │  ├──────────────┤    ├──────────────┤    ├──────────────┤      │
 * │  │ pool (nice -20)│  │ pool (nice -20)│  │ pool (nice -20)│   │
 * │  │  worker 0    │    │  worker 0    │    │  worker 0    │      │
 * │  └──────────────┘    └──────────────┘    └──────────────┘      │
 * ├─────────────────────────────────────────────────────────────────┤
 * │                    Unbound Worker Pools                         │
 * │                                                                 │
 * │  ┌──────────────────────────────────────┐                      │
 * │  │ unbound pool (attrs: nice 0, cpumask)│                      │
 * │  │  worker 0, worker 1, ... worker N    │                      │
 * │  └──────────────────────────────────────┘                      │
 * │  ┌──────────────────────────────────────┐                      │
 * │  │ unbound pool (attrs: nice -20, ...)  │                      │
 * │  │  worker 0, worker 1, ... worker N    │                      │
 * │  └──────────────────────────────────────┘                      │
 * └─────────────────────────────────────────────────────────────────┘
 */

Per-CPU Worker Pool: 각 CPU마다 두 개의 pool이 존재합니다. 하나는 일반 우선순위(nice 0), 다른 하나는 높은 우선순위(nice -20, WQ_HIGHPRI용)입니다. bound 워크큐의 work item은 큐잉된 CPU의 Per-CPU pool에서 실행됩니다.

Unbound Worker Pool: CPU에 바인딩되지 않은 pool입니다. WQ_UNBOUND 플래그가 설정된 워크큐의 work item을 실행합니다. 워크큐 속성(nice 값, cpumask 등)에 따라 풀이 생성되며, 동일 속성의 워크큐는 풀을 공유합니다.

핵심 자료구조 관계

/* kernel/workqueue_internal.h, kernel/workqueue.c */

/* worker_pool: 실제 실행 인프라 */
struct worker_pool {
    spinlock_t          lock;           /* pool 보호 락 */
    int                 cpu;            /* Per-CPU pool: 바인딩된 CPU (-1이면 unbound) */
    int                 node;           /* NUMA 노드 */
    int                 id;             /* pool ID */
    unsigned int        flags;          /* POOL_MANAGER_ACTIVE 등 */

    unsigned long       watchdog_ts;    /* worker stall 감지용 */

    struct list_head    worklist;       /* 대기 중인 work item 리스트 */
    int                 nr_workers;     /* 전체 worker 수 */
    int                 nr_idle;        /* 유휴 worker 수 */

    struct list_head    idle_list;      /* 유휴 worker 리스트 */
    struct list_head    workers;        /* 전체 worker 리스트 */

    struct workqueue_attrs *attrs;      /* unbound pool 속성 */
    struct hash_head    busy_hash[];    /* 실행 중인 work→worker 매핑 */
};

/* pool_workqueue: 워크큐 ↔ worker_pool 연결 */
struct pool_workqueue {
    struct worker_pool  *pool;          /* 연결된 worker pool */
    struct workqueue_struct *wq;        /* 소속 워크큐 */
    int                 nr_active;      /* 활성 work item 수 */
    int                 max_active;     /* 최대 동시 활성 수 */
    struct list_head    inactive_works; /* max_active 초과 시 대기 리스트 */
};

/* workqueue_struct: 사용자가 상호작용하는 워크큐 객체 */
struct workqueue_struct {
    struct list_head    pwqs;           /* pool_workqueue 리스트 */
    struct list_head    list;           /* 전역 워크큐 리스트 */
    unsigned int        flags;          /* WQ_UNBOUND, WQ_HIGHPRI 등 */
    int                 saved_max_active; /* freezing 시 저장 */
    char                name[];         /* 워크큐 이름 */
};

3계층 구조: workqueue_struct(사용자 인터페이스) → pool_workqueue(연결 역할) → worker_pool(실행 인프라). 이 구조 덕분에 여러 워크큐가 동일한 worker pool을 공유할 수 있습니다.

work_struct / delayed_work

work item은 워크큐에 큐잉되는 작업의 기본 단위입니다. 두 가지 기본 타입이 있습니다.

work_struct 정의

/* include/linux/workqueue.h */
struct work_struct {
    atomic_long_t data;     /* 플래그 + pool_workqueue 포인터 (하위 비트는 플래그) */
    struct list_head entry;  /* worklist 연결 */
    work_func_t func;       /* 콜백 함수: void (*)(struct work_struct *) */
};

struct delayed_work {
    struct work_struct work;    /* 내장된 work_struct */
    struct timer_list timer;   /* 지연 타이머 */
    struct workqueue_struct *wq; /* 타이머 만료 시 큐잉할 워크큐 */
    int cpu;                   /* 타이머 만료 시 큐잉할 CPU */
};

data 필드의 비트 레이아웃

work_struct.dataatomic_long_t이지만, 단순 데이터가 아니라 플래그와 pool_workqueue 포인터를 동시에 저장합니다:

/* include/linux/workqueue.h - data 필드 비트 레이아웃 */
#define WORK_STRUCT_PENDING_BIT  0   /* 큐에 대기 중 */
#define WORK_STRUCT_INACTIVE_BIT 1   /* inactive 리스트에 있음 */
#define WORK_STRUCT_PWQ_BIT      2   /* data가 pwq 포인터를 담고 있음 */
#define WORK_STRUCT_LINKED_BIT   3   /* 다음 work와 연결됨 */
#define WORK_STRUCT_COLOR_SHIFT  4   /* flush color 비트 시작 */

/*
 * 상위 비트: pool_workqueue 포인터 (정렬 보장으로 하위 비트 사용 가능)
 * 하위 비트: 플래그
 *
 * ┌─────────────────────────────────┬──────┬─┬─┬─┬─┐
 * │     pool_workqueue pointer      │color │L│P│I│P│
 * │     (or pool ID when idle)      │      │ │W│N│E│
 * │                                 │      │ │Q│A│N│
 * └─────────────────────────────────┴──────┴─┴─┴─┴─┘
 * MSB                                               LSB
 */

초기화 매크로

/* 정적 초기화 */
DECLARE_WORK(name, func);              /* 정적 work_struct 선언+초기화 */
DECLARE_DELAYED_WORK(name, func);      /* 정적 delayed_work 선언+초기화 */

/* 동적 초기화 */
struct work_struct my_work;
INIT_WORK(&my_work, my_work_handler);

struct delayed_work my_dwork;
INIT_DELAYED_WORK(&my_dwork, my_dwork_handler);

/* 콜백 함수 시그니처 */
static void my_work_handler(struct work_struct *work)
{
    /* container_of로 부모 구조체 접근 */
    struct my_device *dev = container_of(work, struct my_device, work);
    /* 프로세스 컨텍스트: 슬립 가능, 뮤텍스 획득 가능 */
}

static void my_dwork_handler(struct work_struct *work)
{
    struct delayed_work *dwork = to_delayed_work(work);
    struct my_device *dev = container_of(dwork, struct my_device, dwork);
    /* ... */
}

work_struct 재사용 규칙: 동일한 work_struct는 동시에 두 번 큐잉할 수 없습니다. queue_work()는 이미 WORK_STRUCT_PENDING이 설정된 work를 무시합니다(false 반환). 동일 작업을 여러 번 큐잉해야 한다면, 각각 별도의 work_struct를 사용해야 합니다.

시스템 워크큐

커널은 사전 정의된 시스템 워크큐를 제공합니다. 대부분의 드라이버에서는 커스텀 워크큐 대신 이들을 사용하면 충분합니다:

/* kernel/workqueue.c - 시스템 워크큐 정의 */
struct workqueue_struct *system_wq;           /* 범용 (Per-CPU, nice 0) */
struct workqueue_struct *system_highpri_wq;   /* 높은 우선순위 (nice -20) */
struct workqueue_struct *system_long_wq;      /* 장시간 실행 work용 */
struct workqueue_struct *system_unbound_wq;   /* CPU 비종속 (unbound) */
struct workqueue_struct *system_freezable_wq; /* suspend 시 동결 */
struct workqueue_struct *system_power_efficient_wq; /* 전력 효율 */
시스템 워크큐플래그용도
system_wq0일반적인 짧은 작업. schedule_work()의 기본 대상
system_highpri_wqWQ_HIGHPRI지연이 중요한 작업 (nice -20 worker에서 실행)
system_long_wq0장시간 실행 work (concurrency 관리에서 제외하지는 않음)
system_unbound_wqWQ_UNBOUNDCPU 바인딩 불필요한 작업 (스케줄러가 CPU 선택)
system_freezable_wqWQ_FREEZABLEsuspend/hibernate 시 동결되어야 하는 작업
system_power_efficient_wqWQ_UNBOUND (조건부)workqueue.power_efficient 부트 파라미터에 따라 unbound 전환

편의 API (시스템 워크큐 직접 사용)

/* 시스템 워크큐(system_wq)에 직접 큐잉하는 편의 함수 */
static inline bool schedule_work(struct work_struct *work)
{
    return queue_work(system_wq, work);
}

static inline bool schedule_delayed_work(struct delayed_work *dwork,
                                            unsigned long delay)
{
    return queue_delayed_work(system_wq, dwork, delay);
}

/* 사용 예: IRQ 핸들러에서 work 큐잉 */
static irqreturn_t my_irq_handler(int irq, void *data)
{
    struct my_device *dev = data;

    /* 최소한의 처리 후 나머지를 워크큐로 위임 */
    dev->irq_status = readl(dev->regs + IRQ_STATUS);
    writel(dev->irq_status, dev->regs + IRQ_ACK);

    schedule_work(&dev->work);   /* system_wq에 큐잉 */
    return IRQ_HANDLED;
}
💡

system_wq vs 커스텀 워크큐: 대부분의 경우 schedule_work()/schedule_delayed_work()로 시스템 워크큐를 사용하는 것으로 충분합니다. 커스텀 워크큐가 필요한 경우: (1) flush 범위를 분리하고 싶을 때, (2) 특수 플래그(WQ_MEM_RECLAIM 등)가 필요할 때, (3) ordered 실행이 필요할 때.

커스텀 워크큐 생성

alloc_workqueue() API

/* include/linux/workqueue.h */
struct workqueue_struct *alloc_workqueue(
    const char *fmt,       /* 워크큐 이름 (printf 포맷) */
    unsigned int flags,    /* WQ_* 플래그 조합 */
    int max_active,        /* 최대 동시 활성 work 수 (0 = 기본값) */
    ...                    /* fmt에 대한 가변 인자 */
);

/* 매크로 래퍼 */
#define alloc_ordered_workqueue(fmt, flags, args...) \
    alloc_workqueue(fmt, WQ_UNBOUND | __WQ_ORDERED | (flags), 1, ##args)

/* 해제 */
void destroy_workqueue(struct workqueue_struct *wq);

WQ 플래그 상세

플래그설명
WQ_UNBOUNDPer-CPU pool 대신 unbound pool 사용. CPU 마이그레이션이 자유롭고, NUMA 지역성 최적화 적용. 장시간 실행 work에 적합
WQ_HIGHPRI높은 우선순위(nice -20) worker pool 사용. 지연 민감한 작업에 사용
WQ_FREEZABLEsuspend/hibernate 진입 시 새 work 큐잉을 막고 기존 work 완료를 대기. PM 관련 드라이버에 필수
WQ_MEM_RECLAIM메모리 회수 경로에서 사용하는 워크큐. rescuer worker를 보장하여 메모리 부족 시에도 진행 가능(forward progress)
WQ_CPU_INTENSIVECPU를 장시간 점유하는 work. concurrency 관리에서 제외(실행 중에도 다른 work를 동시에 처리 가능)
__WQ_ORDERED내부 플래그. alloc_ordered_workqueue()에서 자동 설정. 순서 보장

max_active 파라미터

/*
 * max_active: 워크큐당 Per-CPU/unbound pool에서 동시에 실행 가능한 work 수
 *
 * - 0: 기본값 사용 (WQ_DFL_ACTIVE = 256, 또는 WQ_UNBOUND_MAX_ACTIVE = CPU 수 * 4)
 * - 1: 순서 보장 (ordered workqueue)
 * - N: 최대 N개의 work가 동시 실행 가능
 *
 * 주의: max_active는 Per-CPU pool 기준입니다.
 * bound 워크큐에서 max_active=2이면, 각 CPU에서 최대 2개까지 동시 실행.
 * 시스템 전체로는 CPU_수 * 2개가 동시 실행될 수 있습니다.
 */

/* 예: 동시 실행 제한이 있는 워크큐 */
struct workqueue_struct *my_wq;
my_wq = alloc_workqueue("my_driver_wq",
                        WQ_UNBOUND | WQ_MEM_RECLAIM,
                        4);  /* 최대 4개 동시 실행 */
if (!my_wq)
    return -ENOMEM;

WQ_MEM_RECLAIM과 rescuer 스레드

메모리 회수(reclaim) 경로에서 워크큐를 사용하면 순환 의존성 위험이 있습니다. 메모리 부족으로 새 worker를 생성할 수 없는데, work 실행이 메모리를 해제하는 상황입니다. WQ_MEM_RECLAIM은 이를 rescuer 스레드로 해결합니다:

/* kernel/workqueue.c - rescuer 스레드 */
static int rescuer_thread(void *__rescuer)
{
    struct worker *rescuer = __rescuer;
    struct workqueue_struct *wq = rescuer->rescue_wq;

    /*
     * rescuer는 워크큐 생성 시 미리 할당된 전용 worker입니다.
     * 일반 worker를 생성할 수 없을 때(메모리 부족) 활성화되어
     * pending work를 직접 실행합니다.
     *
     * 이것이 forward progress guarantee입니다:
     * 메모리 회수 work가 실행되어 메모리를 해제하면,
     * 다시 일반 worker 생성이 가능해집니다.
     */

    for (;;) {
        set_current_state(TASK_IDLE);

        if (need_to_create_worker(pool)) {
            /* pool의 pending work를 가져와서 직접 실행 */
            move_linked_works(work, &rescuer->scheduled, &n);
            process_scheduled_works(rescuer);
        }

        schedule();
    }
}

필수 규칙: 메모리 회수 경로(직접 또는 간접)에서 사용하는 워크큐에는 반드시 WQ_MEM_RECLAIM을 설정해야 합니다. 그렇지 않으면 메모리 부족 시 데드락이 발생할 수 있습니다. 블록 디바이스 드라이버, 파일시스템, 스왑 관련 코드에서 특히 주의하세요.

실전 워크큐 생성 예제

/* 예 1: 범용 드라이버 워크큐 (가장 일반적) */
wq = alloc_workqueue("mydrv", 0, 0);

/* 예 2: 순서 보장 워크큐 */
wq = alloc_ordered_workqueue("mydrv_ordered", 0);

/* 예 3: 블록 디바이스 드라이버 (메모리 회수 경로) */
wq = alloc_workqueue("myblk", WQ_MEM_RECLAIM | WQ_HIGHPRI, 0);

/* 예 4: CPU 집약적 연산 (암호화, 압축 등) */
wq = alloc_workqueue("mycrypto", WQ_UNBOUND | WQ_CPU_INTENSIVE, 0);

/* 예 5: 전원 관리 관련 드라이버 */
wq = alloc_workqueue("mypm", WQ_FREEZABLE | WQ_MEM_RECLAIM, 0);

/* 예 6: 이름에 동적 정보 포함 */
wq = alloc_workqueue("mydrv-%s", WQ_UNBOUND, 0, dev_name(dev));

/* 모든 경우 NULL 체크 필수 */
if (!wq)
    return -ENOMEM;

Worker Pool 관리

동시성 관리 메커니즘

CMWQ의 "C"(Concurrency Managed)는 worker pool이 자동으로 동시성을 관리한다는 의미입니다. Per-CPU bound pool에서 동작하는 핵심 메커니즘은 다음과 같습니다:

/*
 * 동시성 관리 핵심 원리:
 *
 * 1. worker가 work를 실행하기 시작하면 "running" 카운트 증가
 * 2. worker가 슬립(I/O 대기, 뮤텍스 등)하면 "running" 카운트 감소
 * 3. running 카운트가 0이 되면 → 새 worker를 깨움
 * 4. 깨어난 worker가 대기 중인 work를 처리
 *
 * 이를 통해 CPU가 유휴 상태로 빠지는 것을 방지하면서도
 * 불필요한 worker 생성을 억제합니다.
 */

/* kernel/workqueue.c - worker가 슬립할 때 호출 */
void wq_worker_sleeping(struct task_struct *task)
{
    struct worker *worker = kthread_data(task);
    struct worker_pool *pool;

    pool = worker->pool;

    /* running 카운트 감소 */
    if (atomic_dec_and_test(&pool->nr_running) &&
        !list_empty(&pool->worklist)) {
        /* 더 이상 running worker가 없고 대기 work가 있으면 */
        /* 유휴 worker를 깨움 */
        wake_up_worker(pool);
    }
}

/* worker가 깨어날 때 호출 */
void wq_worker_running(struct task_struct *task)
{
    struct worker *worker = kthread_data(task);

    if (!worker->sleeping)
        return;
    worker->sleeping = 0;
    /* running 카운트 증가 */
    atomic_inc(&worker->pool->nr_running);
}

WQ_CPU_INTENSIVE의 의미: WQ_CPU_INTENSIVE 워크큐의 work는 실행 시작 시 running 카운트에서 제외됩니다. 즉, CPU 집약적 work가 오래 실행되어도 pool의 동시성 관리가 "work가 블록되었다"고 오판하여 불필요한 worker를 깨우지 않습니다.

Worker 스레드 생명주기

/*
 * Worker 스레드 상태 전이:
 *
 *  생성 ──→ IDLE ──→ BUSY (work 실행) ──→ IDLE
 *            │                              │
 *            │         유휴 시간 초과         │
 *            └──────→ 소멸 (destroy) ←───────┘
 *
 * 생성 조건:
 *   - pool에 대기 work가 있는데 running worker가 없을 때
 *   - manager가 need_more_worker()로 판단
 *
 * 소멸 조건:
 *   - idle_list에 IDLE_WORKER_TIMEOUT(5분) 이상 머문 worker
 *   - 단, pool당 최소 1개의 worker는 유지 (min_idle_workers)
 */

/* kernel/workqueue.c */
#define IDLE_WORKER_TIMEOUT    (300 * HZ)  /* 5분 */

static int worker_thread(void *__worker)
{
    struct worker *worker = __worker;
    struct worker_pool *pool = worker->pool;

woke_up:
    spin_lock_irq(&pool->lock);

    /* 유휴 worker 정리 대상인지 확인 */
    if (too_many_workers(pool))
        goto die;

    /* worker가 manager 역할을 해야 하는지 확인 */
    if (!need_more_worker(pool))
        goto sleep;

    /* manager: 필요하면 새 worker 생성 */
    if (manage_workers(worker))
        goto recheck;

    /* 대기 중인 work 처리 */
    do {
        struct work_struct *work = list_first_entry(
            &pool->worklist, struct work_struct, entry);
        process_one_work(worker, work);
    } while (keep_working(pool));

sleep:
    worker_enter_idle(worker);
    schedule();
    goto woke_up;

die:
    worker_detach_from_pool(worker);
    kthread_exit(0);
}

kworker 네이밍 규칙

pstop에서 볼 수 있는 kworker 스레드의 이름은 다음 규칙을 따릅니다:

kworker 네이밍 패턴:

  kworker/CPU:WORKER_ID          Per-CPU, 일반 우선순위
  kworker/CPUH:WORKER_ID         Per-CPU, 높은 우선순위 (H = Highpri)
  kworker/u POOL_ID:WORKER_ID    Unbound pool (u = Unbound)

  예시:
  kworker/0:1      - CPU 0의 일반 pool, worker ID 1
  kworker/3:0H     - CPU 3의 highpri pool, worker ID 0
  kworker/u8:2     - Unbound pool ID 8, worker ID 2

  rescuer 스레드:
  kworker_rescue-WORKQUEUE_NAME  - 해당 워크큐의 rescuer worker

큐잉과 실행

queue_work() 계열 API

/* include/linux/workqueue.h */

/* 기본 큐잉: 현재 CPU의 pool에 큐잉 */
bool queue_work(struct workqueue_struct *wq, struct work_struct *work);

/* 특정 CPU의 pool에 큐잉 */
bool queue_work_on(int cpu, struct workqueue_struct *wq,
                   struct work_struct *work);

/* 지연 큐잉: delay jiffies 후 큐잉 */
bool queue_delayed_work(struct workqueue_struct *wq,
                        struct delayed_work *dwork,
                        unsigned long delay);

/* 특정 CPU에 지연 큐잉 */
bool queue_delayed_work_on(int cpu, struct workqueue_struct *wq,
                           struct delayed_work *dwork,
                           unsigned long delay);

/* 타이머가 이미 pending인 delayed_work의 지연 시간 변경 */
bool mod_delayed_work(struct workqueue_struct *wq,
                       struct delayed_work *dwork,
                       unsigned long delay);

/* 반환값: true = 새로 큐잉됨, false = 이미 pending 상태 */

queue_work() 내부 동작

/* kernel/workqueue.c - 큐잉 경로 (간략화) */
bool queue_work_on(int cpu, struct workqueue_struct *wq,
                   struct work_struct *work)
{
    bool ret = false;
    unsigned long flags;

    local_irq_save(flags);

    /* PENDING 비트를 원자적으로 설정 시도 */
    if (!test_and_set_bit(WORK_STRUCT_PENDING_BIT,
                          work_data_bits(work))) {
        /* 성공: 아직 큐에 없었음 → 실제 큐잉 수행 */
        __queue_work(cpu, wq, work);
        ret = true;
    }
    /* 실패: 이미 PENDING → 중복 큐잉 방지 */

    local_irq_restore(flags);
    return ret;
}

static void __queue_work(int cpu, struct workqueue_struct *wq,
                         struct work_struct *work)
{
    struct pool_workqueue *pwq;

    /* 1. 적절한 pool_workqueue 선택 */
    if (wq->flags & WQ_UNBOUND) {
        /* unbound: NUMA 노드 기반 pwq 선택 */
        pwq = unbound_pwq_by_node(wq, cpu_to_node(cpu));
    } else {
        /* bound: 해당 CPU의 pwq 선택 */
        pwq = per_cpu_ptr(wq->cpu_pwqs, cpu);
    }

    /* 2. max_active 체크 */
    if (pwq->nr_active >= pwq->max_active) {
        /* 초과: inactive 리스트에 대기 */
        list_add_tail(&work->entry, &pwq->inactive_works);
        set_work_inactive(work);
    } else {
        /* 3. worker pool의 worklist에 추가 */
        pwq->nr_active++;
        list_add_tail(&work->entry, &pwq->pool->worklist);
        /* 4. 유휴 worker 깨우기 (필요시) */
        wake_up_worker(pwq->pool);
    }
}

process_one_work() - 실행 경로

/* kernel/workqueue.c - work 실행 핵심 함수 (간략화) */
static void process_one_work(struct worker *worker,
                              struct work_struct *work)
{
    struct pool_workqueue *pwq = get_work_pwq(work);
    struct worker_pool *pool = worker->pool;
    work_func_t f = work->func;

    /* 1. work를 worklist에서 제거 */
    list_del_init(&work->entry);

    /* 2. busy hash에 등록 (flush/cancel에서 추적용) */
    hash_add(pool->busy_hash, &worker->hentry,
             (unsigned long)work);
    worker->current_work = work;
    worker->current_func = f;
    worker->current_pwq = pwq;

    /* 3. PENDING 비트 클리어 (큐잉 가능하게) */
    set_work_pool_and_clear_pending(work, pool->id);

    /* 4. pool 락 해제 후 work 함수 호출 */
    spin_unlock_irq(&pool->lock);

    lockdep_start();
    trace_workqueue_execute_start(work);

    f(work);    /* ← work 콜백 실행 (프로세스 컨텍스트) */

    trace_workqueue_execute_end(work, f);
    lockdep_end();

    /* 5. 정리 */
    spin_lock_irq(&pool->lock);
    worker->current_work = NULL;

    /* 6. inactive 리스트에서 다음 work 활성화 */
    pwq_dec_nr_in_flight(pwq);
}

PENDING 비트 클리어 타이밍: process_one_work()에서 work 콜백을 호출하기 전에 PENDING 비트가 클리어됩니다. 이는 work 콜백 내에서 동일한 work_struct를 다시 큐잉할 수 있게 하기 위함입니다 (자기 재큐잉 패턴).

취소와 플러시

취소 API

/* work 취소: 대기 중이면 제거, 실행 중이면 완료 대기 */
bool cancel_work_sync(struct work_struct *work);

/* delayed_work 취소: 타이머 + work 모두 취소 */
bool cancel_delayed_work(struct delayed_work *dwork);
bool cancel_delayed_work_sync(struct delayed_work *dwork);

/*
 * cancel_work_sync() vs cancel_delayed_work():
 *
 * cancel_work_sync():
 *   - 실행 중인 work가 있으면 완료될 때까지 블록
 *   - 반환 후 work가 더 이상 실행되지 않음을 보장
 *   - IRQ 컨텍스트에서 호출 불가 (슬립 가능)
 *
 * cancel_delayed_work():
 *   - 타이머만 취소 (비동기, non-blocking)
 *   - 이미 큐잉되었거나 실행 중인 work는 영향 없음
 *   - IRQ 컨텍스트에서 호출 가능
 *
 * cancel_delayed_work_sync():
 *   - 타이머 취소 + 실행 중 work 완료 대기
 *   - 가장 안전한 cleanup 방법
 */

플러시 API

/* 특정 work의 실행 완료 대기 */
void flush_work(struct work_struct *work);
void flush_delayed_work(struct delayed_work *dwork);

/* 워크큐의 모든 pending work 실행 완료 대기 */
void flush_workqueue(struct workqueue_struct *wq);

/* 워크큐의 모든 work 완료 대기 + 새 큐잉 차단 */
void drain_workqueue(struct workqueue_struct *wq);

flush_workqueue() 내부 메커니즘 (color 기반)

/*
 * flush_workqueue()는 "color" 메커니즘으로 구현됩니다:
 *
 * 1. flush 호출 시, 현재 work_color를 기록하고 다음 color로 전진
 * 2. 새로 큐잉되는 work는 새 color를 받음
 * 3. 이전 color의 모든 work가 완료되면 flush 완료
 *
 * 이 방식의 장점:
 * - flush 중에 새로 큐잉된 work를 구분할 수 있음
 * - 여러 flush를 동시에 처리 가능
 *
 * 시퀀스 다이어그램:
 *
 *  시간 →
 *  ═══════════════════════════════════════════════
 *  color 0: [work A] [work B] [work C]
 *                                      ↑ flush 호출
 *  color 1:                     [work D] [work E]
 *                                ↑ 새로 큐잉된 work
 *
 *  flush는 color 0의 A, B, C만 기다림
 *  color 1의 D, E는 대상이 아님
 */

올바른 정리(cleanup) 패턴

/* 드라이버 제거 시 올바른 정리 순서 */
static void my_driver_remove(struct platform_device *pdev)
{
    struct my_device *dev = platform_get_drvdata(pdev);

    /* 1. 새로운 work 큐잉 방지 (플래그 설정 등) */
    dev->shutting_down = true;

    /* 2. pending delayed_work의 타이머 취소 + 실행 중 work 완료 대기 */
    cancel_delayed_work_sync(&dev->periodic_work);

    /* 3. 일반 work 취소 + 완료 대기 */
    cancel_work_sync(&dev->irq_work);

    /* 4. 커스텀 워크큐 파괴 (모든 work가 완료된 후) */
    if (dev->wq) {
        drain_workqueue(dev->wq);    /* 잔여 work 처리 */
        destroy_workqueue(dev->wq);
    }

    /* 5. 나머지 리소스 해제 */
    free_irq(dev->irq, dev);
    /* ... */
}

/* work 콜백에서 shutting_down 확인 */
static void my_periodic_work(struct work_struct *work)
{
    struct delayed_work *dwork = to_delayed_work(work);
    struct my_device *dev = container_of(dwork, struct my_device,
                                         periodic_work);

    /* 실제 작업 수행 */
    do_periodic_check(dev);

    /* 종료 중이 아니면 자기 재큐잉 */
    if (!dev->shutting_down)
        queue_delayed_work(dev->wq, dwork, HZ);
}

데드락 주의: work 콜백 내에서 자신이 속한 워크큐에 대해 flush_workqueue()를 호출하면 데드락이 발생합니다. work가 자신의 완료를 기다리는 순환 대기 상태가 됩니다. 마찬가지로 cancel_work_sync()를 자기 자신에 대해 호출하면 안 됩니다.

Ordered Workqueue

Ordered workqueue는 큐잉된 순서대로 work를 실행하는 것을 보장합니다. 동시에 하나의 work만 실행됩니다.

생성과 특성

/* ordered workqueue 생성 */
struct workqueue_struct *owq;
owq = alloc_ordered_workqueue("my_ordered", 0);

/* 위는 다음과 동일:
 * alloc_workqueue("my_ordered", WQ_UNBOUND | __WQ_ORDERED, 1);
 *
 * 핵심 특성:
 * - max_active = 1: 한 번에 하나의 work만 실행
 * - WQ_UNBOUND: unbound pool 사용 (CPU 마이그레이션 가능)
 * - __WQ_ORDERED: max_active 변경 방지 (freeze/thaw 시에도 유지)
 */

/* 사용 예: 상태 머신 구현 */
struct state_machine {
    struct workqueue_struct *wq;  /* ordered workqueue */
    struct work_struct event_work;
    struct list_head event_queue;
    spinlock_t lock;
    enum sm_state state;
};

/* 이벤트 핸들러: 순서 보장으로 별도의 직렬화 불필요 */
static void process_events(struct work_struct *work)
{
    struct state_machine *sm = container_of(work,
                                struct state_machine, event_work);
    /* ordered workqueue이므로 이 함수는 절대 동시 실행되지 않음 */
    /* 따라서 sm->state 접근에 별도 동기화 불필요 */
    handle_event(sm);
}
💡

Ordered workqueue vs 뮤텍스: ordered workqueue는 작업 직렬화를 위해 명시적인 뮤텍스 없이도 순서 보장을 제공합니다. 특히 상태 머신, 순차적 I/O 처리, 이벤트 큐 등에 유용합니다. 다만, 동시에 하나만 실행되므로 처리량(throughput)이 제한될 수 있습니다.

ordered vs max_active=1의 차이

/*
 * alloc_ordered_workqueue() vs alloc_workqueue(..., 0, 1):
 *
 * 둘 다 동시에 하나의 work만 실행하지만 중요한 차이가 있습니다:
 *
 * alloc_workqueue("name", 0, 1):
 *   - bound (Per-CPU) 워크큐
 *   - max_active=1이지만 각 CPU의 pool에서 독립적
 *   - 즉, CPU 0에서 1개 + CPU 1에서 1개 = 동시 2개 실행 가능!
 *   - freeze/thaw 시 max_active가 변경될 수 있음
 *
 * alloc_ordered_workqueue("name", 0):
 *   - unbound 워크큐 (단일 pool)
 *   - 시스템 전체에서 하나의 work만 실행
 *   - __WQ_ORDERED로 max_active 변경 방지
 *   - 진정한 순서 보장
 */

PREEMPT_RT와 Workqueue

PREEMPT_RT (Real-Time) 패치가 적용된 커널에서는 workqueue의 동작이 일부 변경됩니다:

RT 커널에서의 주요 변화

/*
 * PREEMPT_RT에서의 workqueue 변화:
 *
 * 1. 모든 worker 스레드가 선점 가능(preemptible)
 *    - 일반 커널: softirq 컨텍스트에서 일부 work 처리 가능
 *    - RT 커널: 모든 work가 스레드 컨텍스트에서 실행
 *
 * 2. Worker 우선순위
 *    - 일반 커널: kworker는 SCHED_NORMAL
 *    - RT 커널: WQ_HIGHPRI worker는 더 높은 RT 우선순위 가능
 *
 * 3. BH(Bottom Half) workqueue
 *    - RT 커널에서 softirq가 스레드화되면서
 *    - BH workqueue가 softirq 대체 가능
 *    - WQ_BH 플래그로 BH 컨텍스트 에뮬레이션
 */

/* RT 커널에서의 BH workqueue (커널 6.x+) */
#ifdef CONFIG_PREEMPT_RT
/* softirq 대신 BH workqueue를 사용하는 예 */
struct workqueue_struct *my_bh_wq;
my_bh_wq = alloc_workqueue("my_bh", WQ_BH | WQ_HIGHPRI, 0);
#endif

RT 이식성 팁: 드라이버 코드를 RT 커널에서도 정상 동작하게 하려면, workqueue를 사용하는 것이 가장 안전한 Bottom Half 메커니즘입니다. softirq/tasklet은 RT에서 스레드화되면서 기대와 다른 지연 시간을 보일 수 있지만, workqueue는 본래부터 프로세스 컨텍스트이므로 변화가 적습니다.

우선순위 역전 문제

/*
 * RT 환경에서의 우선순위 역전 시나리오:
 *
 * 1. 높은 우선순위 RT 태스크가 work 완료를 대기 (flush_work)
 * 2. work를 실행하는 kworker는 SCHED_NORMAL (낮은 우선순위)
 * 3. 중간 우선순위 태스크가 kworker를 선점
 * 4. → 높은 우선순위 태스크가 간접적으로 블록됨 (우선순위 역전)
 *
 * 해결 방법:
 * - WQ_HIGHPRI 사용 (worker 우선순위 상승)
 * - RT 태스크에서 flush_work() 대신 다른 동기화 메커니즘 사용
 * - 커널 설정에서 kworker의 RT 스케줄링 정책 설정
 */

디버깅

debugfs 인터페이스

# workqueue 상태 확인
cat /sys/kernel/debug/workqueue

# 출력 예시:
# workqueue        CPU  nr_active  max_active  flags
# events            0       0          256      0x0
# events_highpri    0       0          256      0x10 (WQ_HIGHPRI)
# events_unbound    3       1          512      0x2  (WQ_UNBOUND)
# mydrv_wq          0       2            4      0x8  (WQ_MEM_RECLAIM)

# worker pool 정보
cat /proc/stat | grep cpu   # CPU별 부하 확인

# kworker 스레드 목록
ps -eo pid,comm,wchan | grep kworker

# 특정 kworker가 어떤 work를 실행 중인지 확인
cat /proc/<kworker_pid>/stack

# workqueue_attrs 확인 (sysfs)
ls /sys/bus/workqueue/devices/
cat /sys/bus/workqueue/devices/writeback/cpumask
cat /sys/bus/workqueue/devices/writeback/nice

sysfs를 통한 런타임 튜닝

# unbound 워크큐의 CPU 친화성 변경
echo ff > /sys/bus/workqueue/devices/writeback/cpumask

# unbound 워크큐의 nice 값 변경
echo 5 > /sys/bus/workqueue/devices/writeback/nice

# NUMA 인식 설정
echo 1 > /sys/bus/workqueue/devices/writeback/numa

ftrace를 통한 workqueue 추적

# workqueue 이벤트 활성화
echo 1 > /sys/kernel/debug/tracing/events/workqueue/enable

# 또는 개별 이벤트만:
echo 1 > /sys/kernel/debug/tracing/events/workqueue/workqueue_queue_work/enable
echo 1 > /sys/kernel/debug/tracing/events/workqueue/workqueue_execute_start/enable
echo 1 > /sys/kernel/debug/tracing/events/workqueue/workqueue_execute_end/enable

# 추적 결과 확인
cat /sys/kernel/debug/tracing/trace

# 출력 예시:
# kworker/0:1-123  [000]  workqueue_execute_start: work=0xffff... function=my_work_handler
# kworker/0:1-123  [000]  workqueue_execute_end:   work=0xffff... function=my_work_handler

일반적인 버그와 진단

/* 버그 1: work_struct를 포함한 구조체의 조기 해제 */

/* 잘못된 코드 */
static void bad_cleanup(struct my_device *dev)
{
    kfree(dev);  /* dev->work가 아직 실행 중일 수 있음! */
}

/* 올바른 코드 */
static void good_cleanup(struct my_device *dev)
{
    cancel_work_sync(&dev->work);       /* 완료 대기 */
    cancel_delayed_work_sync(&dev->dwork);
    kfree(dev);  /* 이제 안전 */
}

/* 버그 2: work 콜백에서 자기 워크큐 flush */
static void deadlock_work(struct work_struct *work)
{
    struct my_device *dev = container_of(work, struct my_device, work);
    flush_workqueue(dev->wq);  /* 데드락! 자기 완료를 기다림 */
}

/* 버그 3: 스택에 work_struct 할당 */
static void stack_work_bug(void)
{
    struct work_struct work;  /* 스택 변수! */
    INIT_WORK(&work, handler);
    schedule_work(&work);
    /* 함수 반환 후 work가 실행되면 → 스택 손상 */
}

/* 버그 4: 초기화 전 큐잉 */
static int init_order_bug(void)
{
    struct my_device *dev = kzalloc(sizeof(*dev), GFP_KERNEL);
    schedule_work(&dev->work);  /* INIT_WORK 전! → 미정의 동작 */
    INIT_WORK(&dev->work, handler);  /* 늦었음 */
}

lockdep 활용: CONFIG_LOCKDEP을 활성화하면 workqueue 관련 데드락을 조기에 감지할 수 있습니다. lockdep은 flush_work/cancel_work_sync에서의 잠금 순서 위반, 순환 의존성 등을 보고합니다. 개발 중에는 반드시 활성화하세요.

사용 패턴과 모범 사례

패턴 1: IRQ → Bottom Half 위임

가장 일반적인 패턴으로, 인터럽트 핸들러에서 최소 작업만 수행하고 나머지를 워크큐로 위임합니다:

struct my_net_device {
    void __iomem *regs;
    int irq;
    struct work_struct rx_work;
    struct workqueue_struct *wq;
    u32 pending_status;
};

static irqreturn_t my_net_irq(int irq, void *data)
{
    struct my_net_device *ndev = data;

    /* Top Half: 최소 작업 */
    ndev->pending_status = readl(ndev->regs + STATUS_REG);
    writel(ndev->pending_status, ndev->regs + ACK_REG);

    /* Bottom Half로 위임 */
    queue_work(ndev->wq, &ndev->rx_work);
    return IRQ_HANDLED;
}

static void my_net_rx_work(struct work_struct *work)
{
    struct my_net_device *ndev = container_of(work,
                                struct my_net_device, rx_work);

    /* 프로세스 컨텍스트: 슬립, 뮤텍스, 메모리 할당 가능 */
    struct sk_buff *skb = netdev_alloc_skb(ndev->netdev, len);
    /* ... 패킷 처리 ... */
}

패턴 2: 주기적 작업 (Self-Requeueing)

struct health_monitor {
    struct delayed_work check_work;
    struct workqueue_struct *wq;
    bool running;
};

static void health_check(struct work_struct *work)
{
    struct delayed_work *dwork = to_delayed_work(work);
    struct health_monitor *mon = container_of(dwork,
                                struct health_monitor, check_work);

    /* 상태 점검 수행 */
    check_device_health(mon);
    update_statistics(mon);

    /* 자기 재큐잉: 5초 후 다시 실행 */
    if (mon->running)
        queue_delayed_work(mon->wq, dwork, 5 * HZ);
}

/* 시작 */
static int start_monitoring(struct health_monitor *mon)
{
    mon->wq = alloc_workqueue("health_mon", WQ_FREEZABLE, 0);
    if (!mon->wq)
        return -ENOMEM;

    INIT_DELAYED_WORK(&mon->check_work, health_check);
    mon->running = true;
    queue_delayed_work(mon->wq, &mon->check_work, 0);
    return 0;
}

/* 정지 */
static void stop_monitoring(struct health_monitor *mon)
{
    mon->running = false;
    cancel_delayed_work_sync(&mon->check_work);
    destroy_workqueue(mon->wq);
}

패턴 3: 작업 배치 처리

struct batch_processor {
    struct work_struct batch_work;
    struct list_head pending_items;
    spinlock_t lock;
    struct workqueue_struct *wq;
};

/* 빠른 경로: 아이템 추가 (IRQ/atomic 컨텍스트에서 호출 가능) */
static void enqueue_item(struct batch_processor *bp,
                         struct work_item *item)
{
    unsigned long flags;

    spin_lock_irqsave(&bp->lock, flags);
    list_add_tail(&item->list, &bp->pending_items);
    spin_unlock_irqrestore(&bp->lock, flags);

    /* work가 이미 pending이면 큐잉되지 않음 (중복 방지) */
    queue_work(bp->wq, &bp->batch_work);
}

/* 느린 경로: 축적된 아이템 일괄 처리 */
static void process_batch(struct work_struct *work)
{
    struct batch_processor *bp = container_of(work,
                                struct batch_processor, batch_work);
    LIST_HEAD(local_list);
    struct work_item *item, *tmp;

    /* 리스트를 로컬로 이동 (lock 구간 최소화) */
    spin_lock_irq(&bp->lock);
    list_splice_init(&bp->pending_items, &local_list);
    spin_unlock_irq(&bp->lock);

    /* lock 없이 일괄 처리 */
    list_for_each_entry_safe(item, tmp, &local_list, list) {
        process_single_item(item);
        list_del(&item->list);
        kfree(item);
    }
}

패턴 4: 모듈 전체 예제

#include <linux/module.h>
#include <linux/workqueue.h>
#include <linux/slab.h>

struct my_driver {
    struct workqueue_struct *wq;
    struct work_struct event_work;
    struct delayed_work poll_work;
    atomic_t event_count;
    bool active;
};

static struct my_driver *drv;

static void event_handler(struct work_struct *work)
{
    struct my_driver *d = container_of(work, struct my_driver,
                                        event_work);
    int count = atomic_read(&d->event_count);
    pr_info("Processing %d events\n", count);
    atomic_set(&d->event_count, 0);
}

static void poll_handler(struct work_struct *work)
{
    struct delayed_work *dwork = to_delayed_work(work);
    struct my_driver *d = container_of(dwork, struct my_driver,
                                        poll_work);

    pr_info("Polling device status\n");

    if (d->active)
        queue_delayed_work(d->wq, dwork, HZ);
}

static int __init my_driver_init(void)
{
    drv = kzalloc(sizeof(*drv), GFP_KERNEL);
    if (!drv)
        return -ENOMEM;

    /* ordered + MEM_RECLAIM 워크큐 생성 */
    drv->wq = alloc_ordered_workqueue("my_driver", WQ_MEM_RECLAIM);
    if (!drv->wq) {
        kfree(drv);
        return -ENOMEM;
    }

    INIT_WORK(&drv->event_work, event_handler);
    INIT_DELAYED_WORK(&drv->poll_work, poll_handler);
    atomic_set(&drv->event_count, 0);
    drv->active = true;

    /* 주기적 폴링 시작 */
    queue_delayed_work(drv->wq, &drv->poll_work, HZ);

    pr_info("my_driver loaded\n");
    return 0;
}

static void __exit my_driver_exit(void)
{
    /* 정리 순서가 중요! */
    drv->active = false;                          /* 재큐잉 방지 */
    cancel_delayed_work_sync(&drv->poll_work);   /* 타이머+work 취소 */
    cancel_work_sync(&drv->event_work);          /* work 취소 */
    destroy_workqueue(drv->wq);                   /* 워크큐 파괴 */
    kfree(drv);

    pr_info("my_driver unloaded\n");
}

module_init(my_driver_init);
module_exit(my_driver_exit);
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("Workqueue usage example");
MODULE_AUTHOR("MINZKN");

모범 사례 요약

규칙설명
시스템 워크큐 우선 사용특별한 이유가 없으면 schedule_work()/schedule_delayed_work() 사용
WQ_MEM_RECLAIM 설정메모리 회수 경로에서 사용하는 워크큐에 필수
cancel_*_sync() 호출모듈 제거/장치 해제 전 반드시 호출
스택에 work 할당 금지work_struct는 반드시 힙 또는 전역/정적 메모리에 할당
자기 flush 금지work 콜백에서 자신의 워크큐를 flush하면 데드락
container_of 사용work 콜백에서 부모 구조체 접근 시 container_of 패턴 사용
ordered 워크큐 활용직렬 실행이 필요하면 alloc_ordered_workqueue() 사용
CPU 집약적 work 표시오래 실행되는 CPU bound work에는 WQ_CPU_INTENSIVE 사용

API 빠른 참조

범주API설명
초기화INIT_WORK()work_struct 동적 초기화
초기화INIT_DELAYED_WORK()delayed_work 동적 초기화
초기화DECLARE_WORK()work_struct 정적 선언+초기화
초기화DECLARE_DELAYED_WORK()delayed_work 정적 선언+초기화
워크큐alloc_workqueue()커스텀 워크큐 생성
워크큐alloc_ordered_workqueue()ordered 워크큐 생성
워크큐destroy_workqueue()워크큐 파괴
큐잉queue_work()work를 워크큐에 큐잉
큐잉queue_delayed_work()delayed_work를 지연 큐잉
큐잉schedule_work()system_wq에 큐잉 (편의 함수)
큐잉schedule_delayed_work()system_wq에 지연 큐잉
큐잉mod_delayed_work()pending delayed_work 지연 시간 변경
취소cancel_work_sync()work 취소 + 실행 완료 대기
취소cancel_delayed_work()타이머만 취소 (비동기)
취소cancel_delayed_work_sync()타이머 + work 취소 + 완료 대기
플러시flush_work()특정 work 완료 대기
플러시flush_workqueue()워크큐의 모든 work 완료 대기
플러시drain_workqueue()완료 대기 + 새 큐잉 차단