본문 바로가기

CS/[Embedded]Embedded System Software

[Embedded] Interrupt Handling - Top / Bottom Half (Softirqs, Tasklets, Workqueues)

Interrupt Handling- Top/Bottom half

커널의 하드웨어 interrupt 처리 방식인 interrupt handling는 종종 긴 시간 처리가 필요한 경우가 있다.

 

하지만 interrupt handling는 비동기적(asynchronize)으로 실행되기 때문에 다른 interrupt handling를 포함한 다른 중요한 코드를 중단 시키게 되는데, 이 중단을 너무 오래 하면 안되기 때문에 가능한 빠르게 interrupt 처리가 진행 되어야 한다.

 

또한, interrupt handling 가 처리되는 동안은 모든 interrupt를 disable 하게 만들게 되는데 모든 hw 와 통신할 수 없게 되고, 이는 시스템 반응속도, 성능에 많은 영향을 미치기 때문에 가능한 빠르게 interrupt 처리가 진행 되어야 한다.

 

예를들어, 네트워크 트래픽 수신을 처리하는 동안 커널이 키 입력을 처리할 수 없어서는 안된다. 

 

interrupt handler는 process context 가 아닌, interrupt context에서 실행되므로 sleep 모드(휴면상태)로 바뀔 수 없는 제약 조건도 있기 때문에,

 

interrupt handling는 최대한 빠른 시간 내에 중요한 처리를 먼저하고, 그렇지 않은 작업은 이후로 미루는 형태로 이루어 지도록 한다.

이렇게 두 부분으로 나눈 것이, top half / bottom half 라고 한다.

 

일반적으로 top half를 interrupt handler이라고 한다. 그리고 나머지 이후에 처리하는 부분을 bottom half 라고 한다.

 

bottom half는 interrupt 를 활성화된 상태에서 실행되므로 interrupt 비활성화 시간을 최소화 하여 시스템 지연시간을 줄일 수 있다.

즉, 최대한 많은 일을 bottom half에 덜어냄으로서 interrupt handler가 중단시켰던 시스템 제어권을 가능한 빠르게 돌려줄 수 있다.

 

 Top half

-interrupt에 비동기적으로 즉각적으로 반응하는 부분으로 일반적인 interrupt handler이다.

 

-all interrupt 는 disable 되어 있다.

 

-interrupt handler이므로 request_irq 함수를 이용하여 커널에 등록한다.

 

-interrupt 수신 확인이나 hw 재설정처럼 처리 시한이 중요한 작업을 처리

 

-디바이스로부터 데이터를 복사해 오거나 복사 하는 작업은 시간에 민감한 작업이므로 top half에서 처리하는 것이 좋다.

 

-bottom half 에 해당하는 부분을 수행하기 위해 bottom half에 해당하는 함수를 schedule 하는 역할을 한다.

 

Bottom half

 

위에 기술된 작업들 이외의 거의 모든 일들은 bottom half에서 진행하게 된다. interrupt handler가 처리하지 않은 모든 인터럽트 관련 처리를 처리를 수행한다. 

 최대한 많은 일을 bottom half로 덜어냄으로서 handler가 중단시켰던 시스템 제어권을 가능한 빨리 돌려줄 수 있는 것이다.

 

 사실 이러한 어떠한 작업을 어디서 해야 한다는 규칙(top/bottom)은 따로 없기 때문에 개발자가 알아서 하면 되는 것이긴 하다. 

하지만 아래의 규칙을 지키면 좋다.

1. 실행 시간에 민감한 작업이라면 top half

2. hw와 관련된 작업이라면 top half

3. 다른 interrupt 가 방해하면 안 되는 작업이라면 top half

4. 그 외의 작업들은 bottom half

 

작업을 왜 뒤로 미루는지, 미루는 시기가 정확이 언제인지 이해하는 것이 중요하다.

interrupt handler는 처리 중인 interrupt line 을 모든 processor에 대해 비활성화 시키고, 현재 프로세서의 모든 interrupt line을 비활성화 시킨 상태로 실행되므로 interrupt handler에서 처리할 작업의 양을 제한할 필요가 있다. 

