Linux kernel Bottom half 정리 (softirq, tasklet, workqueue) - part 2

2021. 12. 31. 13:03Linux

    목차
반응형

* softirq 사용

 

    linux/interrupt.h

        enum

        {

              HI_SOFTIRQ=0,

              TIMER_SOFTIRQ,

              NET_TX_SOFTIRQ,

              NET_RX_SOFTIRQ,

              BLOCK_SOFTIRQ,

              BLOCK_IOPOLL_SOFTIRQ,

              TASKLET_SOFTIRQ,

              SCHED_SOFTIRQ,

              HRTIMER_SOFTIRQ,        high resolution timer

              RCU_SOFTIRQ,         <- RCU lock

        

              NR_SOFTIRQS

        };

            

 

1) registering handler
    
        open_softirq(NET_TX_SOFTIRQ, net_tx_action);    <- 동적 등록
 
2) raise    
    1) 핸들러를 enum list 추가한 
    2) open_softirq 등록하면, raise 가능
    3) raise 대부분 ISR에서 수행        
    4) 그럼, do_softirq() 실행됨
        여기서 softirq handler 수행됨
 
 
    raise_softirq(NET_TX_SOFTIRQ);  -> enable
        
        이렇게 하면
        kernel 다음의 do_softirq 호출  (다음 softirq 처리 )
        net_tx_action() handler 수행됨.
        
        raise_softirq, interrupt disable  softirq raise하고
 interrupt 다시 이전 상태로 복원
            
        raise_softirq_irqoff()
            이미 interrupt 비활성화  상태에는 이것을 사용
        
        
    /*
        interrupt disabled...
    */
        
    raise_softirq_irqoff(NET_TX_SOFTIRQ);
 

 

 

* tasklet

 

softirq 기반
lock 사용이 용이 (다른 processor 동시 실행하지 않음)    
device driver 거의 softirq 아니라, tasklet 사용
    
softiq 실행 횟수가 빈번하고 병렬 처리 가능한 경우에 사용

 

 

* tasklet 구현

 

    tasklet softirq HI_SOFTIRQ, TASKLET_SOFTIRQ 사용하여 동작함    

    

    struct tasklet_struct {

        ...

        unsigned long state;        <- tasklet 상태

        ...

        void (*func)(unsigned long);

        ...

        

    };

    

    

    state 항목의 값은

        0, TASKLET_STATE_SCHED, TASKLET_STATE_RUN   하나

 

    count

        참조 count

 

 

* tasklet scheduling

 

    tasklet scheduling 정보:

tasklet_vec tasklet_hi_vec  개의 processor 별로 존재하는 구조체에 저장

     tasklet_vec과와 tasklet_hi_vec processor 별로 존재

 

tasklet_schedule() 로직
   1) tasklet 상태가 TASKLET_STATE_SCHED (실행 대기상태인지 확인
      실행 대기 (TASKLET_STATE_SCHED) 바로 return
   2) interrupt 상태를 저장하고 disable
      (tasklet 처리 동안 processor 방해받는 일을 막음)
   3) tasklet tasklet_vec 혹은 tasklet_hi_vec 추가
       
   4) TASKLET_SOFTIRQ 혹은 HI_SOFTIRQ softirq raise
      do_softirq에서 tasklet 처리하게 
        
   5) 인터럽트 상태를 복원하고 return

 

 

    tasklet_action / tasklet_hi_action handler function

    

        요약

          : tasklet_vec / tasklet_hi_vec에서

            tasklet들을 보며처리   있는 (실행 중이 아닌 활성화  ) 처리함

 

    

        1) 현재 processor interrupt disable

           processor tasklet_vec 혹은 tasklet_hi_vec list 가져옴

        2) interrupt enable

        3) 받은 list 대기 중인 tasklet 반복 처리

        4) multi-processor라면

           TASKLET_STATE_RUN flag 확인해서 

           다른 processor에서 tasklet 실행중인지 확인,

           실행중이면 다음 tasklet으로 넘어감

        5) 실행중이 아니면 tasklet state TASKLET_STATE_RUN 으로 변경

        6) count 값이 0이면 비활성화 상태이므로다른 tasklet으로 넘어감

        7) 이제다른 곳에서 실행되지도 않고실행할 수도 없도록 state 변경했기에,

           tasklet handler 수행

        8) 실행  state 변경

        9) tasklet list 있는 것이 모두 없어질 때까지 반복

 

 

* tasklet 사용

 

    1) 선언    

        1] static

아래   하나 사용
DECLARE_TASKLET(name, func, data)      <- count 0으로 생성(활성화 상태)
DECLARE_TASKLET_DISABLED(name, func, data)  <- count 1 생성
(비활성화 상태)
 
