리눅스 커널 Tasklet과 Workqueue 비교

리눅스 커널은 다양한 비동기 작업을 관리하기 위한 여러 메커니즘을 제공하는데, 그 중 Tasklet과 Workqueue는 주목할 만한 두 가지 방법이다. 이 두 가지 메커니즘은 각각 고유한 특성과 사용 사례를 가지고 있으며, 어떤 상황에서 어떻게 사용해야 하는지를 이해하는 것이 중요하다. 이 글에서는 이에 대해 다룬다.

Tasklet과 Workqueue 차이점 요약

Tasklet과 workqueue의 차이점을 간단히 요약하면 다음 테이블과 같다.

taskletworkqueue
동작방식InterruptSchedule
장점빠른 응답으로 처리가 빠르다.시스템에 무리가 없다.
단점시스템에 무리를 줄 수 있다.상대적으로 처리가 느리다.

Tasklet

Tasklet은 리눅스 커널에서 가장 기본적인 비동기 작업 스케줄링 메커니즘 중 하나이다. Tasklet은 Soft IRQ 서비스 중 하나로 동적으로 Soft IRQ 서비스를 쓸 수 있게 만든 인터페이스이다. 아래는 softirq 서비스 이름들을 담은 배열 선언부인데 “TASKLET“을 확인할 수 있다.

const char * const softirq_to_name[NR_SOFTIRQS] = {
        "HI", "TIMER", "NET_TX", "NET_RX", "BLOCK", "IRQ_POLL",
        "TASKLET", "SCHED", "HRTIMER", "RCU"
};

Soft IRQ TASKLET 서비스가 스케줄 되면 tasklet 서비스 핸들러인 tasklet_action() 함수가 호출되고, 등록되어 있는 tasklet 작업을 수행한다.

Tasklet 특징

Tasklet은 다음과 같은 특징을 가진다.

  • 빠른 응답: Tasklet은 인터럽트 컨텍스트에서 실행되며, 작업을 빠르게 처리하기 위해 설계되었다. 따라서 빠른 응답이 필요한 작업에 적합하다.
  • 단일 CPU: Tasklet은 특정 CPU에 바인딩되지 않으므로 여러 CPU 코어에서 동작할 수 있다.
  • 인터럽트 가능: Tasklet은 본질적으로 인터럽트 컨텍스트에서 동작하므로 인터럽트를 다시 활성화할 수 있다.
  • 인터럽트와 밀접한 관련: Tasklet은 인터럽트 핸들러 내에서 사용하기 위해 설계되었으며, 주로 하드웨어 인터럽트와 관련된 작업을 처리하는 데 사용된다.

Tasklet 예제 코드

#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/interrupt.h>
#include <linux/kthread.h>
#include <linux/delay.h>

static void my_tasklet_handler(struct tasklet_struct *ts);

static DECLARE_TASKLET(my_tasklet, my_tasklet_handler);

struct task_struct *tasklet_test = NULL;

static int tasklet_test_fn(void *arg)
{
        while (!kthread_should_stop()) {
                printk("tasklet_test: tasklet schedule!!\n");
                tasklet_schedule(&my_tasklet);
                ssleep(1);
        }
        return 0;
}

static void my_tasklet_handler(struct tasklet_struct *ts)
{
        printk("my_tasklet run: do what the tasklet want to do....\n");
        mdelay(500);
}

static int my_tasklet_init(void)
{
        printk("module init start. \n");

        if (!tasklet_test) {
                tasklet_test = kthread_run(tasklet_test_fn, NULL, "tasklet_test");
                printk("run tasklet test kthread --\n");
        }

        printk("module init end.\n");
        return 0;
}

static void my_tasklet_exit(void)
{
        kthread_stop(tasklet_test);
        tasklet_test = NULL;
        tasklet_kill(&my_tasklet);
        printk("Goodbye, tasklet!\n");
}

module_init(my_tasklet_init);
module_exit(my_tasklet_exit);

MODULE_DESCRIPTION("Tasklet example");
MODULE_LICENSE("GPL");

my_tasklet 은 my_tasklet_init() 에서 tasklet_test_fn을 수행하는 kthread를 생성하고 실행한다. tasklet_test_fn은 1초 마다 my_tasklet에 대해 tasklet_schedule()을 한다.

my_tasklet.c 를 모듈 빌드하고, insmod/rmmod 하는 사이 dmesg 를 확인할 수 있다.

$ sudo insmod my_tasklet.ko
$ sudo rmmod my_tasklet.ko
[  833.628630] module init start.
[  833.628650] run tasklet test kthread --
[  833.628650] module init end.
[  833.632153] tasklet_test: tasklet schedule!!
[  833.632158] my_tasklet run: do what the tasklet want to do....
[  834.650455] tasklet_test: tasklet schedule!!
[  834.650461] my_tasklet run: do what the tasklet want to do....
...
[  847.962943] my_tasklet run: do what the tasklet want to do....
[  848.986996] tasklet_test: tasklet schedule!!
[  848.987002] my_tasklet run: do what the tasklet want to do....
[  851.003974] Goodbye, tasklet!

