만족

[Linux Kernel] Deferrable Functions 본문

[Linux Kernel] Deferrable Functions

기타 CS 이론/Kernel Satisfaction 2021. 6. 15. 05:31

 

인터럽트가 발생할 경우 다른 프로세스들을 정지시킨 채로 인터럽트 핸들러가 우선적으로 실행되며

이 인터럽트 핸들러는 non-preemptive(다른 프로세스가 선점할 수 없음)하며,

다른 프로세스, 심지어 다른 인터럽트 핸들러들 까지도 현재 인터럽트 핸들러가 끝나기 전까지는 실행되지 않는다.

 

인터럽트 핸들러에서 실행하는 작업 중,

중요성이 비교적 낮고 시간이 오래 걸리는 작업에 대해

시스템 반응성을 최대한 보전하면서 진행하려면 어떻게 해야 할까?

 

중요도가 높은 것은 Top Half(즉시 한번에 처리),

그렇지 않은 것은 Bottom Half(여유가 될 때 까지 연기)로 이관하여

Interrupt Handler의 동작을 쪼갤 수 있다.

 

Kinds of Bottom Halves

Bottom Half와 관련된 동작들은 모두 Top Half보다 중요도가 떨어지며, 실행 시점이 연기될 수 있다.

 

그러나 여전히 Interrupt Handler의 Bottom Half 동작 역시 Interrupt Context에서 처리된다는 점을 명확히 하고 가자.

 

Bottom Half: 다른 CPU Core에 대해서 동시 실행이 불가능하다.

Tasklet: 다른 CPU Core에 대해서 동시 실행이 가능하지만, 같은 Device에 대해서는 불가능하다.

Softirq: 다른 CPU Core에 대해서 동시 실행이 가능하고, 같은 Device에 대해서도 가능하다.

 

Softirq가 무조건 좋네? 할 수도 있지만,

Softirq는 커스텀이 불가능하기 때문에 수정이 필요할 경우 kernel code를 수정하여 다시 컴파일해야 한다. 

 

Bottom Halves: 실행 시점

1.커널이 시스템 콜 핸들링을 완료했을 떄

2.커널이 예외를 처리했을 때

3.커널이 scheduling()함수를 호출했을 때

등에 실행된다.

 

Tasklet

Tasklet은 다른 CPU Core에 대해서 동시 실행이 가능하지만, 같은 Device에 대해서는 불가능하다.

struct tasklet_struct my_tasklet;
...
void my_func(unsigned long recvData){
  //tasklet_schedult()에 의해 스케쥴된 tasklet callback
  //즉시 실행되는 것 처럼 보이지만, 위 조건을 충족했을 때 실행된다.
  printk("tasklet task is started!\n");
}
static int __init init_my_tasklet(void){
  //tasklet 초기화
  //2nd arg= callback function pointer
  //3rd arg= paramter of callback function
  tasklet_init(&my_tasklet, my_func, 3);
  
  //start schedule
  tasklet_schedule(&my_tasklet);
  
  return 0;
}

static void __exit exit_my_tasklet(void){
  tasklet_kill(&my_tasklet);
}

다시 강조하지만 tasklet 역시 Interrupt Context에서 실행되기 때문에 sleep, delay 등을 사용할 수 없다.

 

만약 필요한 경우 Interrupt Handler쪽이 아니라 pthread를 사용하는 쪽으로 진행해야 한다.

 

Enable/Disable Tasklet

//tasklet을 비활성화한다
//disable되기 전 schedule된 경우 해당 tasklet은 대기 상태가 된다 (tasklet_enable되면 실행 가능)
tasklet_disable(&my_tasklet);

//tasklet을 활성화한다
//enable되기 전 N번 schedule되었더라도, 1번만 스케쥴된다.
tasklet_enable(&my_tasklet);

 

KThread

K-스레드 아님 ㅎㅎ;

KThread는 커널 영역에서 동작하는 스레드이다.

 

커널 영역에서 실행되지만, 프로세스 컨텍스트에서 실행되기 때문에 sleep, delay 등을 사용할 수 있다.

 

#include <linux/kthread.h>

struct task_struct* my_kthread= NULL;

int kthread_func(void* v){
  //kthread_stop()한다고 해도 kthread가 kill되는 것이 아니기 때문에
  //kthread 내에서 종료 여부를 확인해 주어야 한다.
  while(!kthread_should_stop()){
    //some jobs...
  }
}

...
static int __init my_module_init(void){
  //kthread 생성
  my_kthread= kthread_create(kthread_func, NULL, "K-THREAD");
  if(my_kthread!= NULL){
    //생성에 성공했을 경우 func 시작
    wake_up_process(my_kthread);
  }
  return 0;
}
static void __exit my_module_exit(void){
  if(my_kthread){
    kthread_stop(my_kthread);
  }
}

WorkQueue

 

Work Queue에 work들을 넣어두면,

CPU Core 갯수만큼 존재하는 worker pool에서 work를 꺼내서 실행한다.

 

worker thread가 부족하면, work pool에서 worker thread(kthread)를 추가로 생성해 처리하기도 한다.

 

마찬가지로 Process Context에서 실행되기 때문에, 일반 프로세스와 동일하게 스케쥴링된다.

 

다량의 KThread와 적절한 스케쥴링이 필요한 경우 유용하게 사용할 수 있다.

 

#include <linux/workqueue.h>

static struct workqueue_struct* my_wq;
static struct my_work_t{
  struct work_struct my_work;
  int data;
};
struct my_work_t work;

void my_func(struct work_struct* work){
  msleep(1000);
  //my_work_t 구조체의 시작 주소와, my_work_t 안의 my_work는 시작 주소가 동일하기 때문에 가능하다
  struct my_work_t* wt= (struct my_work_t*)work;
  printk("my data is %d\n", wt.data);
}
...
static int __init my_module_init(void){
  int ret= 0;
  
  //create workqueue
  my_wq= create_workqueue("work_queue_example");
  
  //2개의 work 등록-1
  work= (struct my_work_t*)kmalloc(sizeof(struct my_work_t), GFP_KERNEL);
  if(work){
    work->data= 1;
    
    //work 초기화
    //my_work_t 구조체의 시작 주소와, my_work_t 안의 my_work는 시작 주소가 동일하기 때문에 가능하다
    INIT_WORK((struct work_struct*) work, my_func);
    //workqueue에 work 추가
    ret= queue_work(my_wq, (struct work_struct*)work);
  }
  
  //2개의 work 등록-2
  work= (struct my_work_t*)kmalloc(sizeof(struct my_work_t), GFP_KERNEL);
  if(work){
    work->data= 2;
    
    //work 초기화
    //my_work_t 구조체의 시작 주소와, my_work_t 안의 my_work는 시작 주소가 동일하기 때문에 가능하다
    INIT_WORK((struct work_struct*) work, my_func);
    //workqueue에 work 추가
    ret= queue_work(my_wq, (struct work_struct*)work);
  }
  
  return ret;
}
static void __exit my_module_exit(void){
  //workqueue에 존재하는 work 전부 삭제
  flush_workqueue(my_wq);
  //workqueue 제거
  destroy_workqueue(my_wq);
}

'기타 CS 이론 > Kernel' 카테고리의 다른 글

[Linux Kernel] Real-Time CPU Scheduling  (0) 2021.06.15
[Linux Kernel] RCU (Read-Copy Update)  (0) 2021.06.15
[Linux Kernel] Interrupt  (0) 2021.06.15
[Linux Kernel] Kernel Linked List  (0) 2021.03.28


Comments