시스템 반응속도 및 성능에 영향을 미치는 중요한 문제인 것이다. 예를 들어 네트워크 트래픽 수신을 처리하는 동안 커널이 키입력을 처리할 수 없어서는 안되는 것이다.

 

bottom half를 나중에 처리한다는 의미가 단지 지금이 아닌 시점을 뜻한다는 것도 중요하다. 해당 작업을 미래의 특정 시점에 처리한다는 것이 아니라, interrupt handler가 종료 되고 어떤 순간 진행한다는 의미이다. 

실제로 interrupt handler가 종료된 직후에 처리되는 경우가 많다.

 

리눅스 뿐 아니라 대부분 운영체제에서 하드웨어 인터럽트 처리를 두 부분으로 나눈다

 

-all interrupt 는 able 되어 있다.

-process를 깨우거나, io 연산을 진행

 

Interrupt Handling in Linux

결국 정리하면 interrupt handling은 phase 1,2,3 세단계로 나눌 수 있다.

phase 1 : Critical phase

->generic interrupt handler

phase 2 : Immediate phase

->device driver handler

phase 3 : deferred phase

->deferred handlers(bottom half)

 

Bottom half mechanism

bottom half를 구현하는 방법에는 여러가지가 있는데,

대표적으로

1. Softirqs

2. Tasklets

3. Workqueues

가 있다.

 

softirq는 모든 processor에 동시에 실행할 수 있는 정적으로 정의된 bottom half

Tasklets는 softirq를 기반으로 만들어진 동적으로 생성 가능한 유연한 bottom half

Workqueues는 tasklet, softirq와 별도 방식을 사용하는 sub system 이다.

 

softirq를 직접 사용하는 경우는 드물다. 일반적으로 tasklet을 사용하는 경우가 더 많다. 하지만, tasklet이 softirq를 기반으로 만들어 졌으므로 softirq에 대해 알아야 한다.

 

 softirq는 모든 프로세서에서 동시에 실행할 수 있는 정적으로 정의된 bottom half이고, 같은 유형의 softirq 또한 동시에 실행될 수 있다.

두 개의 다른 tasklet이 다른 프로세서에서 동시에 실행될 수는 있지만, 같은 유형의 tasklet은 동시에 실행될 수 없다.

 

따라서 tasklet은 사용 편의성과 성능 사이의 균형점을 제공한다.

대부분의 경우는 tasklet으로 처리가 충분하지만, 네트워크 같은 성능이 아주 중요한 경우에는 softirq가 더욱 유용하다. 하지만 같은 softirq가 동시에 실행될 수 있으므로 사용 시 주의가 더욱 필요하고, 커널 컴파일 시에 정적으로 등록이 되어 있어야 한다. 

반면 tasklet은 코드상에서 동적으로 등록 가능하다.

 

 

Softirq

softirq는 컴파일 시에 정적으로 할당한다. 따라서 tasklet처럼 동적으로 등록하거나 제거할 수 없다.

 

softirq 코드는 커널 소스의 <kernel/softirq.c> 파일에 들어 있다.

 

<linux/interrupt.h> 파일에 정의된 softirq_action 구조체를 이용하여 표현한다.

struct softirq_action{
	void (*action) (struct softirq_action *);
}

softirq.c 파일에는 해당 구조체의 32개 짜리 배열이 선언되어 있고, 등록된 softirq 마다 해당 배열을 하나씩 사용한다.

현재 커널에 등록할 수 있는 최대 개수는 32개이지만, 실제로는 9개 까지로 등록되어 있다.

 

softirq handlers 는 interrupts enable이며, process context가 아닌 interrupt context에서 돌아가므로 sleep 할 수 없다.

 

softirq는 하나의 프로세서에서 동시에 실행할 수 없다. 하지만 다른 프로세서에서는 동시에 실행이 가능하다. 

