Linux Kernel(4.14) Hacks

rousalome.egloos.com

포토로그 Kernel Crash




[라즈베리파이][리눅스커널] IRQ Thread는 언제 생성할까?[1] #CS [라즈베리파이]인터럽트후반부

IRQ Thread를 생성하기 위해서는 request_threaded_irq 을 호출하면 IRQ Thread가 생성된다고 설명해 드렸습니다. 사실 request_threaded_irq을 호출할 때 IRQ Thread가 생성되는 것은 아니고 다음 흐름에서  __kthread_create_on_node 함수가 실행할 때 생성됩니다.









우리는 커널 쓰레드를 생성할 때 kthread_create 함수를 호출한다고 배웠죠. IRQ Thread도 이 kthread_create 함수를 호출해서 생성합니다. 이로 IRQ Thread도 커널 쓰레드의 한 종류라고 유추할 수 있겠네요.

request_threaded_irq 부터 __kthread_create_on_node 함수까지 IRQ Thread를 어떻게 생성하는지 코드를 함께 살펴볼까요? 이를 위해 우선 인터럽트 핸들러를 설정하는 코드를 확인할 필요가 있습니다. 어떤 인터럽트 핸들러를 설정하는 코드를 봐야 할까요? 당연히 IRQ Thread를 생성하는 인터럽트 핸들러 코드를 봐야겠죠.

이전에 확인했다 싶히 라즈베리안에서는 "irq/92-mmc1" IRQ Thread를 확인할 수 있습니다. 
root@raspberrypi:/home/pi/dev_raspberrian# ps –ely | grep irq
S   UID   PID  PPID  C PRI  NI   RSS    SZ WCHAN  TTY          TIME CMD
S     0    65     2  0   9   -     0     0 irq_th ?        00:00:00 irq/92-mmc1

92번 인터럽트 핸들러와 해당 IRQ Thread를 설정하는 코드를 함께 분석하겠습니다. 
[drivers/mmc/host/bcm2835-mmc.c]
1 static int bcm2835_mmc_add_host(struct bcm2835_host *host)
2 {
3 struct mmc_host *mmc = host->mmc;
4 struct device *dev = mmc->parent;
//...
5 bcm2835_mmc_init(host, 0);
6 ret = devm_request_threaded_irq(dev, host->irq, bcm2835_mmc_irq,
7 bcm2835_mmc_thread_irq, IRQF_SHARED,
8 mmc_hostname(mmc), host); 

위 코드를 보면 request_threaded_irq 함수 대신 낯설게 보이는 devm_request_threaded_irq 함수로 IRQ Thread를 설정합니다. 다른 설정을 하는 함수 같아 보이기도 하는데요. 다음 코드와 같이 devm_request_threaded_irq 함수에서 request_threaded_irq 함수를 호출합니다.
1 int devm_request_threaded_irq(struct device *dev, unsigned int irq,
2       irq_handler_t handler, irq_handler_t thread_fn,
3       unsigned long irqflags, const char *devname,
4       void *dev_id)
5 {
6 struct irq_devres *dr;
7 int rc;
8
//...
9
10 rc = request_threaded_irq(irq, handler, thread_fn, irqflags, devname,
11   dev_id);

devm_request_threaded_irq 함수는 인터럽트 설정 정보를 디바이스 드라이버에서 체계적으로 관리하는 코드 빼고는 request_threaded_irq 함수와 같은 역할을 수행합니다. IRQ Thread 관점으로 devm_request_threaded_irq 함수를 호출하면 request_threaded_irq 함수가 실행된다고 봐도 무방합니다.

아래 코드에서 devm_request_threaded_irq 에서 호출하는request_threaded_irq 함수는 어디서 많이 본 함수 같지 않나요? 맞습니다. 인터럽트 핸들러를 등록할 때 호출하는 request_irq 함수에서 request_threaded_irq 함수를 호출했죠.  
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);
}

이번에 request_threaded_irq 함수에 전달하는 인자가 약간 다른 것 같습니다.
6 ret = devm_request_threaded_irq(dev, host->irq, bcm2835_mmc_irq,
7 bcm2835_mmc_thread_irq, IRQF_SHARED,
8 mmc_hostname(mmc), host); 

