Linux Kernel(4.19) Hacks

rousalome.egloos.com

포토로그 Kernel Crash


통계 위젯 (화이트)

16192
888
89789


[리눅스커널][SoftIRQ] Soft IRQ 서비스는 언제 요청하나? 6장. 인터럽트 후반부 처리

6.7 Soft IRQ 서비스는 언제 요청하나?

이번 시간에는 Soft IRQ 서비스를 어떻게 요청하는지 배워 보겠습니다. 우리는 이전 절에서 Soft IRQ를 등록하는 과정을 배웠습니다. 다음 코드와 같이 open_softirq() 함수를 써서 TIMER_SOFTIRQ 타입의 Soft IRQ 서비스를 등록했습니다.
[https://elixir.bootlin.com/linux/v4.19.30/source/kernel/time/timer.c]
1 void __init init_timers(void)
2 {
3 init_timer_cpus();
4 open_softirq(TIMER_SOFTIRQ, run_timer_softirq);
5 }

 TIMER_SOFTIRQ 타입의 Soft IRQ 서비스를 등록만 하면 핸들러 함수인 run_timer_softirq() 함수가 호출될까요? 그렇지 않습니다. 따로 Soft IRQ 서비스를 요청해야 합니다. 인터럽트 핸들러를 등록하는 과정보다 약간 복잡합니다.

6.7.1 Soft IRQ 서비스 요청 전체 흐름도

전체 Soft IRQ 구조에서 Soft IRQ 서비스를 요청하는 흐름도는 다음과 같습니다.
  
[그림 6.11] Soft IRQ 서비스 요청 시 자료구조

위 그림과 같이 __softirq_pending 변수의 비트 필드 순서는 Soft IRQ 벡터에 대응합니다. 즉, __softirq_pending 변수의 두 번째 비트 필드가 1이면 run_timer_softirq 함수가 호출되는 것입니다.

6.7.2 raise_softirq() 함수 분석

Soft IRQ 서비스를 요청하려면 raise_softirq() 함수 혹은 __raise_softirq_irqoff() 함수를 호출해야 합니다. raise_softirq() 함수 분석으로 Soft IRQ 서비스 요청 세부 동작을 알아봅시다.

raise_softirq() 함수 코드를 함께 보겠습니다. 
[https://elixir.bootlin.com/linux/v4.19.30/source/kernel/softirq.c]
1 void raise_softirq(unsigned int nr)
2 {
3 unsigned long flags;
4
5 local_irq_save(flags);
6 raise_softirq_irqoff(nr);
7 local_irq_restore(flags);
8 }
9
10 inline void raise_softirq_irqoff(unsigned int nr)
11{
12 __raise_softirq_irqoff(nr);
13
14 if (!in_interrupt())
15 wakeup_softirqd();
16 }

raise_softirq() 함수 6 번째 줄 코드를 보면 raise_softirq_irqoff() 함수를 호출합니다. 이어서 raise_softirq_irqoff() 함수에서 __raise_softirq_irqoff() 함수를 호출합니다.

Soft IRQ 서비스를 요청하려면 raise_softirq() 혹은 __raise_softirq_irqoff() 함수를 호출해야 하는데 두 함수의 차이점은 뭘까요?

raise_softirq() 함수는 local_irq_save()와 local_irq_store() 함수를 써서 raise_softirq_irqoff() 함수 실행 도중 인터럽트 동기화를 수행합니다. 그래서 대부분 Soft IRQ 서비스를 요청할 때 raise_softirq() 함수를 씁니다. 다만 이미 인터럽트 발생에 필요없다면 __raise_softirq_irqoff() 함수를 써도 됩니다.


raise_softirq_irqoff() 함수 14~16 번째 줄 코드로 흥미로운 사실을 유추할 수 있습니다.
14 if (!in_interrupt())
15 wakeup_softirqd();
16 }

14 번째 줄은 현재 실행 코드가 인터럽트 컨택스트 아닌지 점검합니다. 인터럽트 컨택스트가 아니면 15 번째 줄 코드와 같이 wakeup_softirq() 함수를 호출해 ksoftirqd 스레드를 깨웁니다. 

위 코드 분석으로 다음과 같은 사실을 알 수 있습니다.
인터럽트 컨택스트가 아닐 때도 Soft IRQ 서비스 요청을 할 수 있습니다.

이번에는 __raise_softirq_irqoff() 함수 코드를 분석할 차례입니다.
[https://elixir.bootlin.com/linux/v4.19.30/source/kernel/softirq.c]
1 void __raise_softirq_irqoff(unsigned int nr)
2 {
3 trace_softirq_raise(nr);
4 or_softirq_pending(1UL << nr);
5 }

3 번째 줄 코드는 softirq_raise이란 ftrace 이벤트 로그를 출력합니다. softirq_raise 이벤트가 켜져 있을 때만 실행합니다.

다음 4 번째 줄 코드를 봅시다.
입력 인자인 nr을 왼쪽으로 쉬프트한 값을 or_softirq_pending() 함수에 전달합니다.

다음 소절에선 or_softirq_pending() 함수 분석을 진행합니다.

6.7.3 irq_stat 전역 변수 분석

or_softirq_pending() 함수를 호출하면 irq_stat 전역 변수에 비트 마스크를 설정합니다.
Soft IRQ 서비스를 요청하면 이 정보를 irq_stat란 전역 변수에 저장합니다.

이어서 or_softirq_pending() 함수 코드를 봅시다. 
[https://elixir.bootlin.com/linux/v4.19.30/source/include/linux/interrupt.h]
1 #define or_softirq_pending(x)  (local_softirq_pending() |= (x))
2
3 #define local_softirq_pending() \
4 __IRQ_STAT(smp_processor_id(), __softirq_pending)
5
6 extern irq_cpustat_t irq_stat[]; /* defined in asm/hardirq.h */
7 #define __IRQ_STAT(cpu, member) (irq_stat[cpu].member)

or_softirq_pending() 함수는 연달아 다른 함수 코드로 치환됩니다. 이해하기 편하게 코드를 펼치면 다음과 같습니다.
or_softirq_pending(x)
  (local_softirq_pending() |= (x))
   __IRQ_STAT(smp_processor_id(), __softirq_pending)
  (irq_stat[cpu].member)

or_softirq_pending 매크로 함수를 쓰면 다음 코드가 실행된다는 점을 알 수 있습니다.
(irq_stat[cpu].__softirq_pending) |= (1UL << x)

분석한 코드 내용을 종합하면 위 코드 연산 결과는 다음과 같이 알기 쉽게 표현할 수 있습니다.
#define or_softirq_pending(x)  (local_softirq_pending() |= (x))

irq_stat[cpu]__softirq_pending |=  (1UL << nr)
irq_stat[cpu]__softirq_pending =  irq_stat[cpu]__softirq_pending + (1UL << nr)

원래 __softirq_pending 변수가 저장한 값과 “|=” 연산을 하니 __softirq_pending 필드가 저장한 값은 그대로 두는 것입니다. 

매크로 함수 중에 smp_processor_id() 함수는 현재 실행 중인 CPU 번호를 알려 줍니다. 코드를 잠깐 보면 smp_processor_id() 함수는 current_thread_info()->cpu로 치환됩니다. 5장에서 배운 in_interrupt() 함수와 비슷한 원리입니다.

이번에 irq_stat 전역 변수를 소개합니다.
[https://elixir.bootlin.com/linux/v4.19.30/source/include/linux/irq_cpustat.h]
#define __IRQ_STAT(cpu, member) (irq_stat[cpu].member)

[https://elixir.bootlin.com/linux/v4.19.30/source/kernel/softirq.c]
extern irq_cpustat_t irq_stat[]; /* defined in asm/hardirq.h */

irq_stat 는 CPU 개수만큼 배열입니다.

라즈베리파이는 4개의 CPU를 쓰므로 irq_stat 변수를 Trace32 프로그램으로 보면 다음과 같습니다.
v.v %t %d %h %i irq_stat
(static irq_cpustat_t [4]) irq_stat = (
    [0] = (
      (unsigned int) __softirq_pending = 0 = 0x1,
    [1] = (
      (unsigned int) __softirq_pending = 0 = 0x3,
    [2] = (
      (unsigned int) __softirq_pending = 0 = 0x0,
    [3] = (
      (unsigned int) __softirq_pending = 0 = 0x6,

커널은 CPU 별로 지정한 __softirq_pending 변수에 (1 << Soft IRQ 서비스 아이디) 비트 연산 결과를 저장합니다. Soft IRQ 서비스 아이디를 왼쪽 시프트 한 결괏값입니다.

각 CPU 별로 저장한 __softirq_pending 변수를 해석하면 다음과 같습니다. 
CPU 번호 10진수 2진수 비트 연산자 Soft IRQ 서비스 아이디
0 1 001 (1 << 0) HI_SOFTIRQ
1 2 010 (1 << 1) TIMER_SOFTIRQ
2 0 000 0 N/A
3 6 110 (1 << 2) | (1 << 1) NET_TX_SOFTIRQ | TIMER_SOFTIRQ

각 Soft IRQ 서비스 아이디 값은 다음과 같이 선언돼있습니다. HI_SOFTIRQ 아이디가 0이고, RCU_SOFTIRQ가 9입니다.
[https://elixir.bootlin.com/linux/v4.19.30/source/include/linux/interrupt.h] 
enum
{
HI_SOFTIRQ=0,
TIMER_SOFTIRQ,
NET_TX_SOFTIRQ,
NET_RX_SOFTIRQ,
BLOCK_SOFTIRQ,
IRQ_POLL_SOFTIRQ,
TASKLET_SOFTIRQ,
SCHED_SOFTIRQ,
HRTIMER_SOFTIRQ, 
RCU_SOFTIRQ,  
NR_SOFTIRQS
};

Trace32 프로그램으로 확인한 irq_stat 값과 위에서 연산한 결과를 주석문으로 같이 확인해보겠습니다.    
(static irq_cpustat_t [4]) irq_stat = (
    [0] = (
      (unsigned int) __softirq_pending = 0 = 0x1, // HI_SOFTIRQ
    [1] = (
      (unsigned int) __softirq_pending = 0 = 0x2, // HI_SOFTIRQ | TIMER_SOFTIRQ
    [2] = (
      (unsigned int) __softirq_pending = 0 = 0x0,
    [3] = (
      (unsigned int) __softirq_pending = 0 = 0x6, // NET_TX_SOFTIRQ | TIMER_SOFTIRQ

CPU0에는 HI_SOFTIRQ이란 Soft IRQ 서비스를 요청했고, CPU1는 HI_SOFTIRQ와 TIMER_SOFTIRQ Soft IRQ 서비스를 요청한 상태입니다. CPU3에서 요청된 Soft IRQ 서비스는 NET_TX_SOFTIRQ와 TIMER_SOFTIRQ입니다.


이렇게 or_softirq_pending란 함수를 3단계로 매크로가 매크로를 호출하니 소스를 따라가기 어렵습니다. 이럴 때는 3장에서 소개했듯이 전처리 코드를 보면 효율적으로 커널 코드를 읽을 수 있습니다. 전처리 코드는 매크로를 모두 푼 정보를 담고 있기 때문입니다.

전처리 코드를 보겠습니다. 
1 void __raise_softirq_irqoff(unsigned int nr)
2 {
3  trace_softirq_raise(nr);
4  ((irq_stat[(current_thread_info()->cpu)].__softirq_pending) |= (1UL << nr));
5 }

전처리 코드를 보니 or_softirq_pending(1UL << nr); 코드는 위와 같은 4번째 줄 코드로 치환됩니다. irq_stat[cpu갯수].__softirq_pending 멤버에 Soft IRQ enum 값을 왼쪽으로 쉬프트한 값을 OR 연산으로 저장합니다.


6.7.4 Soft IRQ 서비스를 요청했는지는 누가 어떻게 점검할까?

Soft IRQ 서비스를 요청하는 코드를 살펴봤습니다. Soft IRQ 서비스를 요청했으면 어디선가 이 서비스 요청 알아보고 점검할 것입니다. Soft IRQ 서비스를 요청했는지는 누가 어떻게 점검할까요? 

Soft IRQ 서비스 요청 여부는 인터럽트 핸들러 수행이 끝나면 호출하는 irq_exit()과 ksoftirqd 스레드 핸들 함수인 run_ksoftirqd() 함수에서 점검합니다. 이 때 커널에서 제공하는 local_softirq_pending() 함수를 호출합니다.

실제 소스 코드를 함께 살펴보겠습니다. 다음 코드 irq_exit() 함수에서 local_softirq_pending() 함수를 호출합니다.
[https://elixir.bootlin.com/linux/v4.19.30/source/kernel/softirq.c]
1 void irq_exit(void)
2 {
...
3 if (!in_interrupt() && local_softirq_pending())
4 invoke_softirq();

irq_exit() 함수에서 2가지 조건을 점검한 후 Soft IRQ 서비스를 실행할지 결정합니다. 첫 번째는 현재 실행 중인 코드가 인터럽트 컨택스트 상태인지와 두 번째로 Soft IRQ 서비스 요청이 있었는지를 점검합니다.

irq_exit 함수를 전처리 코드에서 보면 local_softirq_pending() 함수는 irq_stat[(current_thread_info()->cpu)].__softirq_pending)로 치환됨을 알 수 있습니다.
1 void irq_exit(void)
2 {
...
3 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))

ksoftirqd 스레드도 마찬가지입니다. 다음 4번째 줄 코드에서 local_softirq_pending 함수를 호출해서 Soft IRQ 서비스 요청이 있었는지 점검합니다.
[https://elixir.bootlin.com/linux/v4.19.30/source/kernel/softirq.c]
1 static void run_ksoftirqd(unsigned int cpu)
2 {
3 local_irq_disable();
4 if (local_softirq_pending()) {
5 __do_softirq();

run_ksoftirqd() 함수를 전처리 코드로 열어봐도 (irq_stat[(current_thread_info()->cpu)].__softirq_pending) 코드가 실행함을 알 수 있습니다.
[./kernel/.tmp_softirq.i]
static void run_ksoftirqd(unsigned int cpu)
{
  do { arch_local_irq_disable(); trace_hardirqs_off(); } while (0);
  if ((irq_stat[(current_thread_info()->cpu)].__softirq_pending)) {
   __do_softirq();

이렇게 __raise_softirq_irqoff 함수를 써서 irq_stat[(current_thread_info()->cpu)].__softirq_pending)에 값을 써주면 나중에 이 변수를 읽어서 확인하는 것입니다.

이번 절에 배운 내용을 Q/A 로 간단히 정리해 보겠습니다.

Q: Soft IRQ 서비스는 어떻게 요청할까?

A: 인터럽트 핸들러에서 or_softirq_pending()이란 함수를 호출해서 Soft IRQ 서비스를 요청합니다. 

Q: Soft IRQ 서비스 요청은 어느 코드에서 점검할까?

A: 인터럽트 핸들러 실행을 마친 후 호출되는 irq_exit() 함수가 ksoftirqd 스레드 핸들인 run_softirqd() 함수에서 local_softirq_pending() 함수를 호출해 Soft IRQ 요청을 확인합니다. 

또한 Soft IRQ 서비스 핸들러를 호출하는  __do_softirq() 함수에서 각 CPU 별로 irq_stat[cpu].__softirq_pending 변수에 접근해서 비트 필드를 점검합니다. 비트 필드가 활성화됐으면 해당 softirq_vec에 접근해서 Soft IRQ 서비스 핸들러 함수를 호출합니다.

다음 절에는 Soft IRQ 서비스 요청을 어디서 처리하는지 살펴봅니다. 

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


# Reference 인터럽트 후반부 처리








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




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


    핑백

    덧글

    댓글 입력 영역