등록된 softirq는 실행 표시를 해 주어야 실행된다. softirq를 올린다고 표현 하며 보통 interrupt handler가 실행 종료 전에 실행 표시를 해두는 것이다 이후 적당한 시간이 되면 softirq가 실행된다. 

 

지연 상태인 softirq는 

1. hw interrupt 코드가 return 되는 경우

2. ksoftirqd kernel thread에 의해

3. 네트워크 서브시스템 처럼 명시적으로 코드에서 지연 상태 softirq를 확인하고 실행하는 경우

에 확인 및 실행이 진행된다.

 

softirq는 열거형으로 컴파일 시 <linux/interrupt.h> 파일에 정적으로 선언한다. kernel은 0 부터 시작하는 index 값을 우선순위로 사용한다. 

필요한 우선순위에 따라 새 항목을 맨 뒤가 아닌 곳에 추가할 수도 있다.

 

softirq handler에서 사용하는 공유 데이터에는 적절한 lock이 필요하기 때문에 coding에 어려움을 많이 겪는다.(reentrant, synchronization)

 

이러한 이유로 taskltes이 주로 softirq보다 선호된다.

 

 

 

Tasklet

tasklet은 softirq와 유사하지만 더 간단하고 lock 사용 제한이 더욱 유연하다.

softirq의 HI_SOFTIRQ, TASKLET_SOFTIRQ 두 가지를 사용하며 두 가지 softirq에 함수를 붙여가며 사용하는 것이 tasklet인 것이다.

둘의 차이는 우선순위로 우선순위가 더 높은 것을 먼저 실행하는 것이다,

 

tasklet 또한 softirq를 이용하는 것으로 interrupt context에서 실행되는 것으로 all interrupt enable, cannot sleep 특성이 있다.

하지만 softirq와 달리 같은 tasklet은 다른 processor에서도 concurrently하게 사용이 불가능하다. 

단, 다른 tasklet은 다른 cpu에서도 동시 실행이 가능하다.

 

서로 다른 Tasklet이 서로 다른 cpu에서 동시 실행 될 때, 만약 shared data 상태라면 동기화, lock 문제를 해결해야 한다.

Using Tasklet

사용 방법은 다음과 같다. 

1. tasklet선언/생성

2.scheduling

 

tasklet-example

 

Workqueue

Workqueues는 kernel thread 가 수행해주는 것으로 process context에서 수행되는 것이다.

interrupt context가 아닌 process context에서 실행되다 보니 sleep이 가능하다.

또한 process context에서 실행되다 보니 scheduling도 가능하게 된다.

 

여러모로 통상적인 process context의 이점을 누릴 수 있는 것이다.

 

workqueue를 쓸지, tasklet이나 softirq를 쓸지 결정하는 일은 보통 간단하다.

지연되는 작업이 sleep 상태 전환이 필요한 경우라면 workqueue를 사용하고

그렇지 않다면 softirq나. tasklet을 사용하면 된다.

 

sleep 상태 전환이 가능하기 때문에, 많은 양의 memory 할당이나 semaphore 할당, blcok I/O 작업이 필요한 경우에 매우 유용하다.

ex) kmalloc (GLP_KERNEL) 써도 괜찮다.

 

Using WorkQueues

1. create/destroying a work queue

기본 워크 큐도 있지만 새로운 워크 큐를 생성할 수 도 있다.

 

 

2. Creating a work

지연시킬 작업을 생성한다.

생성하는 방식은 두가지가 있다.

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

: 구조체를 정적으로 선언하는 방법이다.

이름이 name, 함수가 func, 해당 함수 인자가 data인 work_struct 구조체를 정적으로 생성한다.

 

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

: 포인터로 생성하는 방법이다.

work 포인터가 가르키는 구조체를 func 함수와 data 인자로 구조체를 초기화 한다.

 

3. Scheduling a work

schedule_work 함수의 경우는 시스템 기본 work queue에 스케줄링 하는 함수이고,

queue_work 함수의 경우는 직접 생성한 새로운 work queue에 스케줄링 하는 함수이다.

 

workqueue-example