Linux Kernel(4.19) Hacks

rousalome.egloos.com

포토로그 Kernel Crash


통계 위젯 (화이트)

11206
629
98793


[리눅스커널][인터럽트후반부] IRQ 스레드는 언제 생성할까? 6장. 인터럽트 후반부 처리

6.3 IRQ 스레드는 어떻게 생성할까?

IRQ 스레드를 생성하려면 적절한 인자와 함께 request_threaded_irq() 함수를 호출해야 합니다.

request_threaded_irq() 함수의 선언부 코드를 먼저 보겠습니다. 
[https://elixir.bootlin.com/linux/v4.19.30/source/include/linux/interrupt.h]
extern int __must_check
request_threaded_irq(unsigned int irq, irq_handler_t handler,
     irq_handler_t thread_fn,
     unsigned long flags, const char *name, void *dev);

세 번째 파라미터인 thread_fn에 IRQ 스레드가 실행되면 호출할 스레드 핸들러만 지정해주면 됩니다. 그러면 커널에서 인터럽트 이름과 번호 정보로 IRQ 스레드를 생성해 줍니다.

여기서 Top Half는 인터럽트 핸들러인 handler 그리고 Bottom Half는 IRQ Thread가 수행되면 수행하는 핸들러 함수인 thread_fn 라고 볼 수 있습니다.

이제부터 라즈베리파이에서 IRQ 스레드가 어떻게 실행하는지 더 자세히 알아 보겠습니다.

6.3.1 IRQ 스레드는 언제 생성할까?

IRQ 스레드를 생성하기 위해서는 request_threaded_irq() 함수를 호출하면 됩니다. IRQ 전체 실행 흐름도와 함께 IRQ 스레드 생성 과정을 소개합니다.

IRQ 스레드 생성 전체 흐름도 파악하기 
IRQ 스레드를 생성하는 흐름도는 다음과 같습니다.
 
[그림 6.3] IRQ 스레드 생성 흐름도

request_threaded_irq() 함수를 호출하면 다음 동작을 수행합니다.
- 전달한 IRQ 스레드 정보를 인터럽트 컨택스트에 설정
- kthread_create() 함수를 호출해서 IRQ 스레드 생성

kthread_create() 함수는 kthread_create_on_node() 함수로 치환됩니다. 위 그림에서 이해를 돕기 위해 kthread_create() 함수를 호출하면 kthread_create_on_node() 함수를 호출하는 것처럼 표시했습니다.

우리는 커널 스레드를 생성할 때 kthread_create() 함수를 호출한다고 배웠습니다. IRQ 스레드도 kthread_create() 함수를 호출해서 생성합니다. IRQ 스레드도 커널 스레드의 한 종류입니다. 

request_threaded_irq() 함수부터 __kthread_create_on_node() 함수까지 IRQ 스레드 어떤 방식으로 생성하는지 코드를 분석하겠습니다.
- IRQ 스레드 생성: request_threaded_irq() 함수 분석하기
- IRQ 스레드를 생성하기 위해서 request_threaded_irq() 함수를 호출해야 합니다.

함수 선언부와 인자를 소개합니다.
[https://elixir.bootlin.com/linux/v4.19.30/source/include/linux/interrupt.h]
extern int __must_check
request_threaded_irq(unsigned int irq, irq_handler_t handler,
     irq_handler_t thread_fn,
     unsigned long flags, const char *name, void *dev);

request_threaded_irq() 함수에 전달되는 인자들은 다음과 같습니다.
 
unsigned int irq;
인터럽트 번호

irq_handler_t  handler;
인터럽트 핸들러 주소

irq_handler_t  thread_fn;
IRQ 스레드 핸들 함수 주소

unsigned long flags;
인터럽터 핸들링 플래그

const char  name;
인터럽트 이름

request_threaded_irq() 함수 동작은 2단계로 나눌 수 있습니다.

1 단계: 인터럽트 디스크립터 설정
requested_threaded_irq() 함수에 전달된 인자를 인터럽트 디스크립터 필드에
저장합니다.

2 단계: IRQ 스레드 생성
irq_handler_t thread_fn 인자에 IRQ 스레드 핸들 주소를 저장하면 IRQ 스레드를 생성합니다.

 IRQ 스레드 생성 단계에 대해 소개했으니 이제 코드 분석으로 넘어 가겠습니다.
request_threaded_irq() 함수 코드를 소개합니다.
[https://elixir.bootlin.com/linux/v4.19.30/source/kernel/irq/manage.c]
1 int request_threaded_irq(unsigned int irq, irq_handler_t handler,
2 irq_handler_t thread_fn, unsigned long irqflags,
3 const char *devname, void *dev_id)
4 {
5 struct irqaction *action;
6 struct irq_desc *desc;
7 int retval;
...
8 action = kzalloc(sizeof(struct irqaction), GFP_KERNEL);
9 if (!action)
10 return -ENOMEM;
...
11 action->handler = handler;
12 action->thread_fn = thread_fn;  
13 action->flags = irqflags;
14 action->name = devname;
15 action->dev_id = dev_id;
16 
17 chip_bus_lock(desc);
18 retval = __setup_irq(irq, desc, action);  

먼저 8~10 번째 줄 코드를 봅시다.
8 action = kzalloc(sizeof(struct irqaction), GFP_KERNEL);
9 if (!action)
10 return -ENOMEM;

struct irqaction 타입인 action 지역 변수에 struct irqaction 구조체 크기만큼 동적 메모리를 할당합니다. 

action란 포인터 타입 지역 변수의 타입은 struct irqaction 구조체입니다. 이 변수는 8번째 줄 코드와 같이 메모리 할당을 받은 다음에 11~12번째 줄 코드와 같이 인터럽트 핸들러와 IRQ 스레드 핸들러 정보를 저장합니다. 이 변수는 나중에 인터럽트 디스크립터의 action 필드에 저장됩니다. 

만약 메모리를 할당 못하면 10 번째 줄 코드을 실행해 –ENOMEM 매크로를 반환하며 함수 실행을 종료합니다.

다음에 볼 11~15 번째 줄 코드는 인자를 action 필드에 저장하는 동작입니다.
11 action->handler = handler;
12 action->thread_fn = thread_fn;  
13 action->flags = irqflags;
14 action->name = devname;
15 action->dev_id = dev_id;

12 번째 줄 코드를 보면 IRQ 스레드 핸들 함수 주소를 저장하고 있는 thread_fn 포인터를 action->thread_fn에 저장합니다.

struct irqaction 타입 action 변수는 해당 인터럽트 디스크립터에 저장됩니다.
Trace32로 확인한 인터럽트 디스크립터 자료 구조는 다음과 같습니다.
1 (struct irq_desc *) (struct irq_desc*)0xB008B300
...
2    (struct irqaction *) action = 0xBB4E6E40  
3      (irq_handler_t) handler = 0x8061EC00 = bcm2835_mmc_irq, /* 인터럽트 핸들러 */
4      (void *) dev_id = 0xBB47B010  /* 인터럽트 핸들러 핸들
5      (void *) percpu_dev_id = 0x0 = ,
6      (struct irqaction *) next = 0x0 = ,
7      (irq_handler_t) thread_fn = 0x8061DCC4 = bcm2835_mmc_thread_irq, /* IRQ Thread 핸들러 */
8      (struct task_struct *) thread = 0xBB516CC0 /* “irq/92-mmc1” IRQ 스레드의 태스크 디스크립터 */
9      (struct irqaction *) secondary = 0x0 = ,
10      (unsigned int) irq = 92, /* 인터럽트 번호 */

우리는 5장에서 인터럽트 디스크립터는 인터럽트 속성을 저장하는 자료구조라고 배웠습니다. 그런데 IRQ 스레드도 인터럽트 디스크립터에서 같이 관리합니다.

위 인터럽트 디스크립터는 라즈베리파이 92번 인터럽트를 관리하는 IRQ 스레드를 설정 정보를 포함합니다. 각각 필드에 대한 설명은 주석문을 참고하시기 바랍니다.

다음 18번째 줄 코드를 보면 __setup_irq() 함수를 호출합니다.
16 retval = __setup_irq(irq, desc, action);

여기까지 인터럽트 핸들러를 등록하는 실행 흐름과 똑같습니다. 그런데 __setup_irq() 함수 코드를 조금 더 살펴보면 IRQ 스레드로 설정할 때만 달리 동작하는 코드를 볼 수 있습니다. 

IRQ 스레드 생성: __setup_irq() 함수 분석

이어서 __setup_irq() 코드를 같이 분석해 봅시다.
[https://elixir.bootlin.com/linux/v4.19.30/source/kernel/irq/manage.c]
1 static int
2 __setup_irq(unsigned int irq, struct irq_desc *desc, struct irqaction *new)
3 {
4 struct irqaction *old, **old_ptr;
5 unsigned long flags, thread_mask = 0;
...
6 nested = irq_settings_is_nested_thread(desc);
...
7 if (new->thread_fn && !nested) {  
8 ret = setup_irq_thread(new, irq, false);
9 if (ret)
10 goto out_mput;

우선 __setup_irq() 함수에 전달되는 파라미터는 아래와 같습니다.
 - unsigned int irq: 인터럽트 번호
 - struct irq_desc *desc: 인터럽트 디스크립터
 - struct irqaction *new: 인터럽트 디스크립터의 action 멤버(struct irq_desc->action)

__setup_irq() 함수는 IRQ 스레드 핸들 함수가 등록됐는지 점검한 후 등록이 됐으면 setup_irq_thread() 함수를 호출해 IRQ 스래드를 생성합니다.

먼저 6 번째 줄 코드를 봅시다. 
6 if (new->thread_fn && !nested) {  
7 ret = setup_irq_thread(new, irq, false);

이 코드는 두 가지 조건을 점검합니다.  struct irqaction 타입인 new->thead_fn 필드에 함수 포인터가 등록됐거나 nested 변수가 0일 때 setup_irq_thread() 함수를 호출합니다. 

nested 변수는 현재 설정하는 IRQ 스레드가 nested 타입인지 점검합니다. 이 기능을 쓸 경우 nested 변수가 1이 됩니다. nested 변수를 읽어 오는 다음 코드를 눈여겨봅시다.
6 nested = irq_settings_is_nested_thread(desc);

여기서 new->thead_fn 로 IRQ 스레드 핸들러 함수가 등록됐고 nested 변수가 0이면 8번째 줄 코드를 실행됩니다.

IRQ 스레드 생성: setup_irq_thread() 함수 분석

이어서 IRQ 스레드를 생성 역할을 수행하는 setup_irq_thread() 함수를 분석하겠습니다.
1 static int
2 setup_irq_thread(struct irqaction *new, unsigned int irq, bool secondary)
3 {
4 struct task_struct *t;
5 struct sched_param param = {
6 .sched_priority = MAX_USER_RT_PRIO/2,
7 };
8
9 if (!secondary) {
10 t = kthread_create(irq_thread, new, "irq/%d-%s", irq,   
11    new->name);
12 } else {
13 t = kthread_create(irq_thread, new, "irq/%d-s-%s", irq,
14    new->name);
15 param.sched_priority -= 1;
16 }

위 코드를 보면 특별한 동작을 수행하지 않습니다. kthread_create() 함수를 호출해서 커널 스레드를 생성합니다. 이 코드로 IRQ 스레드도 커널 스레드의 한 종류라고 말할 수 있겠습니다.

커널 스레드는 커널 공간에서만 실행하는 프로세스입니다. 
커널 스레드는 유저 공간과 시스템 콜로 통신하지 않습니다. 커널 공간에서만 대부분 배경 작업으로 커널 리소스(메모리, 프로세스 관리)를 관리합니다.

커널에는 다양한 커널 스레드가 있습니다. 커널 서브 시스템이나 드라이버 목적에 맞게 커널 스레드를 생성할 수 있습니다. 워크큐를 실행하는 스레드를 워커 스레드, 메모리가 부족할 때 kswapd 스레드 그리고 Soft IRQ 후반부 처리용 ksoftirqd 스레드를 예를 들 수 있습니다.

IRQ 스레드로 여러 커널 스레드 중 하나입니다.

먼저 10번째 줄 코드를 보겠습니다. irq_thread() 이란 IRQ 스레드를 제어하는 함수와 "irq/%d-%s"란 이름을 지정해 IRQ 스레드를 생성합니다. 
9 t = kthread_create(irq_thread, new, "irq/%d-%s", irq,   
10    new->name);

kthread_create() 함수를 호출할 때 다음과 같은 인자를 지정합니다.
 - irq_thread: IRQ 스레드 핸들 함수
 - new: IRQ 스레드 핸들 매개 인자(struct irqaction)
 - "irq/%d-%s": IRQ 스레드 이름 타입
 - irq: 인터럽트 번호
 - new->name: 인터럽트 이름 

kthread_create() 함수에 전달하는 두 번째 파라미터인 new는 스레드 핸들 함수로 전달되는 매개 인자입니다. 이 매개 인자는 struct irqaction 구조체 타입입니다.

IRQ 스레드 핸들러 함수에 전달하는 매개인자 확인하기

IRQ 스레드의 스레드 핸들인 irq_thread() 함수 코드를 보면서 매개 인자 처리 방식에 대해 조금 더 살펴보겠습니다.
[https://elixir.bootlin.com/linux/v4.19.30/source/kernel/irq/manage.c]
1 static int irq_thread(void *data)
2 {
3 struct callback_head on_exit_work;
4 struct irqaction *action = data;

우리는 커널 스레드를 생성하면 커널 스레드 핸들 함수에서 무한 루프를 돌면서 스레드 목적에 맞는 동작을 수행합니다. 이를 스레드 핸들 함수라고 합니다. IRQ 스레드는 irq_thread() 함수가 이 역할을 수행합니다. 

IRQ 스레드가 실행할 때 irq_thread() 함수가 실행하는데 함수 인자로 void 타입 data 포인터를 전달합니다. 위 irq_thread() 함수 4 번째 줄 코드를 눈여겨보면 이 포인터를 struct irqaction * 타입으로 캐스팅합니다.

다음 그림을 보면 이 과정을 쉽게 이해할 수 있습니다. 
 
[그림 6.4] IRQ 스레드 생성과 스레드 매개인자 전달 흐름도

위 그림에서 볼드체로 된 왼쪽 박스를 봅시다. 
struct irqaction 타입인 new 변수를 매개 인자로 등록합니다. 

오른쪽은 IRQ 스레드 핸들러로 매개인자가 전달되는 부분입니다. IRQ 스레드 핸들러인 irq_thread() 함수로 전달된 void 타입 data 변수는 struct irqaction 타입으로 캐스팅됩니다.

이 방식으로 IRQ 스레드에서 인터럽트 속성 정보가 담긴 struct irqaction 구조체 매개인자로 세부 제어를 할 수 있습니다.


     "혹시 글을 읽고 궁금한 점이 있으면 댓글로 질문 남겨주세요. 아는 한 성실하게 답변 올려 드리겠습니다!"


# Reference 인터럽트 후반부 처리








6.9 Soft IRQ 서비스는 누가 언제 처리하나?




6.13 Soft IRQ 디버깅
6.13.1 ftrace Soft IRQ 이벤트 분석 방법
6.13.2 /proc/softirqs로 Soft IRQ 서비스 실행 횟수 확인
 




    핑백

    덧글

    댓글 입력 영역