Linux Kernel(4.19) Hacks

rousalome.egloos.com

포토로그 Kernel Crash


통계 위젯 (화이트)

96258
1323
114598


[리눅스커널] 스케줄링/디버깅: 프로세스를 깨울 때 콜스택 분석하기 10. Process Scheduling

이번에는 sched_wakeup ftrace 이벤트를 출력하는 ttwu_do_wake_up() 함수 콜스택을 확인하겠습니다. ftrace 메시지를 열어 보면 ttwu_do_wakeup() 함수가 다양한 경로로 호출된다는 사실을 알 수 있습니다.

이 중 평소 자주 볼 수 있는 함수 호출 흐름 중심으로 살펴봅시다.


ftrace를 설정하고 ftrace 로그를 받은 방법은 이전 10.11.2 절에서 소개한 내용을 참고하세요.


먼저 ftrace 메시지를 보겠습니다.
1 lxterminal-840 [000] d... 8688.516171: ttwu_do_wakeup+0x10/0x1a4 <-ttwu_do_activate+0x80/0x84
2 lxterminal-840 [000] d... 8688.516204: <stack trace>
3  => wake_up_process+0x20/0x24
4 => insert_work+0x8c/0xd4
5 => __queue_work+0x1ac/0x51c
6 => queue_work_on+0xa0/0xa4
7 => tty_flip_buffer_push+0x3c/0x40
8 => pty_write+0x84/0x88
9 => n_tty_write+0x368/0x458
10 => tty_write+0x1c8/0x2e4
11 => __vfs_write+0x3c/0x13c
12 => vfs_write+0xd4/0x214
13 => sys_write+0x4c/0xa0
14 => __sys_trace_return+0x0/0x10
15 lxterminal-840   [000] d...  8688.516208: sched_wakeup: comm=kworker/u8:0 pid=1128 prio=120 target_cpu=000
16 lxterminal-840   [000] d...  8688.516497: sched_switch: prev_comm=lxterminal prev_pid=840 prev_prio=120 prev_state=D ==> next_comm=Xorg next_pid=552 next_prio=120
17 Xorg-552   [000] d...  8688.516757: sched_switch: prev_comm=Xorg prev_pid=552 prev_prio=120 prev_state=D ==> next_comm=lxterminal next_pid=840 next_prio=120
18 lxterminal-840   [000] d...  8688.516900: sched_switch: prev_comm=lxterminal prev_pid=840 prev_prio=120 prev_state=D ==> next_comm=kworker/u8:0 next_pid=1128 next_prio=120

워크를 워크큐에 큐잉하면 워커 스레드를 깨웁니다. 이때 함수 호출 흐름을 볼 수 있습니다.

다음 단계로 워커 스레드를 깨우면 스케줄러가 어떤 흐름으로 워커 스레드를 스케줄링 하는 과정을 볼 수 있습니다. 

    이 경우 wake_up_process() 함수를 호출합니다.

이제 함수 호출 순서 기준으로 ftrace 메시지를 분석하겠습니다. 1~14 번째 줄 메시지를 보겠습니다.
1 lxterminal-840 [000] d... 8688.516171: ttwu_do_wakeup+0x10/0x1a4 <-ttwu_do_activate+0x80/0x84
2 lxterminal-840 [000] d... 8688.516204: <stack trace>
3  => wake_up_process+0x20/0x24
4 => insert_work+0x8c/0xd4
5 => __queue_work+0x1ac/0x51c
6 => queue_work_on+0xa0/0xa4
7 => tty_flip_buffer_push+0x3c/0x40
8 => pty_write+0x84/0x88
9 => n_tty_write+0x368/0x458
10 => tty_write+0x1c8/0x2e4
11 => __vfs_write+0x3c/0x13c
12 => vfs_write+0xd4/0x214
13 => sys_write+0x4c/0xa0
14 => __sys_trace_return+0x0/0x10

함수 호출 흐름은 14 번째 줄 로그에서 1번 째 줄 로그 방향입니다. 이해를 쉽게 하기 위해 함수 호출 방향 기준으로 로그를 설명하겠습니다. 

14~8 번째 줄 메시지는 유저 공간에서 write() 시스템 콜 함수를 실행했다는 사실을 알 수 있습니다. 커널 공간에서 write() 시스템 콜 핸들러인 sys_write() 함수가 호출됩니다. 파일 객체에 등록된 tty 드라이버 write() 함수를 호출해서 tty 버퍼 처리를 수행하는 동작입니다  

