Linux Kernel(4.19) Hacks

rousalome.egloos.com

포토로그 Kernel Crash


통계 위젯 (화이트)

230224
1178
109352


[리눅스커널][인터럽트] 인터럽트 디스크립터: 인터럽트 발생 횟수 확인하기 5. Interrupt Handling

5.5.2 인터럽트 발생 횟수는 어떻게 저장할까?

대부분 디바이스 드라이버는 인터럽트 핸들러로 하드웨어 디바이스와 통신합니다. 예를 들면 터치 디바이스에서 사용자가 터치를 입력하면 터치 디바이스에서 올려주는 인터럽트로 터치 입력이 발생했다고 알려줍니다. 

디바이스 드라이버에서 인터럽트 핸들러를 등록한 후 해당 디바이스가 제대로 인터럽트를 발생시키는지 어떻게 알 수 있을까요? 이럴 때는 인터럽트 발생 횟수를 점검하면 됩니다.

이번에는 인터럽트 발생 횟수를 인터럽트 디스크립터가 어떤 방식으로 관리하는지 살펴보겠습니다. 

인터럽트 발생 횟수는 인터럽트 디스크립터 내 struct irq_desc 구조체 kstat_irqs 필드에 저장합니다. 이 변수는 per-cpu 타입입니다 
[https://elixir.bootlin.com/linux/v4.19.30/source/include/linux/irqdesc.h]
struct irq_desc {
struct irq_data irq_data;
unsigned int __percpu *kstat_irqs;

인터럽트가 발생하면 아래 흐름으로 handle_fastoei_irq() 함수가 호출됩니다.
 
[그림 5.14] 인터럽트 발생 횟수가 업데이트되는 코드 흐름

이후 해당 인터럽트 번호로 인터럽트 디스크립터를 가져와서, kstat_incr_irq_this_cpu() 함수를 호출합니다. 
[https://elixir.bootlin.com/linux/v4.19.30/source/kernel/irq/chip.c]
01 void handle_fasteoi_irq(unsigned int irq, struct irq_desc *desc)
02 {
03 struct irq_chip *chip = desc->irq_data.chip;
04
05 raw_spin_lock(&desc->lock);
...
06 kstat_incr_irqs_this_cpu(irq, desc);  
...
07 handle_irq_event(desc); 

아래 코드를 보면 __this_cpu_inc함수를 써서 struct irq_desc->kstat_irqs 값을 1만큼 증가시킵니다. 인터럽트 발생 횟수를 저장합니다.
[https://elixir.bootlin.com/linux/v4.19.30/source/kernel/irq/internals.h]
static inline void kstat_incr_irqs_this_cpu(struct irq_desc *desc)
{
__this_cpu_inc(*desc->kstat_irqs);
__this_cpu_inc(kstat.irqs_sum);
}

void kstat_incr_irq_this_cpu(unsigned int irq)
{
kstat_incr_irqs_this_cpu(irq, irq_to_desc(irq));
}

인터럽트 실행 횟수를 점검하는 실습 패치 코드 소개

위에서 분석한 코드가 실제 어떻게 동작하는지 실습으로 알아봅시다. 라즈비안 커널 코드에서 다음 패치를 적용하면 92번 mmc1 인터럽트 발생 횟수를 확인할 수 있습니다. 
diff --git a/drivers/mmc/host/bcm2835-mmc.c b/drivers/mmc/host/bcm2835-mmc.c
index b7f5fd96e..929adedaf 100644
--- a/drivers/mmc/host/bcm2835-mmc.c
+++ b/drivers/mmc/host/bcm2835-mmc.c
@@ -1051,6 +1051,46 @@ static irqreturn_t bcm2835_mmc_irq(int irq, void *dev_id)
  return result;
 }
 
+#include<linux/interrupt.h>
+#include<linux/irq.h>
+#include<linux/kernel_stat.h>
+
+static void interrupt_debug_irq_times(int irq_num)
+{
+ struct irqaction *action;
+ struct irq_desc *desc;
+
+ int cpu_num = 0;
+ unsigned long all_count = 0;
+
+ desc = irq_to_desc(irq_num);
+
+ if (!desc ) {
+ pr_err("invalid desc at %s line: %d\n", __func__, __LINE__);
+ return;
+ }
+
+ action = desc->action;
+
+ if (!action ) {
+ pr_err("invalid action at %s line:%d \n", __func__, __LINE__);
+ return;
+ }
+
+ printk("[+] irq_desc count debug start \n");
+        printk("process: %s \n", current->comm);
+    
+ printk("irq num: %d name: %8s \n",  action->irq , action->name);
+
+ for_each_online_cpu(cpu_num) {
+ all_count |= kstat_irqs_cpu(action->irq, cpu_num);
+ printk("%10u ",kstat_irqs_cpu(action->irq, cpu_num));
+ }
+
+ printk("irq trigger times: %ld ", all_count);
+    printk("[-] irq_desc count debug end \n");
+}
+
 static irqreturn_t bcm2835_mmc_thread_irq(int irq, void *dev_id)
 {
  struct bcm2835_host *host = dev_id;
@@ -1058,6 +1098,9 @@ static irqreturn_t bcm2835_mmc_thread_irq(int irq, void *dev_id)
  u32 isr;
 
  spin_lock_irqsave(&host->lock, flags);
+
+ interrupt_debug_irq_times(irq);
+
  isr = host->thread_isr;
  host->thread_isr = 0;
  spin_unlock_irqrestore(&host->lock, flags);

interrupt_debug_irq_times() 함수 내 대부분 코드는 이전 절에서 상세히 설명해 드렸기 때문에 이번에 새롭게 추가한 다음 코드 위주로 살펴보겠습니다. 
+ for_each_online_cpu(cpu_num) {
+ all_count |= kstat_irqs_cpu(action->irq, cpu_num);
+ printk("%10u ",kstat_irqs_cpu(action->irq, cpu_num));
+ }

여기서 조금 더 살펴볼 코드는 for_each_online_cpu() 함수입니다. 이 함수는 현재 구동 중인 cpu 개수만큼 for문을 반복합니다. 라즈베리파이는 CPU가 4개이니 4번 루프를 수행합니다. kstat_irqs_cpu() 함수는 인터럽트 번호와 해당 cpu번호를 입력받아 각 cpu별로 인터럽트 처리 횟수를 출력합니다.

위 코드를 추가한 다음에 커널 빌드를 후 라즈베리파이에 커널 이미지를 설치해서 커널 로그를 확인하면 아래와 같이 인터럽트 발생횟수를 확인할 수 있습니다. 
[6.909319] [+] irq_desc count debug start 
[6.909327] process: irq/92-mmc1 
[6.909335] irq num: 92 name:     mmc1 
[6.909342]       4823 
[6.909348]          0 
[6.909355]          0 
[6.909362]          0 
[6.909369] irq trigger times: 4823

위 로그를 해석하면 각 CPU별로 4823, 0, 0, 0 개수만큼 발생한 인터럽트 개수고, irq trigger times: 4823은 이 값들을 합친 개수입니다.

위 패치에 대해서 조금 더 생각해 볼 부분이 있습니다. 92번 인터럽트 발생 횟수를 출력하는 코드를 인터럽트 핸들러인 bcm2835_mmc_irq() 함수가 아니라 bcm2835_mmc_thread_irq() 함수에서 호출하는 이유는 뭘까요? 커널 로그를 출력하는 printk() 함수는 시스템 관점으로 많은 비용이 드는 동작이므로 인터럽트 핸들러인 bcm2835_mmc_irq() 함수에서 쓰지 않는 것입니다. 

인터럽트 핸들러는 빨리 수행해야 한다고 배웠습니다. 그래서 인터럽트가 발생하고 나서 바로 빨리 처리할 코드는 bcm2835_mmc_irq() 핸들러에서 수행하고 조금 이후 커널 쓰래드 레벨에서 처리할 코드는 IRQ 스레드에서 처리할 수 있습니다. (IRQ 스레드는 다음 장에서 상세히 다루니 참고하세요.) 그런데 bcm2835_mmc_thread_irq() 함수는 바로 IRQ 스레드 핸들러이므로 이 함수에서 92번 인터럽트 정보를 출력하는 커널 로그를 출력하는 것입니다. 

이렇게 인터럽트 핸들러를 두 단계로 나눠서 처리하는 코드는 리눅스 드라이버에서 아주 많이 적용하므로 이 개념은 잘 알아두면 좋습니다. 임베디드 소프트웨어에서 용어로 Top-Half, Bottom Half를 씁니다. 인터럽트가 발생하면 바로 실행하는 코드는 Top-Half이고 Bottom-Half는 바로 처리 안 하고 나중에 프로세스 레벨로 처리하는 부분을 말합니다. 이 개념을 위 패치 코드 기준으로 해석하면 다음 공식으로 만들 수 있습니다.
- Top-Half : 인터럽트 핸들러 = bcm2835_mmc_irq
- Bottom-Half: IRQ Thread = bcm2835_mmc_thread_irq


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

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

# Reference (인터럽트 처리)


핑백

덧글

댓글 입력 영역