Linux Kernel(4.14) Hacks

rousalome.egloos.com

포토로그 Kernel Crash




[라즈베리파이] Soft IRQ 서비스는 누가 언제 처리하나?[1] [라즈베리파이]인터럽트후반부

이제 Soft IRQ 기법의 하이라이트인 Soft IRQ 서비스를 처리하는 흐름을 살펴볼 차례입니다. Soft IRQ 서비스는 언제 처리할까요? 아래 Soft IRQ 전체 흐름도와 같이 인터럽트 핸들러를 처리하는 인터럽트 서비스 루틴이 끝나는 시점에 Soft IRQ 서비스 처리를 시작합니다. 그래서 Soft IRQ 서비스 처리를 시작하는 코드를 점검하려면 인터럽트 서비스 루틴이 끝나는 코드부터 확인해야 합니다.

이번 절에서는 Soft IRQ 전체 흐름도에서 볼드체로 된 부분을 점검할 예정이니 눈여겨보세요.


인터럽트 서비스 루틴이 끝나는 코드가 __handle_domain_irq 함수에서 실행하니 우선 이 함수부터 분석해야 합니다. 같이 볼 코드는 다음과 같습니다. 
1 int __handle_domain_irq(struct irq_domain *domain, unsigned int hwirq,
bool lookup, struct pt_regs *regs)
3 {
4 struct pt_regs *old_regs = set_irq_regs(regs);
5 unsigned int irq = hwirq;
6 int ret = 0;
7
8 irq_enter();
9
10 #ifdef CONFIG_IRQ_DOMAIN
11 if (lookup)
12 irq = irq_find_mapping(domain, hwirq);
13 #endif
14
15 /*
16  * Some hardware gives randomly wrong interrupts.  Rather
17  * than crashing, do something sensible.
18  */
19 if (unlikely(!irq || irq >= nr_irqs)) {
20 ack_bad_irq(irq);
21 ret = -EINVAL;
22 } else {
23 generic_handle_irq(irq);
24 }
25
26 irq_exit();  //<<---

다음 23번째 줄 코드에서 호출하는generic_handle_irq 함수 서브 루틴에서 인터럽트 핸들러를 호출합니다.
22 } else {
23 generic_handle_irq(irq);
24 }

예전에 봤던 ftrace 로그를 보면 아래 generic_handle_irq 함수가 보이죠? 이 함수가 호출된 후 62번 인터럽트 핸들러인 dwc_otg_common_irq 함수가 호출됐습니다.
kworker/0:0-27338 [000] 6028.897809: dwc_otg_common_irq <-__handle_irq_event_percpu
kworker/0:0-27338 [000] 6028.897847: <stack trace>
 => handle_irq_event
 => handle_level_irq
 => generic_handle_irq
 => bcm2836_chained_handle_irq
 => generic_handle_irq
 => __handle_domain_irq
 => bcm2836_arm_irqchip_handle_irq
 => __irq_svc

이제 인터럽트 핸들러 처리를 마치고 irq_exit() 함수를 호출합니다. 이 irq_exit() 함수가 Soft IRQ 서비스를 처리하는 시작점입니다. 이제 irq_exit() 함수를 분석하겠습니다.
1 void irq_exit(void)
2 {
3 account_irq_exit_time(current);
4 preempt_count_sub(HARDIRQ_OFFSET);
5 if (!in_interrupt() && local_softirq_pending())
6 invoke_softirq();
7
8 tick_irq_exit();
9 rcu_irq_exit();
10 trace_hardirq_exit(); /* must be last! */
11}

4번째 줄 코드를 보면 preempt_count_sub 함수를 호출합니다. struct thread_info->preeempt_count 변수에서 HARDIRQ_OFFSET를 뺍니다. 이제는 인터럽트 컨택스트가 아니라는 표시를 하는 겁니다.
4 preempt_count_sub(HARDIRQ_OFFSET);
5 if (!in_interrupt() && local_softirq_pending())
6 invoke_softirq();

그다음 5번째 줄 코드에서는 두 가지 조건을 점검합니다.

