앞에서 인터럽트에 대해 소개했으니 리눅스 커널에서 인터럽트를 처리하는 방식을 이해하기 위해 알아야 할 주요 개념을 소개합니다.

 인터럽트 핸들러
 인터럽트 벡터 
 인터럽트 디스크립터 
 인터럽트 컨텍스트

인터럽트 핸들러란?

 

인터럽트가 발생하면 이를 핸들링하기 위한 함수가 호출되는데 이를 인터럽트 핸들러라고 합니다. 예를 들어, 키보드를 타이핑해서 인터럽트가 발생하면 키보드 인터럽트를 처리하는 키보드 인터럽트 핸들러가 호출됩니다. 마찬가지로 휴대폰에서 화면을 손으로 만지면 터치 인터럽트가 발생하고 터치 인터럽트를 처리하는 터치 인터럽트 핸들러가 호출됩니다.

다음 그림을 보면서 각 디바이스별로 인터럽트 핸들러가 처리되는 과정을 알아보겠습니다.
 


그림 5.2 디바이스별로 실행되는 인터럽트 핸들러

그림 5.2에서 볼 수 있듯이 인터럽트 종류별로 인터럽트 핸들러가 있습니다. 인터럽트 핸들러는 함수 형태로 존재하며, 커널 내 인터럽트 함수에서 호출합니다. 이처럼 인터럽트가 발생해 지정한 인터럽트 핸들러가 동작하려면 어떻게 해야 할까요? request_irq() 함수를 적절한 인자와 함께 호출해서 미리 인터럽트 핸들러를 등록해야 합니다.

이해를 돕기 위해 컴퓨터에서 마우스를 움직였을 때 인터럽트를 처리하는 코드를 예로 들겠습니다.

https://github.com/raspberrypi/linux/blob/rpi-4.19.y/drivers/input/mouse/amimouse.c
01 static int amimouse_open(struct input_dev *dev)
02 {
03 unsigned short joy0dat;
...
04 error = request_irq(IRQ_AMIGA_VERTB, amimouse_interrupt, 0, "amimouse",
05     dev);

04 번째 줄을 보면 request_irq() 함수의 두 번째 인자로 인터럽트 핸들러 함수인 amimouse_interrupt()를 등록합니다.

이후 마우스 인터럽트가 발생하면 request_irq() 함수에서 지정한 amimouse_interrupt() 함수가 호출됩니다.

https://github.com/raspberrypi/linux/blob/rpi-4.19.y/drivers/input/mouse/amimouse.c
01 static irqreturn_t amimouse_interrupt(int irq, void *data)
02 {
03 struct input_dev *dev = data;
04 unsigned short joy0dat, potgor;
05 int nx, ny, dx, dy;
...
06 input_report_key(dev, BTN_LEFT,   ciaa.pra & 0x40);
07 input_report_key(dev, BTN_MIDDLE, potgor & 0x0100);
08 input_report_key(dev, BTN_RIGHT,  potgor & 0x0400);

인터럽트 핸들러에서는 마우스에서 입력한 데이터 정보를 참고해 유저 공간에 알리는 동작을 수행합니다.

코드는 복잡해 보이지만 다음 그림을 보면 인터럽트의 처리 과정을 쉽게 이해할 수 있습니다.
 


그림 5.3 마우스를 움직였을 때 마우스 인터럽트 핸들러를 호출하는 과정

마우스를 움직이면 마우스가 움직였다는 인터럽트가 발생해 인터럽트 벡터가 실행됩니다. 이후 커널 인터럽트 내부 함수에서 해당 인터럽트에 맞는 인터럽트 핸들러를 찾아 호출합니다. 많은 하드웨어 디바이스가 이 같은 방식으로 인터럽트를 통해 하드웨어의 변화를 알립니다.

인터럽트 컨텍스트는 언제 활성화될까?

 

인터럽트 컨텍스트는 현재 코드가 인터럽트를 처리 중이라는 뜻입니다. 인터럽트 컨텍스트에 대한 이해를 돕기 위해 먼저 소프트웨어 관점에서 인터럽트의 실행 흐름을 단계별로 보겠습니다.

1. 프로세스 실행 중
2. 인터럽트 벡터 실행
3. 커널 인터럽트 내부 함수 호출
4. 인터럽트 종류별로 인터럽트 핸들러 호출
  4.1 인터럽트 컨텍스트 시작
5. 인터럽트 핸들러의 서브루틴 실행 시작
6. 인터럽트 핸들러의 서브루틴 실행 마무리
  6.1 인터럽트 컨텍스트 마무리

복잡한 단계로 인터럽트가 처리되는 것 같아도 처리 과정을 요약하면 다음과 같습니다.

 인터럽트가 발생하면 실행 중인 코드를 멈추고 인터럽트 벡터로 이동해 인터럽트에 대한 처리를 수행합니다.
 인터럽트 종류별로 지정한 인터럽트 핸들러가 실행됩니다.

앞의 목록에서 4.1~6.1 사이에 호출된 함수는 인터럽트 컨텍스트에서 실행됐다고 할 수 있습니다. 여기서 한 가지 의문이 생깁니다. 현재 실행 중인 코드가 인터럽트 컨텍스트인지 어떻게 알 수 있을까요?

in_interrupt() 함수를 호출하면 현재 인터럽트 컨텍스트인지 알려줍니다. 이 함수가 true를 반환하면 현재 실행 중인 코드가 4.1~6.1 구간에 있다는 뜻입니다. 

인터럽트 디스크립터란?


인터럽트 종류별로 다음과 같은 인터럽트의 세부 속성을 관리하는 자료구조를 인터럽트 디스크립터라고 합니다.

 인터럽트 핸들러
 인터럽트 핸들러 매개변수 
 논리적인 인터럽트 번호 
 인터럽트 실행 횟수 

프로세스의 세부 속성을 표현하는 자료구조가 태스크 디스크립터이듯이 인터럽트에 대한 속성 정보를 저장하는 자료구조가 인터럽트 디스크립터인 것입니다. 커널 인터럽트의 세부 함수에서는 인터럽트 디스크립터에 접근해 인터럽트 종류별로 세부적인 처리를 수행합니다. 그림 5.4는 인터럽트가 발생했을 때 인터럽트 핸들러를 호출하는 흐름입니다.
 


그림 5.4 인터럽트 디스크립터로 인터럽트 핸들러를 호출하는 과정

커널 내부의 인터럽트 함수에서 인터럽트 종류별로 지정된 인터럽트 핸들러를 호출하려면 먼저 인터럽트 디스크립터에 접근해야 합니다. 인터럽트 디스크립터는 인터럽트 핸들러의 주소 정보를 갖고 있는데, 커널에서는 이를 읽어서 인터럽트 핸들러를 호출합니다.

인터럽트 디스크립터는 irq_desc 구조체이며 선언부는 다음과 같습니다.

https://github.com/raspberrypi/linux/blob/rpi-4.19.y/include/linux/irqdesc.h
struct irq_desc {
struct irq_common_data irq_common_data;
struct irq_data irq_data;
unsigned int __percpu *kstat_irqs;
irq_flow_handler_t handle_irq;

참고로 이번 절에서 소개한 인터럽트의 주요 개념은 5.2절부터 상세히 살펴볼 예정입니다.

+ Recent posts