ex.
DECLARE_TASKLET(my_tasklet, my_tasklet_handler, dev);

 

               

        2] dynamic

tasklet_init(t, tasklet_handler, dev);

 

 

    2) handler

        void tasklet_handler(unsigned long int)

        

            softirq 처럼 tasklet sleep 불가

                : , semaphore, GFP_ATOMIC 아닌 kmalloc 등은 사용 불가

                

            tasklet 모든 interrupt 활성화  상태에서 실행 (공유자원 사용에 주의)

            

                여기 안에서 interrupt disable 시킬 

                원하는 interrupt 발생하지 않을  있음

 

    3) tasklet scheduling

        taskle_schedule(&my_tasklet);    <- ISR에서 호출

        

            1] tasklet_vec(or tasklet_hi_vec) 추가

            2] raise

                do_softirq에서 action 수행

        

        do_softirq 실행 시점

                1] do_sortirq

                    IRQ exit에서 호출 (do_IRQ )

                

                2] ksoftirqd kernel thread에서 수행

                

                3] 명시적으로 sub_system에서 수행

 

 

        tasklet 실행 processor

            항상  scheduling processor에서 실행 

            (processor cache/TLB flush 막기 위함)

            

    

    4) tasklet disable

        tasklet_disable() 비활성화 가능

        

            실행 중이면종료시까지 대기했다가 비활성화  리턴

            

        tasklet_disable_nosync()

            실행 중이어도그냥 리턴

            (안전하지 않음)

            

        ex.

            tasklet_disable(&my_tasklet);

            

            // tasklet 비활성   수행할  처리

            

            tasklet_enable(&my_tasklet);

 

    5) tasklet kill

        실행 대기 중인 tasklet 제거

 

        tasklet 실행 중이 아니면 (실행 중이면 대기했다가) list에서 제거

        sleep 상태 전환 가능 function

 

 

    ex.

        struct tasklet_struct     my_tasklet;

        

        static void my_tasklet_handler(unsigned long priv)

        {

            ...

        }

        

        tasklet_init(&my_tasklet, my_tasklet_handler, (unsigned long)0);

        

        @ ISR

        tasklet_schedule(&my_tasklet);

 

 

* ksoftirqd

 

    softirq tasklet 모두 processor 별로 존재하는 kernel thread 사용함

    

     thread 너무 많은 softirq 발생  경우

        이를 나중에 처리하기 위함임 (do_IRQ 반환 시점에서 처리 못한 것들을 처리)

    

 

softirq 처리 시점 (review)
        1) ISR 복귀 시점 (do_IRQ 반환 )
            do_IRQ
             +- irq_enter
             +- generic_handle_irq(__get_IRL())
             +- irq_exit()
                 +- ...
                 +- invoke_softirq   <-- 
                      +- do_softirq
                      +  or
                      +- wakeup_softirqd
                          +- wake_up_process

 

        2) ksoftriqd에서 수행

        3) 명시적으로 수행

            softirq 스스로 raise 수행할 수도 있음 (재등록)

 

 

     등록된 softirq 즉시 실행은,  

1) 시스템의 과부하 (ISR 수행이 길어짐)
2) 무시 -> 문제
        
이런 문제를 갖고 있기에, softirq 수가 많아지면
이를 처리하기 위해 kernel thread 작동
        
 kernel thread 19라는 높은 nice값으로 동작하기에,
과도한 interrupt 발생  언젠가는 실행   있음을 보장함

 

 

* ksoftirqd thread

 

    ksoftirqd/n  형태

    

    for(;;) {

        if (!softirq_pendnig(cpu))

            schedule();

        

        set_current_state(TASK_RUNNING);

        

        while (softirq_pending(cpu)) {

            do_softirq();

            if (need_resched())

                schedule();        

        }

        

        set_current_state(TASK_INTERRUPTIBLE);        

    }

 

 

 

* workqueue

 

process context에서 실행
    . 휴면상태의 전환 가능
    . 지연되는 작업이 휴면 상태 전환이 필요한 경우라면 workqueue 사용
    (많은 양의 memory 할당, semaphore 사용, block I/O 사용 가능)
        
휴면 상태의 전환 필요가 없다면, softirq tasklet 사용
        
. workqueue 대안은 kernel thread

 

 