다음 7~3 번째 줄 메시지를 보겠습니다.
3  => wake_up_process+0x20/0x24
4 => insert_work+0x8c/0xd4
5 => __queue_work+0x1ac/0x51c
6 => queue_work_on+0xa0/0xa4
7 => tty_flip_buffer_push+0x3c/0x40

워크를 워크큐에 큐잉하는 함수 흐름입니다. 
워크를 워크큐 연결 리스트에 등록한 다음 워크를 실행할 워커 스레드를 깨우는 동작입니다.


워커 스레드를 프로세스와 같은 개념으로 설명을 드리고 있는데 용어를 잠깐 정리합시다.

워커 스레드는 프로세스 종류 중 하나입니다.
워커 스레드를 프로세스 관점으로 설명을 하면, 워커 스레드는 워크를 관리하기 위해 커널 공간에서만 실행하는 프로세스입니다.

워크와 워크큐에 세부 동작은 워크큐 챕터를 참고하세요.


프로세스를 깨우는 함수는 wake_up_process() 함수이라는 사실을 기억합시다.

wake_up_process() 함수 코드를 잠깐 보겠습니다.
[https://github.com/raspberrypi/linux/blob/rpi-4.19.y/kernel/sched/core.c]
1 int wake_up_process(struct task_struct *p)
2 {
3 return try_to_wake_up(p, TASK_NORMAL, 0);
4 }

wake_up_process() 함수는 태스크 디스크립터인 p인자를 try_to_wake_up() 함수 1 번째 인자로 전달합니다. p 인자는 깨우려고 하는 프로세스 태스크 디스크립터 주소입니다. 

다음은 프로세스를 깨운 실제 언제 스케줄링이 되는지 살펴보겠습니다.

위에서 살펴본 로그로 kworker/u8:0(pid=1128) 프로세스를 깨웠습니다. 그럼 바로 스케줄러가 kworker/u8:0(pid=1128) 프로세스를 실행할까요? 그렇지는 않습니다. 

    스케줄러는 스케줄러 런큐에 큐잉된 프로세스를 점검합니다. 
    이 과정에서 프로세스 별 우선순위를 계산해서 가장 먼저 실행할 프로세스를 선택합니다.

다음 로그를 이어서 분석해보겠습니다.
prev_comm=lxterminal prev_pid=840 prev_prio=120 prev_state=D ==> next_comm=Xorg next_pid=552 next_prio=120
prev_comm=Xorg prev_pid=552 prev_prio=120 prev_state=D ==> next_comm=lxterminal next_pid=840 next_prio=120
prev_comm=lxterminal prev_pid=840 prev_prio=120 prev_state=D ==> next_comm=kworker/u8:0 next_pid=1128 next_prio=120

kworker/u8:0(pid=1128) 프로세스보다 Xorg(pid=552) 프로세스와 lxterminal(pid=840) 프로세스 우선순위가 더 높다고 판단한 것입니다. 그래서 스케줄러는 Xorg(pid=552) 프로세스와 lxterminal(pid=840) 프로세스를 먼저 실행하고 난 다음 kworker/u8:0(pid=1128) 프로세스를 실행한 것입니다.

프로세스를 깨운다는 용어의 의미는 해당 프로세스를 실행시켜 달라고 스케줄러에게 요청하는 동작입니다. 이때 스케줄러는 다음과 같은 순서로 처리를 합니다. 
런큐에 이미 큐잉된 다른 프로세스들과 우선순위를 비교한다.
스케줄러는 우선순위에 따라 다음에 실행할 프로세스를 결정한다. 

만약 실행 요청을 한 프로세스가 이미 큐잉된 다른 프로세스들 보다 우선순위가 높으면 스케줄러는 실행 요청한 프로세스를 바로 실행을 시킬 것입니다. 


"혹시 궁금한 점이 있으면 댓글로 질문 남겨주세요. 아는 한 성실히 답변 올려드리겠습니다!" 

Thanks,
Austin Kim(austindh.kim@gmail.com)

Reference(프로세스 스케줄링)

스케줄링 소개
프로세스 상태 관리
   어떤 함수가 프로세스 상태를 바꿀까?
스케줄러 클래스
런큐
CFS 스케줄러
   CFS 관련 세부 함수 분석  
선점 스케줄링(Preemptive Scheduling)   
프로세스는 어떻게 깨울까?
스케줄링 핵심 schedule() 함수 분석
컨택스트 스위칭
스케줄링 디버깅
   스케줄링 프로파일링
     CPU에 부하를 주는 테스트   
     CPU에 부하를 주지 않는 테스트



핑백

덧글

댓글 입력 영역