Linux Kernel(4.19) Hacks

rousalome.egloos.com

포토로그 Kernel Crash


통계 위젯 (화이트)

114187
803
94443


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

5.3.3. 인터럽트 핸들러 호출 흐름 분석하기

이전 소절에서 인터럽트가 발생하면 인터럽트 벡터가 실행하는 흐름을 살펴봤습니다. 이어서 커널에서 인터럽트 핸들러까지 어떤 흐름으로 코드가 실행되는지 알아 보겠습니다. 

커널 공간에서 프로세스가 실행 도중에 인터럽트가 발생하면 인터럽트 벡터인 __irq_svc 레이블로 시작해서 handle_irq_event_percpu() 함수까지 실행합니다. 
 
[그림 5.12] 인터럽트 벡터에서 인터럽트 핸들러 호출까지 함수 흐름

위 함수 흐름에서 다음과 같이 인터럽트에 대한 여러 가지 예외 처리를 수행합니다.
1. 커널이 인터럽트를 처리하고 있는 도중 인터럽트가 발생했을 때 리턴 처리
2. 인터럽트 정보를 인터럽트 디스크립터에 저장
3. 가끔 전달되는 쓰레기 인터럽트값에 대한 예외 처리

인터럽트가 발생하는 그 자체가 하드웨어와 밀접한 동작이라 커널에서 세밀한 인터럽트에 대한 예외 처리를 수행합니다.

이전 장에서 커널이 인터럽트 번호에 해당하는 인터럽트 디스크립터를 읽어와 인터럽트 핸들러를 처리하는 과정을 살펴봤습니다. 위 함수 흐름에서 보이는 generic_handle_irq() 함수를 살펴보면 해당 코드를 볼 수 있습니다. 
[https://elixir.bootlin.com/linux/v4.19.30/source/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() 함수를 호출합니다. 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 irqaction *action; 
generic_handle_irq() 함수에서 읽어 온 인터럽트 디스크립터 구조체 struct irq_desc action필드 주소를 저장하고 있습니다.

unsigned int *flags;
인터럽트 플래그 정보를 담고 있습니다.

__handle_irq_event_percpu() 함수에 전달하는 인자와 반환값을 확인했으니 구현부 코드를 분석합시다.
[https://elixir.bootlin.com/linux/v4.19.30/source/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://elixir.bootlin.com/linux/v4.19.30/source/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

이번 소절에서는 인터럽트 핸들러 함수가 호출하는 흐름을 살펴봤습니다. 이 흐름을 파악하는 것은 매우 중요합니다. 인터럽트 핸들러를 등록했는데 핸들러가 호출이 안 될 수 있기 때문입니다.

인터럽트 핸들러를 등록했는데 인터럽트 핸들러가 호출되지 않을 때 그 원인을 파악하는 것은 그리 어렵지 않습니다. 인터럽트 핸들러를 제대로 등록했는지 살펴보면 대부분 문제점을 파악할 수 있기 때문입니다. 하지만 간헐적으로 인터럽트 핸들러가 호출되지 않는 경우는 조금 다릅니다. 이런 문제를 해결하기 위해서는 위에 소개한 코드 흐름대로 제대로 코드가 실행되는지 확인해야 합니다.

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


# Reference (인터럽트 처리)

    핑백

    덧글

    댓글 입력 영역