Linux Kernel(4.19) Hacks

rousalome.egloos.com

포토로그 Kernel Crash


통계 위젯 (화이트)

8200
629
98815


[리눅스커널][SoftIRQ] Soft IRQ 서비스는 누가 언제 처리하나?(1/2) 6장. 인터럽트 후반부 처리

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

이제 Soft IRQ 기법의 핵심인 Soft IRQ 서비스를 처리하는 흐름을 살펴볼 차례입니다. 

Soft IRQ 서비스는 언제 처리할까요? 아래 그림과 같이 인터럽트 핸들러를 처리하는 인터럽트 서비스 루틴이 끝나는 시점에 Soft IRQ 서비스 처리를 시작합니다. 
 
[그림 6.12]  Soft IRQ 서비스 요청 실행 흐름도

Soft IRQ 서비스 처리를 시작하는 코드를 점검하려면 인터럽트 서비스 루틴이 끝나는 코드부터 확인해야 합니다.

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

6.8.1 Soft IRQ 서비스 실행 진입점는 어디일까?

Soft IRQ 서비스 실행 진입점은 인터럽트를 핸들링을 마무리 한 후입니다.
__handle_domain_irq() 함수를 보면서 이 과정을 파악해봅시다. 
[https://elixir.bootlin.com/linux/v4.19.30/source/kernel/irq/irqdesc.c]
1 int __handle_domain_irq(struct irq_domain *domain, unsigned int hwirq,
2 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() 함수 서브 루틴에서 인터럽트 핸들러를 호출합니다.
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 번째 줄 코드 실행을 마치면 26 번재 줄 코드를 실행합니다. 즉, 인터럽트 핸들러 처리를 마치고 irq_exit() 함수를 호출합니다. irq_exit() 함수가 Soft IRQ 서비스를 처리하는 시작점입니다. 

5.2.2 소절에서 봤던 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

Soft IRQ 서비스 실행 시작점은 인터럽트 핸들러 실행을 마친 시점입니다. 이어서 irq_exit() 함수를 분석하겠습니다.

6.8.2 Soft IRQ 서비스 요청 점검

이전 소절에 분석한 바와 같이 인터럽트 핸들러 처리를 마무리 한 후 irq_exit() 함수를 호출합니다. irq_exit() 함수에서 Soft IRQ 서비스 요청을 점검하는 부분부터 분석하겠습니다.

Soft IRQ 서비스 시작점인 irq_exit() 함수 분석하기
Soft IRQ 서비스 실행의 출발점인 irq_exit() 함수를 보겠습니다.
[https://elixir.bootlin.com/linux/v4.19.30/source/kernel/softirq.c]
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() 함수가 true를 반환하게 활성화할 수 있습니다. 
[https://elixir.bootlin.com/linux/v4.19.30/source/include/linux/hardirq.h]
#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() 함수 분석하기

이어서 Soft IRQ 서비스를 실행하는 invoke_softirq() 함수를 점검해 봅시다.
[https://elixir.bootlin.com/linux/v4.19.30/source/kernel/softirq.c]
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으로 설정돼 있습니다.
[https://elixir.bootlin.com/linux/v4.19.30/source/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);

따라서 do_softirq_own_stack() 함수를 호출합니다.

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

이어서 do_softirq_own_stack() 함수를 보겠습니다. 
[https://elixir.bootlin.com/linux/v4.19.30/source/include/linux/interrupt.h]
static void do_softirq_own_stack(void)
{
__do_softirq();
}

do_softirq_own_stack() 함수는 바로 __do_softirq() 함수를 호출합니다.

정리하면 invoke_softirq() 함수는 특별한 동작을 수행하지 않습니다. 바로 __do_softirq() 함수를 호출합니다.


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

# Reference 인터럽트 후반부 처리








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




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



핑백

덧글

댓글 입력 영역