2021. 12. 31. 13:03ㆍLinux
- 목차
* 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와는 관계 없음
'Linux' 카테고리의 다른 글
Linux kernel process (0) | 2021.12.31 |
---|---|
kernel에서 user level로 uevent 보내는 법 (how to send an uevent to the user space) (0) | 2021.12.31 |
Linux kernel Bottom half 정리 (softirq, tasklet, workqueue) - part 1 (0) | 2021.12.14 |
virtual address space (0) | 2021.12.13 |
process scheduling (0) | 2021.12.13 |