6번째 줄을 보면 bcm2835_mmc_irq 함수를 인터럽트 핸들러로 등록합니다. 이 함수는 92번 “mmc1” 인터럽트가 발생하면 호출되는 함수죠. 

7번째 줄 코드를 보면 request_threaded_irq 함수 세 번째 인자로 bcm2835_mmc_thread_irq 함수를 전달합니다. 이 함수를 IRQ Thread 핸들러라고 합니다. IRQ Thread가 실행될 때 호출되는 핸들러 함수입니다.  

조금 더 이해를 돕기 위해 request_threaded_irq 함수의 선언부를 보면 세 번째 인자로 irq_handler_t thread_fn가 선언돼 있습니다. 두 번째 인자는 인터럽트 핸들러 함수입니다.
[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 이란 함수 포인터에 bcm2835_mmc_thread_irq 이란 IRQ Thread 핸들러 함수를 등록하는 것입니다.
이를 알기 쉬운 코드 형식으로 표현하면 아래와 같이 각각 인자를 다음과 같이 등록합니다.
인터럽트 번호    irq    host->irq
인터럽트 핸들러    handler    bcm2835_mmc_irq
IRQ Thread 핸들러 thread_fn  bcm2835_mmc_thread_irq

인터럽트가 발생했을 때 인터럽트 컨택스트에서 수행하는 인터럽트 핸들러는 bcm2835_mmc_irq 이고 “irq/92-mmc1” IRQ Thread에서 실행하는 핸들러 함수는 bcm2835_mmc_thread_irq 라고 볼 수 있습니다. 

이제 bcm2835_mmc_thread_irq 핸들러를 등록하고 “irq/92-mmc1”이란 IRQ Thread를 생성하는 흐름을 살펴보겠습니다. 우선 request_threaded_irq 함수부터 살펴보겠습니다.
1 int request_threaded_irq(unsigned int irq, irq_handler_t handler,
 irq_handler_t thread_fn, unsigned long irqflags,
 const char *devname, void *dev_id)
4 {
struct irqaction *action;
struct irq_desc *desc;
int retval;
//…
8 action = kzalloc(sizeof(struct irqaction), GFP_KERNEL);
//…
action->handler = handler;
10  action->thread_fn = thread_fn; // <<--[1]
11  action->flags = irqflags;
12  action->name = devname;
13  action->dev_id = dev_id;
14 
15  chip_bus_lock(desc);
16  retval = __setup_irq(irq, desc, action); // <--[2]

10번째 줄 [1] 코드를 보면 thread_fn 포인터를 action->thread_fn에 등록합니다. bcm2835_mmc_thread_irq 함수가 irq_handler_t thread_fn란 파라미터로 전달됩니다.
10  action->thread_fn = thread_fn; 
11  action->flags = irqflags;
12  action->name = devname;
13  action->dev_id = dev_id;

action란 포인터 타입 지역 변수는 struct irqaction 구조체로 선언됐습니다.
이 변수는 8번째 줄 코드와 같이 메모리 할당을 받은 다음에 9번째와 13번째 줄 코드와 같이 인터럽트 핸들러와 IRQ Thread 핸들러 정보를 저장하죠.

이 변수는 나중에 인터럽트 디스크립터의 action이란 멤버에 저장됩니다. 다음 struct irq_desc 구조체를 참고하세요.
struct irq_desc {
struct irq_common_data irq_common_data;
//...
struct irqaction *action; /* IRQ action list */

여기까지 설정한 인자들을 Trace32로 확인하면 다음과 같이 확인할 수 있습니다. 각 멤버들에 대한 설명은 주석문을 참고하세요.
(struct irq_desc *) (struct irq_desc*)0xB008B300
...
    (struct irqaction *) action = 0xBB4E6E40  
      (irq_handler_t) handler = 0x8061EC00 = bcm2835_mmc_irq, // 인터럽트 핸들러
      (void *) dev_id = 0xBB47B010  // 인터럽트 핸들러 핸들
      (void *) percpu_dev_id = 0x0 = ,
      (struct irqaction *) next = 0x0 = ,
      (irq_handler_t) thread_fn = 0x8061DCC4 = bcm2835_mmc_thread_irq, // IRQ Thread 핸들러
      (struct task_struct *) thread = 0xBB516CC0 // “irq/92-mmc1” IRQ Thread의 태스크 디스크립터
      (struct irqaction *) secondary = 0x0 = ,
      (unsigned int) irq = 92, // 인터럽트 번호

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

여기까지는 인터럽트 핸들러를 등록하는 동작과 똑같습니다. 그런데 __setup_irq 함수 코드를 조금 더 살펴보면 Thread IRQ로 설정할 때만 동작하는 코드를 볼 수 있습니다. __setup_irq 코드를 같이 분석해볼까요?
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 함수에 전달되는 파라미터는 아래와 같습니다.
irq 인터럽트 번호
desc 인터럽트 디스크립터
new 인터럽트 디스크립터의 action 멤버(struct irq_desc->action)
 
여섯 번째 줄 코드를 보면 두 가지 조건을 점검합니다. (struct irqaction *) 타입 구조체인 new->thead_fn 멤버에 함수 포인터가 등록됐거나 nested 변수가 0일 경우에 setup_irq_thread 함수를 호출합니다. 
6 if (new->thread_fn && !nested) {  
7 ret = setup_irq_thread(new, irq, false);

nested 변수는 현재 설정하는 IRQ Thread가 nested 타입인지 점검합니다. 이 기능을 쓸 경우 nested 변수가 1이 되거든요. nested 변수를 읽어 오는 다음 코드를 눈여겨보세요.
6 nested = irq_settings_is_nested_thread(desc);
여기서 new->thead_fn 로 bcm2835_mmc_thread_irq 함수가 IRQ_Thread 핸들러로 등록됐고 nested 변수가 0이면 7번째 줄 코드가 실행되게 되겠죠.

이번에는 IRQ Thread를 생성하는 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,  // <<-[1]
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 함수를 호출해서 커널 쓰레드를 생성할 뿐이죠. 

먼저 10번째 줄 [1] 코드를 볼까요? 코드를 보니irq_thread란 IRQ Thread를 제어하는 함수와 함께 "irq/%d-%s"란 이름으로 IRQ Thread를 생성합니다. 
9 t = kthread_create(irq_thread, new, "irq/%d-%s", irq,   
10    new->name);

여기서 irq는 인터럽트 번호, new->name은 인터럽트 이름이 되겠죠. 그런데 ftrace 로그를 봐도 92번 인터럽트는 이름이 mmc1이란 사실을 알 수 있습니다.
kworker/u8:2-1310  [000] d.h.  2208.183670: irq_handler_entry: irq=92 name=mmc1
kworker/u8:2-1310  [000] d.h.  2208.183673: bcm2835_mmc_irq <-__handle_irq_event_percpu
kworker/u8:2-1310  [000] d.h.  2208.183714: irq_handler_exit: irq=92 ret=handled

이 정보를 참고하면 “irq=92 name=mmc1” 인터럽트의 IRQ Thread 이름은 “irq/92-mmc1”라는 점을 유추할 수 있습니다. 

여기서 kthread_create 함수에 전달하는 두 번째 파라미터는 (struct irqaction *)new입니다. 이는 커널 쓰레드가 생성되면 쓰레드 핸들 함수로 전달됩니다. IRQ Thread인 경우 irq_thread 함수가 되겠죠. 이때 커널 쓰레드 핸들을 (struct irqaction *)new 변수로 등록한다는 점 기억하세요.
1 static int irq_thread(void *data)
2 {
3 struct callback_head on_exit_work;
4 struct irqaction *action = data;

결국 IRQ Thread가 수행할 때 irq_thread 함수가 실행하는데 이 함수 인자로 void 타입 data 포인터를 전달합니다. 이 포인터를 struct irqaction * 타입으로 캐스팅하는 것이지요.

이 과정을 정리하면 다음 다이어그램으로 설명할 수 있겠네요.

 







“irq/92-mmc1” IRQ Thread는 언제 실행될까요? 92번 인터럽트가 발생하면 해당 인터럽트 핸들러가 IRQ Thread를 실행할 지 결정합니다. 만약 아예 92번 인터럽트가 발생하지 않으면 IRQ Thread는 실행할 필요가 없는 것이지요.




핑백

덧글

댓글 입력 영역