* workqueue 구현

    

    workqueu 기본 work queue thread들이 존재

    worker thread 추가로 생성 가능

    기본 작업 thread 이름은 events/n (processor  1)

 

 

    1) 자체 thread 만드는 경우

        작업 양이 많은 경우

        성능이 중요한 경우

 

 

    2) thread 자료 구조

        workqueue_struct {

            struct cpu_workqueue_struct cpu_wq[NR_CPUS];

            struct list_head list;

            const char *name;

            int singlethread;

            int freezeable;

            int rt;

        };

        

        @ kernel/workqueue.c

        

        

        struct cpu_workqueue_struct {

            spinlock_t lock;

            

            struct list_head worklist;

            wait_queue_head_t more_work;

            struct work_struct *current_struct;

            

            struct workqueue_struct *wq;

            task_t *thread;

        };

 

 

    3) 작업 자료 구조

        모든 작업 thread kernel thread 형태로 구현되며

        worker_thread() 함수를 실행

             thrad 대기 작업이 들어오면 깨어나 작업을 처리

 

 

        struct work_struct {

            atomic_oong_t data;

            struct list_head entry;

            work_funct_t func;

        };

 

     구조체는 processor 유형별로 queue 있음

    

    

        for (;;) {

            prepare_to_wait(&cwq->more_work, &wait, TASK_INTERRUPTIBLE);

            if (list_empty(&cwq->worklist))

                schedule();

            finish_wait(&cwq->more_work, &wait);

            run_workqueue(cwq);

        }

    

        

            1) thread 자신을 TASK_INTERRUPTIBLE (휴면 상태로 )

            2) 깨어나고 작업 연결 list 비어 있으면다시 휴면 상태(schedule)

            3) 비어 있지 않다면, TASK_RUNNING으로 변경 , run_workqueue 수행

 

        run_workqueue(...)

        {

            while (!list_empty(&cwq->worklist) {

                ...

                work = list_entry(cwq->worklis.next,

struct work_struct, entry);

                f = work->func;

                list_del_init(swq>worklist.next);   <- list에서 unlink 수행

                work_clear_pending(work);

                f(work);                  <-- 동적 할당인 경우, handler에서 free

            }

        }

 

 

    +---------------+          +----------------------+
    | worker thread | <-------| cpu_workqueue_struct |
    +---------------+          +----------------------+
                                 ^       ^
                                 |       |
                        +--------+       |
                        |                |
                        |      +------------------+
                        |      | workqueue_struct |      <- per worker
                        |      +------------------+          thread
                        |
                        |      +-------------+
                        +------| work_struct |---+       <-- per work
                               +-------------+   |---+       queue handler
                                  +--------------+   |
                                       +-------------+ ...
 

 

 

* workqueue usage

 

    1) 작업 생성    

       1] static

       DECLARE_WORK(name, void (*func)(void *), void *data);

        

       2] dynamic

       INIT_WORK(struct work_struct *work, void (*func)(void*), void *data);

 

 

    2) workqueue handler

        void work_handler(void *data);        

            mm nil process context에서 실행 (INTR. context 아님)

 

        휴면 상태로 전환될  있음

        

        cf.

            kernel에서는 user level address space 접근 불가하지만,

            system call 시는 해당 호출 process address space (일부접근 가능

 

    3) scheduling

        1] 기본 events 작업 추가

            schedule_work(&work);

        

        2] 일정 시간이 지난 다음에 실행

            schedule_delayed_work(&work, delay);

 

    4) flushing work

        1] flush_scheduled_work(void)

            Q 모든 작업 처리까지 wait 

            휴면 가능 (process context에서만 사용 가능)

        

        2] cancel_delayed_work(struct work_struct *work)

            지연 작업 취소

 

 

    5) 새로운 workqueue 생성

        1]  create_workqueue

        

            ex.

            struct workqueue_struct *keventd_wq;

            keventd_wq = create_workqueue("events");

 

                system processor  하나의 thread 생성

                (octa core 경우 8 생성)

 

    6) 생성한 workqueue 작업 할당

        1] queue_work

        2] queue_delayed_work

 

 

* Which bottom half to use ?

 

1) softirq
        실행 시간에 아주 민감하고 사용빈도가 높은 경우 선택
        같은 softirq 다른 processor에서 수행   있음
        
2) tasklet
        코드가 thread-safe 하지 않은 경우 사용
        같은 유형의  tasklet 동시에 실행되지 않음
        (softirq 보다는 tasklet 우선 고려)
 
3) workqueue
        지연 작업을 process context에서 수행해야  경우 사용
        kernel thread 사용

 

 

* bottomhalf lock

 

1) tasklet & workqueue
        tasklet 외부간의 동기화 mechanism 필요
2) softirq
        softirq간에서도 동기화 mechanism 필요

 

 

    공유 data 보호하기 위해선,

    botom half 처리 비활성화와 함께 락을 설정하는 경우가 존재

    

        softirq tasklet 작업을 비활성화

            local_bh_disable()

        

        반대는 

            local_bh_enable()

 

        중첩호출 가능

 

    preempt_count 사용해서 작업  횟수를 관리

    workqueue와는 관계 없음

 

반응형