Linux Kernel(4.19) Hacks

rousalome.egloos.com

포토로그 Kernel Crash


통계 위젯 (화이트)

0112
737
82110


[리눅스커널][SoftIRQ] ksoftirqd 스레드란 6장. 인터럽트 후반부 처리

6.9 ksoftirqd 스레드

이번 절에서는 Soft IRQ 처리용으로 생성된 ksoftirqd 스레드에 대해 살펴봅니다. ksoftirqd 스레드 생성과정과 동작 원리를 배워봅시다.

6.9.1 ksoftirqd 스레드란

ksoftirqd이란 per-cpu 타입 프로세스입니다. 즉 CPU 개수만큼 생성해서 정해진 CPU 내에서만 실행합니다. ksoftirqd 프로세스는 커널 쓰레드로 Soft IRQ 서비스를 쓰레드 레벨에서 처리합니다. 

리눅스 커널을 탑재한 어떤 시스템에서도 볼 수 있는 친근한 프로세스입니다. 먼저 ksoftirqd 쓰레드를 같이 확인하겠습니다.

다음 사이트를 방문하면 다른 리눅스 시스템에서 ksoftirqd 스레드를 확인할 수 있습니다.
[출처: https://zetawiki.com/wiki/Ksoftirqd]
[root@zetawiki ~]# ps -ef | grep ksoftirqd | grep -v grep
root         3     2  0 Jan08 ?        00:00:07 [ksoftirqd/0]
root        13     2  0 Jan08 ?        00:00:10 [ksoftirqd/1]
root        18     2  0 Jan08 ?        00:00:08 [ksoftirqd/2]
root        23     2  0 Jan08 ?        00:00:07 [ksoftirqd/3]
root        28     2  0 Jan08 ?        00:00:07 [ksoftirqd/4]
root        33     2  0 Jan08 ?        00:00:06 [ksoftirqd/5]
root        38     2  0 Jan08 ?        00:00:06 [ksoftirqd/6]
root        43     2  0 Jan08 ?        00:00:07 [ksoftirqd/7]

“ps –ef” 명령어와 grep 명령어를 조합해서 프로세스 목록 중 ksoftirqd 쓰레드만 출력했습니다. ksoftirqd란 쓰레드 이름 뒤에 숫자가 보입니다. 이는 각각 ksoftirqd 쓰레드가 실행 중인 CPU번호입니다.

이번에는 라즈베리파이에서 ksoftirqd 쓰레드를 확인해 보겠습니다.
root@raspberrypi:/home/pi# ps axl | grep ksoftirq
1     0     7     2  20   0      0     0 smpboo S    ?          0:00 [ksoftirqd/0]
1     0    14     2  20   0      0     0 smpboo S    ?          0:00 [ksoftirqd/1]
1     0    19     2  20   0      0     0 smpboo S    ?          0:00 [ksoftirqd/2]
1     0    24     2  20   0      0     0 smpboo S    ?          0:00 [ksoftirqd/3]

라즈비안은 CPU4개인 쿼드코어 시스템이므로 CPU는 0~3번까지 보입니다.

ksoftirqd 는 시스템 CPU 개수만큼 생성되는데 커널은 다음 규칙으로 ksoftirqd프로세스 이름을 짓습니다.
"ksoftirqd/[CPU 번호]"

ksoftirqd/0 쓰레드는 CPU0에서만 실행하고 ksoftirqd/1, ksoftirqd/2 그리고 ksoftirqd/3 쓰레드도 해당 CPU(CPU1/CPU2/CPU3)에서 수행합니다. 이런 프로세스를 per-cpu 쓰레드라고 부릅니다. 라즈비안에서는 4개 ksoftirqd 프로세스가 보이니 CPU가 4개인 쿼드코어 시스템이라고 유추할 수 있습니다.

 ksoftirqd 프로세스는 언제 생성할까요? 다음과 같이 spawn_ksoftirqd() 함수에서 생성합니다. 
[https://elixir.bootlin.com/linux/v4.19.30/source/kernel/softirq.c]
static __init int spawn_ksoftirqd(void)
{
cpuhp_setup_state_nocalls(CPUHP_SOFTIRQ_DEAD, "softirq:dead", NULL,
  takeover_tasklets);
BUG_ON(smpboot_register_percpu_thread(&softirq_threads));

return 0;
}

spawn_ksoftirqd() 함수 앞에 __init란 매크로가 보이니 커널이 부팅할 때 1번 실행합니다.

ksoftirqd 프로세스 선언부를 보면 ksoftirqd 프로세스가 실행하면 run_ksoftirqd 함수가 수행한다는 사실을 알 수 있습니다.
[https://elixir.bootlin.com/linux/v4.19.30/source/kernel/softirq.c]
static struct smp_hotplug_thread softirq_threads = {
.store = &ksoftirqd,
.thread_should_run = ksoftirqd_should_run,
.thread_fn = run_ksoftirqd,
.thread_comm = "ksoftirqd/%u",
};

ksoftirqd와 같은 per-cpu 타입 쓰레드는 smp 핫플러그 쓰레드로 등록해서 실행합니다. 

간단히 설명을 드리면, 리눅스 커널에선 시스템 부하가 떨어졌을 때는 여러 개의 CPU가 실행할 필요가 없습니다. 예를 들어 라즈베리파이에서 음악이나 동영상 재생을 안 하고 아무 프로그램도 실행을 안 한 상태로 두면 1개 CPU만 실행합니다. 시스템 부하에 따라 CPU를 끄고 키는 동작을 하는데 이때 smp_boot 쓰레드가 동작합니다.

ksoftirqd 쓰레드는 각 CPU마다 생성된 프로세스입니다. 예를 들면 "ksoftirqd/2" 쓰레드는 CPU2에서만 일을 합니다.  그런데 CPU2가 꺼져 있으면 안 됩니다. CPU2가 꺼지는 동작을 유식하게 CPU Hot-plugout이라고 합니다. 

만약 smp_boot는 "ksoftirqd/2"에서 더 처리해야 할 Soft IRQ 서비스가 있는데 CPU2가 Hotplug-out될 상황이면 이 Soft IRQ 서비스를 "ksoftirqd/3”와 같이 깨어 있는 다른 ksoftirqd 쓰레드가 실행하게 처리합니다.

smpboot를 관리하는 함수는 smpboot_thread_fn() 이며 다음 코드와 같습니다.
[https://elixir.bootlin.com/linux/v4.19.30/source/kernel/smpboot.c]
1 static int smpboot_thread_fn(void *data)
2 {
3 struct smpboot_thread_data *td = data;
....
4 if (!ht->thread_should_run(td->cpu)) {
5 preempt_enable_no_resched();
6 schedule();
7 } else {
8 __set_current_state(TASK_RUNNING);
9 preempt_enable();
10 ht->thread_fn(td->cpu);
11 }

smpboot 에 핫플러그인으로 등록된 ksoftirqd 쓰레드의 thread_fn 멤버로 등록한 run_ksoftirqd() 함수는 smpboot_thread_fn() 함수의 다음 10번 줄 코드에서 실행합니다.

smp_boot는 이 책의 범위를 벗어나니 더 자세한 내용은 다음 저자 블로그에 포스팅된 글을 참고하세요. 
[http://rousalome.egloos.com/9994595]


6.9.2 ksoftirqd 스레드는 언제 깨울까?

ksoftirqd 스레드는 깨우려면 wakeup_softirqd() 함수를 호출해야 합니다. 이 함수는 다음 조건에서 호출합니다.
 - __do_softirq() 함수에서 Soft IRQ 서비스 실행 중 MAX_SOFTIRQ_TIME 지피 시간만큼 경과했을 때
 - 인터럽트 컨택스트가 아닌 상황에서 Soft IRQ 서비스를 요청할 때

ksoftirqd 스레드를 깨우는 wakeup_softirqd() 함수를 분석하겠습니다.
[https://elixir.bootlin.com/linux/v4.19.30/source/kernel/softirq.c]
01 static void wakeup_softirqd(void)
02 {
03 /* Interrupts are disabled: no need to stop preemption */
04 struct task_struct *tsk = __this_cpu_read(ksoftirqd);
05
06 if (tsk && tsk->state != TASK_RUNNING)
07 wake_up_process(tsk);
08 }

먼저 04 번째 줄 코드를 보겠습니다.
04 struct task_struct *tsk = __this_cpu_read(ksoftirqd);

percpu 타입 변수인 ksoftirqd으로 실행 중인 CPU에 해당하는 struct task_struct 구조체를 *tsk 변수로 읽습니다.

리눅스 커널 디버깅 툴인 크래시 유틸리티로 본 ksoftirqd 변수는 다음과 같습니다. 
crash> p ksoftirqd
PER-CPU DATA TYPE:
  struct task_struct *ksoftirqd;
PER-CPU ADDRESSES:
  [0]: c17cd608
  [1]: c17d6608
  [2]: c17df608
  [3]: c17e8608

CPU 갯수 별로 struct task_struct 구조체가 있습니다.

06~07 번째 줄 코드를 보겠습니다.
06 if (tsk && tsk->state != TASK_RUNNING)
07 wake_up_process(tsk);

06 번째 줄 코드는 07 번째 줄 코드 실행 조건문입니다. 
ksoftirqd 스레드의 태스트 디스크립터 struct task_struct 구조체 state 필드가 TASK_RUNNING이 아닌 경우 07 번째 줄 코드를 실행합니다. ksoftirqd 스레드가 실행 중이지 않을 때 07 번째 줄 코드인 wake_up_process() 함수를 실행합니다.

프로세스 중복 실행 요청을 방지하기 위한 예외 처리 코드입니다.

wake_up_process() 함수를 호출하면 스케줄러에게 해당 프로세스 실행 요청을 합니다.

__do_softirq() 함수에서 Soft IRQ 서비스 실행 시간이 MAX_SOFTIRQ_TIME을 넘었을 때 
[https://elixir.bootlin.com/linux/v4.19.30/source/kernel/softirq.c]
01 asmlinkage __visible void __softirq_entry __do_softirq(void)
02 {
03 unsigned long end = jiffies + MAX_SOFTIRQ_TIME;
...
04 restart:
...
05 while ((softirq_bit = ffs(pending))) {
...
06 trace_softirq_entry(vec_nr);
07 h->action(h);
08 trace_softirq_exit(vec_nr);
...
09 h++;
10 pending >>= softirq_bit;
11 }
...
12 pending = local_softirq_pending();
13 if (pending) {
14 if (time_before(jiffies, end) && !need_resched() &&
15     --max_restart)
16 goto restart;
17
18 wakeup_softirqd();
19 }

05~11 번째 줄 코드에서 Soft IRQ 서비스 실행합니다.

14~15 번째 줄 코드는 Soft IRQ 서비스 실행 시간이 MAX_SOFTIRQ_TIME을 넘었는지 체크합니다.

__do_softirq() 함수 실행 시간이 길어지면 프로세스 실행에 악영향을 끼칠 수 있습니다. 
18 번째 줄 코드와 같이 wakeup_softirqd() 함수를 호출해 ksoftirqd 프로세스를 깨우고 __do_softirq() 함수 실행을 종료합니다.

Soft IRQ 서비스를 요청할 때

Soft IRQ 서비스를 요청하기 위해서는 raise_softirq_irqoff() 함수를 호출해야 합니다.
[https://elixir.bootlin.com/linux/v4.19.30/source/kernel/softirq.c]
01 inline void raise_softirq_irqoff(unsigned int nr)
02 {
03 __raise_softirq_irqoff(nr);
04
05 if (!in_interrupt())
06 wakeup_softirqd();
07 }

그런데 05 번째 줄 코드와 같이 현재 실행 중인 코드가 인터럽트 컨택스트 아닌 경우 wakeup_softirqd() 함수를 호출해 ksoftirqd 프로세스를 깨웁니다.

Soft IRQ 서비스 실행은 보통 인터럽트 컨택스트에서 요청합니다. 그런데 이렇게 인터럽트 컨택스트가 아닌 경우에도 Soft IRQ 서비스 실행을 요청할 수 있습니다. Soft IRQ 와 연동해 동작하는 드라이버에서 인터럽트 컨택스트가 아닌 경우에도 Soft IRQ 서비스 실행 요청을 하도록 인터페이스를 제공합니다.  

6.9.3 ksoftirqd 핸들 run_softirqd() 함수 분석

다음에 볼 코드는 ksoftirqd 프로세스가 실행될 때 호출되는 run_ksoftirqd() 함수입니다. 
1 static void run_ksoftirqd(unsigned int cpu)
2 {
3 local_irq_disable();
4 if (local_softirq_pending()) {
5 __do_softirq();
6 local_irq_enable(); 
7 cond_resched_rcu_qs();
8 return;
9 }
10 local_irq_enable();
11 }

4 번째 줄 코드를 보면 local_softirq_pending 함수를 호출해서 요청한 Soft IRQ 서비스가 있는지 점검합니다. 만약에 Soft IRQ 서비스 요청이 있으면 __do_softirq() 함수를 호출해서 Soft IRQ 서비스 핸들러를 실행합니다.

이번에는 ksoftirqd 쓰레드를 제어하는 ksoftirqd_should_run() 함수를 살펴보겠습니다.
static int ksoftirqd_should_run(unsigned int cpu)
{
return local_softirq_pending();
}

함수 이름과 같이 ksoftirqd 쓰레드를 실행 여부를 알려주는 임무를 수행합니다.
이번에도 local_softirq_pending() 함수를 호출해서 Soft IRQ 서비스 요청이 있으면 ksoftirqd 쓰레드를 실행 여부를 알려줍니다.

전체 Soft IRQ 구조에서 ksoftirqd 쓰레드는 어떤 역할을 할까요? 다음 그림에서 검은색으로 된 블록을 보시면 됩니다.
 
[그림 6.15] ksoftirqd 스레드 실행 흐름도

인터럽트가 발생한 후 irq_exit() 함수로 시작해서 __do_softirq() 함수에서 Soft IRQ 서비스 핸들러를 실행합니다. __do_softirq() 함수에서 Soft IRQ 서비스 핸들러 실행 시간이 2ms 초를 넘어서면 하던 일을 멈추고 ksoftirqd 프로세스를 깨웁니다. Soft IRQ 서비스는 프로세스 실행을 멈춘 상태에서 동작하므로 실행 시간이 길면 시스템 응답 속도가 느려져 오동작할 수 있기 때문입니다.

여기까지 ksoftirqd 쓰레드가 어떻게 생성되고 Soft IRQ 구조에서 어떤 역할을 수행하는지 알아봤습니다. ksoftirqd 쓰레드는 IRQ Thread와 비슷한 역할을 합니다. ksoftirqd 쓰레드는 프로세스 레벨에서 Soft IRQ 서비스를 처리하는 임무를 수행합니다. 다른 관점으로 Soft IRQ가 인터럽트 후반부 처리를 할 때의 주인공이 ksoftirqd 쓰레드인 것입니다.

# Reference 인터럽트 후반부 처리








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




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




핑백

덧글

댓글 입력 영역