Linux Kernel(4.19) Hacks

rousalome.egloos.com

포토로그 Kernel Crash


통계 위젯 (화이트)

19113
1478
166889


[리눅스커널][인터럽트] 인터럽트 핸들러 호출 코드 알아보기 5. 인터럽트

이전 절에서 인터럽트가 발생하면 실행되는 인터럽트 벡터의 어셈블리 코드를 분석했습니다. 그런데 커널은 인터럽트 벡터부터 커널 내부 함수를 호출해 인터럽트 핸들러 함수를 처리합니다. 즉, 프로세스가 실행되는 도중 인터럽트가 발생하면 인터럽트 벡터인 __irq_svc 레이블로 브랜치하고 handle_irq_event_percpu() 함수까지 실행됩니다. 

이번 절에서는 인터럽트 벡터에서 인터럽트 핸들러까지 이어지는 실행 흐름을 알아보겠습니다. 먼저 그림 5.12를 보면서 실행 흐름을 확인해 봅시다.

 
[그림 5.12] 인터럽트 벡터에서 인터럽트 핸들러 호출까지 함수 흐름

위 함수 흐름에서 다음과 같이 인터럽트에 대한 여러 가지 예외 처리를 수행합니다.

1. 커널이 인터럽트를 처리하고 있는 도중 인터럽트가 발생했을 때 리턴 처리
2. 인터럽트 정보를 인터럽트 디스크립터에 저장
3. 가끔 전달되는 쓰레기 인터럽트 값에 대한 예외 처리

인터럽트는 속성 자체가 하드웨어와 밀접해서 커널 인터럽트 내부 함수에서 다양한 예외 처리를 수행합니다.

이전 장에서 커널이 인터럽트 번호에 해당하는 인터럽트 디스크립터를 읽어와 인터럽트 핸들러를 처리하는 과정을 살펴봤습니다. 이어서 위 함수 흐름에서 보이는 generic_handle_irq() 함수를 분석하면서 관련 동작을 확인해보겠습니다.

https://github.com/raspberrypi/linux/blob/rpi-4.19.y/kernel/irq/irqdesc.c
1 int generic_handle_irq(unsigned int irq)
2 {
3 struct irq_desc *desc = irq_to_desc(irq); 
4
5 if (!desc)
6 return -EINVAL;
7 generic_handle_irq_desc(desc);
8 return 0;
9 }

3번째 줄을 보겠습니다. 정수형 인터럽트 번호인 irq 인자와 함께 irq_to_desc() 함수를 호출해 인터럽트 디스크립터 주소를 desc 변수에 반환합니다. irq_to_desc() 함수는 인터럽트 번호로 인터럽트 디스크립터의 주소를 반환하는 역할을 합니다. 

참고로 인터럽트 디스크립터는 request_irq() 함수에서 등록한 인터럽트 핸들러, 인터럽트 이름, 인터럽트 플래그 값 정보를 저장하고 있습니다. irq_to_desc() 함수는 인터럽트 번호만 알면 인터럽트 디스크립터를 가져올 수 있는 유용한 함수이므로 잘 활용하면 좋습니다.

다음으로 인터럽트 핸들러를 호출하는 handle_irq_event_percpu() 함수를 소개합니다.

irqreturn_t __handle_irq_event_percpu(struct irq_desc *desc, unsigned int *flags);

먼저 이 함수에서 전달하는 인자와 반환값을 점검합시다.

struct irq_desc *desc;

인터럽트 디스크립터 구조체의 주소입니다. 다음 함수와 같이 정수형 인터럽트 번호로 인터럽트 디스크립터 주소를 읽을 수 있습니다.

