BOBO's Note

Interrupt Handler 본문

Embedded System

Interrupt Handler

bobo_hee 2020. 6. 3. 03:14

Interrupt

인터럽트란 CPU가 프로그램을 실행 중일 때, I/O 하드웨어 등의 디바이스나 예외상황이 발생하여 처리가 필요한 경우 CPU에게 알려주는 것이다. 인터럽트는 크게 HW 인터럽트와 SW 인터럽트로 나뉜다.

  1. HW 인터럽트(=interrupt): 하드웨어가 발생시키는 전기적 신호이다. 주로 인터럽트라고 하면 HW 인터럽트를 의미한다. (예. I/O, timer, console 등)
  2. SW 인터럽트(=trap): exception시스템 콜이 SW 인터럽트에 해당한다.
    1. Exception: 프로그램 실행 중 발생하는 예기치 못한 에러이다. (예. divided by 0)
    2. System Call: 프로세스와 커널 사이의 인터페이스로, user 프로세스에서 privileged operation을 하고 싶을 때 시스템 콜을 호출한다.

Interrupt Handler

인터럽트 핸들러는 인터럽트를 처리하기 위해 커널이 실행하는 함수이다. ISR(Interrupt Service Routine)이라고도 한다.

 

Interrupt Handling 과정

인터럽트가 발생하면 인터럽트 모드로 전환되면서 실행 중인 프로세스를 중지하고 PCB에 context를 저장한다. Interrupt vector에서 ISR 주소를 얻고 해당 위치로 jump하여 ISR을 실행한다.

 

ISR, 즉 인터럽트 핸들러는 인터럽트 발생을 인지했음을 하드웨어에게 알린다. 이 과정이 없으면 하드웨어는 계속해서 인터럽트를 발생시킨다.

 

그 후 인터럽트에 맞는 처리를 적절하게 해주고, 기존에 실행하던 프로세스의 context를 복구하여 다시 실행한다.


Top Half와 Bottom Half

인터럽트 핸들러는 두 부분으로 나뉘어 실행된다.

  1. Top Half: 실질적인 인터럽트 핸들러이다. top half 동안 다른 인터럽트는 disable되기 때문에 최대한 빨리 끝나야 한다. 따라서 이 구간에서는 디바이스에서 데이터만 읽어오는 등의 긴급한 작업만 수행한다.
  2. Bottom Half: Top half에서 미처 처리하지 못 한 오랜 시간이 걸리는 작업들을 이 구간에서 수행한다. 리눅스에서 bottom half를 처리하는 다양한 매커니즘이 존재한다. 
    1. softirq: 미리 정의해놓은 SW 인터럽트이다. top half가 끝난 후 바로 실행된다. 원래는 32가지가 존재했지만 현재는 일부만 사용한다. HI_SOFTIRQ(높은 우선순위), TIMER_SOFTIRQ(타이머), NET_TX_SOFTIRQ(네트워크 패킷 송신), NET_RX_SOFTIRQ(네트워크 패킷 수신), TASKLEFT_SOFTIRQ(tasklet) 순으로 우선순위를 갖는다. softirq 종류에 대한 세부사항은 커널 소스의 include/linux/interrupt.h 를 확인하자.
    2. tasklet: softirq를 이용해 만든 deferral(연기) 매커니즘이다. 하나의 tasklet은 하나의 CPU에서만 실행된다. 우선순위가 더 높은 인터럽트가 발생하지 않는 이상 tasklet 작업이 끝날 때까지 CPU를 점유한다. 따라서 sleep 같이 block되는 작업을 할 수 없도록 제한된다. 
    3. workqueue: tasklet보다 더 general한 연기 매커니즘이다. 작업이 스레드로 실행되어 일반 프로세스처럼 스케쥴링 받아 실행된다. 따라서 sleep이 허용된다. 스케쥴링을 통해 실행되므로 처리 속도가 느릴 수 있다.

tasklet vs workqueue

tasklet과 workqueue 모두 작업을 나중으로 미룰 때 사용하는 매커니즘이다.

 

tasklet은 작업이 끝날 때까지 CPU를 반환하지 않기 때문에 빠르게 작업을 마칠 수 있다. 그러나 작업이 길어지면 그만큼 다른 작업들은 밀리기 때문에 전체적인 시스템 성능이 저하될 수 있다.

 

반면 workqueue는 스케쥴링을 통해 실행되므로 처리 속도는 느릴 수 있지만 시스템에 무리가 없고, 유연하고 다양한 API를 제공한다.

 

따라서 low latency(=짧고 빠르게 수행해야 하는) 작업은 tasklet이 적절하고, 빠른 처리 시간 대신에 좀 더 풍부한 API를 사용하고 싶다면 workqueue를 사용하는 것이 좋다.

 

참고: https://developer.ibm.com/tutorials/l-tasklets/

 

Interrupt Handler 개발하는 법

인터럽트 핸들러는 다음과 같은 프로토타입을 가져야 한다. IRQ 번호와 디바이스 ID를 인자로 받고, 인터럽트를 정상적으로 처리했다면 IRQ_HANDLED, 해당 함수에서 인터럽트를 처리하지 않는다면 IRQ_NONE을 반환하도록 한다.

 

static irqreturn_t my_gpio_irq(int irq, void* dev_id){
	...
	return IRQ_HANDLED; // handled interrupt successfully
}​

 

request_irq() 함수에 개발한 인터럽트 핸들러의 함수 포인터를 넘겨주어 인터럽트 핸들러를 등록한다.

static int init module_init(void){
{
	...
	ret = request_irq(gpio_to_irq(gpio_number), my_gpio_irq, IRQF_SHARED | IRQF_TRIGGER_FALLING, "my_handler", &data);
	if(ret < 0){
		pr_alert(("%s: request_irq failed with %d n", __func__, ret);
	}
	...
}

 

free_irq() 함수를 통해 IRQ 번호와 디바이스 ID에 해당하는 인터럽트 핸들러를 삭제한다.

static void __exit rpi_key_exit(void){
	...
	free_irq(gpio_to_irq(gpio_number), &data);
	...
}

'Embedded System' 카테고리의 다른 글

Bare Metal  (0) 2020.06.16
GPIO  (0) 2020.06.04
Kernel Module과 Device Driver  (0) 2020.05.29
Android Boot Sequence  (0) 2020.05.26
임베디드 시스템의 Boot Sequence  (0) 2020.05.26
Comments