in_interrupt() 매크로를 호출해서 인터럽트 컨택스트가 아닌지 확인합니다. 4번째 줄 코드인 preempt_count_sub 함수가 처리된 직후 다른 인터럽트가 발생해서 다음 코드와 같이 preempt_count_add(HARDIRQ_OFFSET)를 실행해서 in_interrupt함수를 활성화할 수도 있기 때문입니다. 
#define __irq_enter()     \
  do {      \
   account_irq_enter_time(current); \
   preempt_count_add(HARDIRQ_OFFSET); \   
   trace_hardirq_enter();   \
  } while (0)

인터럽트는 언제 어디든 발생할 수 있으니 예외 처리 조건을 추가한 것입니다.

다음은 local_softirq_pending() 함수를 호출해서 Soft IRQ 서비스를 요청했는지 점검합니다. 전처리 코드로 irq_exit 함수를 확인하면 local_softirq_pending() 함수는 (irq_stat[(current_thread_info()->cpu)].__softirq_pending) 코드로 치환됩니다. 
void irq_exit(void)
{
...
__preempt_count_sub((1UL << ((0 + 8) + 8)));
if (!((preempt_count() & ((((1UL << (4))-1) << ((0 + 8) + 8)) | (((1UL << (8))-1) << (0 + 8)) | (((1UL << (1))-1) << (((0 + 8) + 8) + 4))))) && (irq_stat[(current_thread_info()->cpu)].__softirq_pending))
invoke_softirq();

이제 Soft IRQ 서비스를 실행하는 invoke_softirq() 함수를 점검해 봅시다.
1 static inline void invoke_softirq(void)
2 {
3
4 if (!force_irqthreads) {
5 #ifdef CONFIG_HAVE_IRQ_EXIT_ON_IRQ_STACK
6 __do_softirq();
7  #else
8 do_softirq_own_stack();
9 #endif
10 } else {
11 wakeup_softirqd();
12 }
13 }

위 코드에서 CONFIG_HAVE_IRQ_EXIT_ON_IRQ_STACK 컨피그에 따라 8번째 줄 혹은 10번째 줄 코드가 컴파일되는 것 같습니다. 이럴 때 라즈비안에서 리눅스를 빌드할 때 생성되는 .config 파일을 열어서 CONFIG_HAVE_IRQ_EXIT_ON_IRQ_STACK 가 있는지 확인해야 할까요? 그럴 필요 없이 다음과 같이 전처리 코드를 열어보면 실제 컴파일하는 코드를 보여줍니다.
1 static inline void invoke_softirq(void)
2 {
3 if (!force_irqthreads) {
 # 361 "/home/pi/dev_raspberrian/src/rasp_kernel/linux/kernel/softirq.c"
4   do_softirq_own_stack();
5  } else {
6   wakeup_softirqd();
7  }

3번째 줄 코드부터 볼까요? force_irqthreads 변수를 1로 설정했으면 wakeup_softirqd 함수를 호출해서 ksofrirqd 쓰레드를 깨웁니다. 이 변수는 리눅스 커널을 부팅하기 전 부트로더에서 threadirqs를 커맨드라인으로 전달하면 설정하는데 라즈베리안에서는 0으로 설정돼 있습니다.
[kernel/irq/manage.c]
__read_mostly bool force_irqthreads;
static int __init setup_forced_irqthreads(char *arg)
{
force_irqthreads = true;
return 0;
}
early_param("threadirqs", setup_forced_irqthreads);

커맨드 라인이란 부트로더에서 리눅스 커널을 부팅시킬 때 전달하는 아규먼트와 같은 역할을 합니다. 리눅스 커널이 부팅할 때 이 커맨드 라인을 읽어서 커널 시스템 설정을 합니다. 

이번에는 6번째 줄 코드입니다. do_softirq_own_stack 함수는 바로 __do_softirq 함수를 호출합니다.
static void do_softirq_own_stack(void)
{
__do_softirq();
}

invoke_softirq 함수는 복잡해 보였는데 코드를 분석하니 바로 __do_softirq 함수를 호출하는 간단한 코드였습니다. 




핑백

덧글

댓글 입력 영역