https://github.com/raspberrypi/linux/blob/rpi-4.19.y/kernel/irq/irqdesc.c
1 int generic_handle_irq(unsigned int irq)
2 {
3 struct irq_desc *desc = irq_to_desc(irq); 

보다시피 3번째 줄은 정수형 인터럽트 번호로 irq_to_desc() 함수를 호출해 인터럽트 디스크립터를 받아오는 동작입니다. 

이어서 2번째 인자를 확인해봅시다.

unsigned int *flags;

인터럽트 플래그 정보를 담고 있습니다.

__handle_irq_event_percpu() 함수에 전달하는 인자와 반환값을 확인했으니 함수를 봅시다.

https://github.com/raspberrypi/linux/blob/rpi-4.19.y/kernel/irq/handle.c
1 irqreturn_t __handle_irq_event_percpu(struct irq_desc *desc, unsigned int *flags)
2 {
3 irqreturn_t retval = IRQ_NONE;
4 unsigned int irq = desc->irq_data.irq;
5 struct irqaction *action;
6
7 record_irq_time(desc);
8
9 for_each_action_of_desc(desc, action) {
10 irqreturn_t res;
11
12 trace_irq_handler_entry(irq, action);
13 res = action->handler(irq, action->dev_id); 
14 trace_irq_handler_exit(irq, action, res);

13번째 줄에서 action->handler 함수 포인터가 실행될 때 인터럽트 핸들러를 호출합니다. 여기서 action->handler 함수 포인터를 실행할 때 전달하는 인자는 irq와 action입니다. irq는 인터럽트 번호이고 action->dev_id는 인터럽트 핸들러 매개변수입니다. action->dev_id 변수는 request_irq() 함수로 인터럽트 핸들러를 등록할 때 다섯 번째 매개변수에 해당합니다. 

[https://github.com/raspberrypi/linux/blob/rpi-4.19.y/include/linux/interrupt.h] 
static inline int __must_check
request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags,
    const char *name, void *dev)
{
return request_threaded_irq(irq, handler, NULL, flags, name, dev);
}

action->dev_id 변수는 보통 디바이스 드라이버 전체를 콘트롤하는 핸들로 많이 사용됩니다.

다음으로 주의깊게 살펴볼 코드는 12 번째와 14번째 줄입니다.

12 trace_irq_handler_entry(irq, action);
13 res = action->handler(irq, action->dev_id); 
14 trace_irq_handler_exit(irq, action, res);

12 번째 줄에서는 ftrace의 irq_handler_entry를, 14번째 줄에서는 irq_hander_exit 이벤트를 활성화했을 때 다음과 같은 ftrace 로그를 출력합니다.

kworker/0:2-20114 [000] d.h.  4486.577344: irq_handler_entry: irq=23 name=3f00b880.mailbox
kworker/0:2-20114 [000] d.h.  4486.577346: irq_handler_exit: irq=23 ret=handled

이번 절에서는 인터럽트 핸들러 함수가 호출되는 흐름을 살펴봤습니다. 그럼 배운 내용을 정리해 봅시다.

인터럽트가 발생하면 인터럽트 벡터가 실행된다.
인터럽트 벡터에서 실행 중인 프로세스의 레지스터 세트를 프로세스 스택에 저장한다.
커널 내부 함수에서 인터럽트에 대한 예외 처리를 수행한다.
정수형 인터럽트 번호로 인터럽트 디스크립터를 읽어 인터럽트 핸들러를 호출한다.

인터럽트 핸들러를 등록했는데 인터럽트 핸들러가 호출되지 않을 때 그 원인을 파악하는 것은 그리 어렵지 않습니다. 인터럽트 핸들러를 등록할 때 플래그 정보를 확인하면 대부분 문제점을 파악할 수 있기 때문입니다. 하지만 가끔 인터럽트 핸들러가 호출되지 않는 문제는 원인을 찾기 어려운 경우가 많습니다. 이런 문제를 해결하기 위해서는 먼저 이번 절에서 소개한 코드 흐름대로 제대로 코드가 실행되는지 확인해야 합니다.

다음 절에서는 인터럽트 핸들러를 등록하는 과정과 디버깅용 실습 패치를 소개합니다.


"이 포스팅이 유익하다고 생각되시면 댓글로 응원해주시면 감사하겠습니다.  
"혹시 궁금한 점이 있으면 댓글로 질문 남겨주세요. 아는 한 성실히 답변 올려드리겠습니다!" 

Thanks,
Austin Kim(austindh.kim@gmail.com)

# Reference (인터럽트 처리)

인터럽트 소개  
   * 리눅스 커널에서의 인터럽트 처리 흐름    
인터럽트 컨텍스트  
인터럽트 핸들러는 언제 호출될까?  
인터럽트 핸들러는 어떻게 등록할까?  
인터럽트 디스크립터  
인터럽트 디버깅  

    핑백

    덧글

    댓글 입력 영역