Workqueue

Workqueue는 Tasklet과 비슷한 비동기 작업 스케줄링 메커니즘이다. Workqueue는 work를 queue에 넣고, workqueue 를 처리하는 workqueue 쓰레드가 스케쥴링 될때 workqueue에 있는 work들이 처리한다.

Workqueue는 다음과 같은 차이점이 있다.

Workqueue 특징

  • 작업 큐: Workqueue는 큐(queue)를 사용하여 작업을 관리하므로, 더 복잡한 작업의 스케줄링과 관리가 가능합니다.
  • 컨텍스트: Workqueue는 커널 스레드 컨텍스트에서 실행되며, 태스크 스케줄러에 의해 관리됩니다. 따라서 작업의 우선 순위를 높이거나 낮출 수 있습니다.
  • 멀티 CPU 지원: Workqueue는 바운드(bound)와 언바운드(unbound) 두 가지 유형이 있어, 특정 CPU에 바인딩되거나 시스템 전체에서 실행할 수 있습니다.
  • 블로킹 작업: Workqueue에서는 블로킹 작업을 수행할 수 있으므로, 블로킹 작업이 필요한 경우에 유용합니다.

Workqueue 예제 코드

#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/workqueue.h>
#include <linux/kthread.h>
#include <linux/delay.h>

static void my_workqueue_handler(struct work_struct *work);
static struct workqueue_struct *my_workqueue;
static DECLARE_WORK(my_work, my_workqueue_handler);
struct task_struct *workqueue_test = NULL;

static void my_workqueue_handler(struct work_struct *work)
{
        printk("my_workqueue_handler: do what the workqueue want to do....\n");
        mdelay(500);
}

static int workqueue_test_fn(void *arg)
{
        while (!kthread_should_stop()) {
                printk("workqueue_test: queue_work!!\n");
                queue_work(my_workqueue, &my_work);
                ssleep(1);
        }
        return 0;
}

static int __init my_workqueue_init(void)
{
        my_workqueue = create_workqueue("my_workqueue");
        if (!my_workqueue) {
                printk(KERN_ERR "Failed to create workqueue\n");
                return -ENOMEM;
        }

        if (!workqueue_test) {
                workqueue_test = kthread_run(workqueue_test_fn, NULL, "workqueue_test");
                printk("run workqueue test kthread --\n");
        }

        printk(KERN_INFO "Workqueue module initialized\n");
        return 0;
}

static void __exit my_workqueue_exit(void)
{
        kthread_stop(workqueue_test);
        workqueue_test = NULL;
        flush_workqueue(my_workqueue);
        destroy_workqueue(my_workqueue);
        printk(KERN_INFO "Workqueue module exited\n");
}

module_init(my_workqueue_init);
module_exit(my_workqueue_exit);

MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("Workqueue Example");

my_workqueue는 my_workqueue_init() 에서 workqueue_test_fn을 수행하는 kthread를 생성하고 실행한다. workqueue_test_fn은 1초 마다 my_workqueue에 대해 queue_work()을 한다.

my_workqueue.c 를 모듈 빌드하고, insmod/rmmod 하는 사이 dmesg 를 확인할 수 있다.

sudo insmod my_workqueue.ko
sudo rmmod my_workqueue
[ 4096.859600] Workqueue module initialized
[ 4096.864560] workqueue_test: workqueue schedule!!
[ 4096.864566] my_workqueue_handler: do what the workqueue want to do....
[ 4097.888192] workqueue_test: workqueue schedule!!
[ 4097.888199] my_workqueue_handler: do what the workqueue want to do....
...
[ 4107.104360] workqueue_test: workqueue schedule!!
[ 4107.104369] my_workqueue_handler: do what the workqueue want to do....
[ 4108.128220] workqueue_test: workqueue schedule!!
[ 4108.128228] my_workqueue_handler: do what the workqueue want to do....
[ 4109.136409] Workqueue module exited

결론

Tasklet과 Workqueue는 리눅스 커널에서 비동기 작업을 처리하고 스케줄링하는 데 사용되는 두 가지 중요한 메커니즘이다. Tasklet은 인터럽트 컨텍스트에서 빠르게 동작하며, 작업이 간단하고 빠른 응답이 필요한 경우에 적합하다. 반면에 Workqueue는 보다 복잡한 작업을 처리하고 블로킹 작업을 수행하는 데 유용하며, 커널 스레드 컨텍스트에서 실행되므로 우선 순위와 블로킹 작업을 지원한다. 따라서, 작업의 특성과 요구 사항에 따라 적절한 메커니즘을 선택해야 한다.

참고

답글 남기기