smpboot(smp thread) 스레드 사용 배경
최근에 사용되는 대부분 시스템은 멀티 프로세서 기반으로 실행됩니다. 예를 들어 인터넷이나 TV 광고에서 "최신 인텔 멀티 프로세서 코어가 적용된 제품"이란 이야기가 들리죠. 일반 대중이 하나 이상 쓰고 있는 휴대폰도 멀티 프로세서 시스템 기반입니다. 휴대폰에는 Arm 프로세서가 탑재됐는데, CPU 코어의 갯수가 8개 이상 구성돼 있죠.
리눅스 커널에서도 이런 멀티 프로세서 구조에서 이를 소프트웨어로 처리하는 드라이버와 같은 존재가 있는데요. 이를 smpboot 혹은 smp 핫 플러그 스레드라고 합니다.
smpboot(smp thread) 스레드 확인하기
그렇다면 smpboot로 등록한 커널 프로세스는 어떻게 확인할 수 있을까요? 리눅스 터미널에서 'ps -ely | grep smpboot' 명령어를 입력하면 smpboot로 등록한 프로세스 목록이 확인됩니다.
root@raspberrypi:/home/pi# ps -ely | grep smpboot
S 0 12 2 0 80 0 0 0 smpboot_th 00:00:00 ksoftirqd/0
S 0 14 2 0 -40 - 0 0 smpboot_th 00:00:00 migration/0
S 0 15 2 0 80 0 0 0 smpboot_th 00:00:00 cpuhp/0
S 0 16 2 0 80 0 0 0 smpboot_th 00:00:00 cpuhp/1
S 0 17 2 0 -40 - 0 0 smpboot_th 00:00:00 migration/1
S 0 18 2 0 80 0 0 0 smpboot_th 00:00:00 ksoftirqd/1
S 0 21 2 0 80 0 0 0 smpboot_th 00:00:00 cpuhp/2
S 0 22 2 0 -40 - 0 0 smpboot_th 00:00:00 migration/2
S 0 23 2 0 80 0 0 0 smpboot_th 00:00:00 ksoftirqd/2
S 0 26 2 0 80 0 0 0 smpboot_th 00:00:00 cpuhp/3
S 0 27 2 0 -40 - 0 0 smpboot_th 00:00:00 migration/3
S 0 28 2 0 80 0 0 0 smpboot_th 00:00:00 ksoftirqd/3
다른 프로세스 이름과 비교하면 smpboot 프로세스의 특징이 보이는데요. 가장 큰 특징은 다음과 같습니다.
* '프로세스 이름'/CPU 코어 번호
smpboot 프로세스는 자신에게 지정된 CPU 코어에서만 실행됩니다. 그래서 per-cpu 타입 스레드라고 부릅니다. 참고로 per-cpu 타입 변수는 특정 CPU 코어에서만 엑세스됩니다. 1번 째 CPU 코어에서 per-cpu runqueues 라는 변수에 접근하면 runqueues 변수의 1번째 per-cpu 주소에 접근합니다.
smpboot(smp thread) 스레드의 동작 방식
"ksoftirqd/[0~4]" 혹은 "migration/[0~4]"와 와 같은 per-cpu 타입 쓰레드는 smp 핫플러그 쓰레드로 등록해서 실행합니다. 여기서 [0~4]는 CPU 코어 번호입니다.
smpboot 스레드의 동작 원리를 설명하기 위해 간단한 예를 들겠습니다. 라즈베리 파이에서 음악이나 동영상을 재생하지 않고 가만히 있으면 CPU 코어는 한 개만 실행합니다. 대신 다른 CPU 코어는 오프라인 모드에 진입하죠. 다 소모 전력을 최소화하기 위한 메커니즘입니다.
간단히 설명을 드리면, 리눅스 커널에선 시스템이 일을 안 할 때는 여러 개의 CPU가 실행할 필요가 없습니다. 예를 들어 라즈베리안에서 음악이나 동영상을 재생 안 하고 아무 프로그램도 실행을 안 한 상태로 두면 CPU 하나만 실행하거든요.
그래서 시스템 부하에 따라 CPU를 끄고 키는 동작(오프 라인 모드 진입, 온라인 모드 진입)을 하는데 이때 smp 핫플러그 쓰레드가 동작합니다.
예를 들어 ksoftirqd 스레드는 각 CPU마다 생성된 프로세스입니다. 예를 들면 "ksoftirqd/2" 쓰레드는 CPU2에서만 일을 합니다. 시스템에 부하가 적으면 CPU2는 오프 라인 모드로 진입합니다. 유저가 아무런 동작을 하지 않아 실행할 프로세스가 없는데 구지 CPU2를 실행시킬 필요가 없겠죠. 이처럼 소프트웨어 매커니즘이 동작해 CPU2가 꺼지는 동작을 유식하게 CPU Hot-plugout이라고 합니다.
만약 "ksoftirqd/2"에서 더 처리해야 할 Soft IRQ 서비스가 있는데 CPU2가 Hotplug-out될 상황이면 이 Soft IRQ 서비스를 "ksoftirqd/3”와 같이 깨어 있는 다른 ksoftirqd 쓰레드가 실행하게 처리합니다.
smpboot (smp thread)스레드 핸들러 코드 리뷰
smpboot 핫 플로그 스레드로 등록한 프로세스가 어떻게 동작하는지를 파악하려면 smpboot_thread_fn() 함수를 분석해야 합니다. 다음은 smpboot_thread_fn() 함수의 구현부입니다.
01 static int smpboot_thread_fn(void *data)
02 {
03 struct smpboot_thread_data *td = data;
04 struct smp_hotplug_thread *ht = td->ht;
05
06 while (1) {
07 set_current_state(TASK_INTERRUPTIBLE);
08 preempt_disable();
...
09 /* Check for state change setup */
10 switch (td->status) {
11 case HP_THREAD_NONE:
12 __set_current_state(TASK_RUNNING);
13 preempt_enable();
14 if (ht->setup)
15 ht->setup(td->cpu);
16 td->status = HP_THREAD_ACTIVE;
17 continue;
18
19 case HP_THREAD_PARKED:
20 __set_current_state(TASK_RUNNING);
21 preempt_enable();
22 if (ht->unpark)
23 ht->unpark(td->cpu);
24 td->status = HP_THREAD_ACTIVE;
25 continue;
26 }
27
28 if (!ht->thread_should_run(td->cpu)) {
29 preempt_enable_no_resched();
30 schedule();
31 } else {
32 __set_current_state(TASK_RUNNING);
33 preempt_enable();
34 ht->thread_fn(td->cpu);
35 }
36 }
37 }
함수에서 예외 처리 루틴을 제외하고 핵심 코드를 모아서 분석하겠습니다.
먼저 07번째 줄을 보겠습니다.
07 set_current_state(TASK_INTERRUPTIBLE);
프로세스의 상태를 TASK_INTERRUPTIBLE로 변경하면서 슬립에 진입합니다.
10~26번째 줄은 핫 플러그인 스레드의 상태 머신에 따라 세부 처리를 하는 루틴입니다.
28~30번째 줄은 핫 플로그 스레드가 실행한 조건이 아닐 때 슬립에 진입하는 코드입니다.
28 if (!ht->thread_should_run(td->cpu)) {
29 preempt_enable_no_resched();
30 schedule();
슬립에 진입하는 코드는 30번째 줄입니다.
마지막으로 31~35번째 줄을 보겠습니다.
31 } else {
32 __set_current_state(TASK_RUNNING);
33 preempt_enable();
34 ht->thread_fn(td->cpu);
35 }
핫 플러그인 스레드로 등록함 함수가 실행되는 루틴인데요. 34번째 줄의 'ht->thread_fn' 구문에서
핫 플러그인 스레드의 핸들러 함수가 호출되니다.
예를 들어 ksoftirqd/2 프로세스인 경우 34번째 줄에서 다음과 같은 함수 흐름으로 run_ksoftirqd() 함수가 호출됩니다.
[ 234.06782 01-01 00:03:56.390 CPU2] [<ffffffff820bbdac>] tasklet_action+0x6c/0xe0
[ 234.06791 01-01 00:03:56.390 CPU2] [<ffffffff820bb870>] __do_softirq+0x110/0x2d0
[ 234.06800 01-01 00:03:56.390 CPU2] [<ffffffff820bba5d>] run_ksoftirqd+0x2d/0x60
[ 234.06809 01-01 00:03:56.390 CPU2] [<ffffffff820e34d4>] smpboot_thread_fn+0x1d4/0x2e0
[ 234.06827 01-01 00:03:56.390 CPU2] [<ffffffff820db2bb>] kthread+0xeb/0xf0
[ 234.06836 01-01 00:03:56.390 CPU2] [<ffffffff820db1d0>] ? kthread_create_on_node+0x140/0x140
[ 234.06845 01-01 00:03:56.390 CPU2] [<ffffffff82a2345c>] ret_from_fork+0x7c/0xb0
[ 234.06854 01-01 00:03:56.390 CPU2] [<ffffffff820db1d0>] ? kthread_create_on_node+0x140/0x140
'Core BSP 분석 > 리눅스 커널 핵심 분석' 카테고리의 다른 글
[Crash-Utility][리눅스커널][디버깅] Radix Tree(라덱스-트리) 디버깅 하기(크래시 유틸리티) (0) | 2023.05.06 |
---|---|
[리눅스커널] 비트 마스크를 어셈블리 코드로 빨리 읽는 방법 - HARDIRQ_MASK, SOFTIRQ_MASK, NMI_MASK (0) | 2023.05.06 |
[리눅스커널][디버깅] 슬럽(슬랩) 캐시 오브젝트 T32로 메모리 디버깅하기 (0) | 2023.05.06 |
[리눅스커널][디버깅] 유저공간 High Memory Zone 페이지 할당 과정 (0) | 2023.05.06 |
[Linux][Kernel] preempt_disable()/preempt_enable() 주의 사항 (0) | 2023.05.06 |