Arm Linux Kernel Hacks

rousalome.egloos.com

포토로그 Kernel Crash


통계 위젯 (화이트)

44111
637
415420


[리눅스커널] 스케줄링: 프로세스는 스케줄러 클래스를 어떻게 등록할까? 10. 프로세스 스케줄링

이전 소절에서는 스케줄러 클래스와 5가지 스케줄러 클래스에 대해서 살펴봤습니다. 이제부터 프로세스 입장에서 스케줄러 클래스를 통해 세부 스케줄러 함수를 호출하는 과정에 대해 알아보겠습니다. 

    스케줄러 클래스를 통해 세부 스케줄러 함수를 실행하는 주인공은 누구일까? 

정답은 프로세스입니다. 프로세스는 스케줄러 클래스를 통해 스케줄러 세부 함수를 실행할 수 있습니다. 이를 위해 프로세스에 스케줄러 클래스를 등록해야 합니다. 

프로세스 입장에선 스케줄러 클래스 설정은 크게 2가지 단계로 분류할 수 있습니다.

1단계: 스케줄러 클래스 설정

프로세스는 생성될 때 부모 프로세스로부터 스케줄러 클래스를 함께 물려 받습니다. 이해를 돕기 위해 프로세스가 생성되는 과정에서 호출되는 sched_fork() 함수를 소개합니다.

[https://elixir.bootlin.com/linux/v4.19.30/source/kernel/sched/core.c]
01 int sched_fork(unsigned long clone_flags, struct task_struct *p)
02 {
03 unsigned long flags;
...
04 if (dl_prio(p->prio))
05 return -EAGAIN;
06 else if (rt_prio(p->prio))
07 p->sched_class = &rt_sched_class;
08 else
09 p->sched_class = &fair_sched_class; 

이 함수의 핵심 동작은 다음과 같습니다. 

태스크 디스크립터 prio 필드에 저장된 우선순위에 따라 스케줄러 클래스를 지정한다.

04~09번째 줄 코드를 보겠습니다.
dl_prio() 함수와 rt_prio() 함수는 p->prio 필드에 저장된 우선순위를 보고 데드라인 혹은 RT 클래스 스케줄러 우선순위인지 식별합니다.

rt_prio() 함수 코드를 잠깐 봅시다.

[https://elixir.bootlin.com/linux/v4.19.30/source/include/linux/sched/rt.h]
1 static inline int rt_prio(int prio)
2 {
3 if (unlikely(prio < MAX_RT_PRIO))
4 return 1;
5 return 0;
6 }

MAX_RT_PRIO는 100으로 선언돼 있습니다.
우선순위가 100보다 작을 때는 1, 나머지 경우 0을 반환합니다.

5~12 번째 줄 코드를 보면 p->sched_class란 필드에 프로세스 우선순위에 따라 각각 스케줄러 클래스를 등록합니다.

2단계: 스케줄러 클래스 변경

프로세스가 실행 도중 우선순위 높혀서 실행해야 할 때가 있습니다. 이때 스케줄러 클래스를 바꿔야할 필요가 있습니다.

이 경우 __setscheduler() 함수를 호출해서 스케줄러 클래스를 바꿉니다.

[https://elixir.bootlin.com/linux/v4.19.30/source/kernel/sched/sched.h]
1 static void __setscheduler(struct rq *rq, struct task_struct *p,
2    const struct sched_attr *attr, bool keep_boost)
3 {
4 __setscheduler_params(p, attr);
5
6 p->prio = normal_prio(p);
7 if (keep_boost)
8 p->prio = rt_effective_prio(p, p->prio);
9
10 if (dl_prio(p->prio))
11 p->sched_class = &dl_sched_class;
12 else if (rt_prio(p->prio))
13 p->sched_class = &rt_sched_class;
14 else
15 p->sched_class = &fair_sched_class;
16 }

이 함수는 태스크 디스크립터 prio 필드에 저장된 우선순위에 따라 스케줄러 클래스를 지정하는 역할을 수행합니다.

10~15 번째 줄 코드는 프로세스 우선순위에 따라 p->sched_class 필드에 각각 스케줄러 클래스를 등록합니다.

정리하면 모든 프로세스의 태스크 디스크립터에는 스케줄러 클래스 정보가 있고 프로세스 우선순위에 따라 스케줄러 클래스를 동적으로 바꿀 수 있는 것입니다.

스케줄러 클래스 자료 구조를 태스크 디스크립터에서 확인하기
마지막으로 태스크 디스크립터에서 스케줄러 클래스를 확인하는 디버깅 시간을 갖겠습니다.

다음 태스크 디스크립터의 주인공은 라즈베리파이에서 92번 인터럽트 후반부 처리를 담당하는 IRQ 스레드인 "irq/92-mmc1" 프로세스입니다.

1  (struct task_struct *) (struct task_struct*)0xB01E8740 
2    (long int) state = 1  
3    (void *) stack = 0xB0260000,
...
4    (int) prio = 49  
5    (int) static_prio = 120  
6    (int) normal_prio = 49  
7    (unsigned int) rt_priority = 50  
...
8    (struct sched_class *) sched_class = 0x80802698 = rt_sched_class -> (
9       (struct sched_class *) next = 0x80802608 = fair_sched_class,
10      (void (*)()) enqueue_task = 0x80159258 = enqueue_task_rt,
11      (void (*)()) dequeue_task = 0x80158CFC = dequeue_task_rt,
12      (void (*)()) yield_task = 0x80156D94 = yield_task_rt,
...
13      (void (*)()) switched_to = 0x80157E18 = switched_to_rt,
14      (void (*)()) prio_changed = 0x80157D74 = prio_changed_rt,
15      (unsigned int (*)()) get_rr_interval = 0x80156DB8 = get_rr_interval_rt,
16      (void (*)()) update_curr = 0x80158964 = update_curr_rt,

8 번째 줄 필드를 보면 struct task_struct 필드 중 하나인 sched_class는 rt_sched_class 전역 변수 주소를 가리키고 있습니다. 이는 "irq/92-mmc1" 프로세스가 RT 클래스 스케줄러를 등록했다는 의미입니다.

다음에 살펴볼 태스크 디스크립터는 “kworker/0:0H” 프로세스입니다. 프로세스 이름으로 워커 스레드임을 알 수 있습니다.

1 (struct task_struct *) (struct task_struct*)0xB1619D00 
2   (long int) state = 1 = 0x1,
3   (void *) stack = 0xB1634000 = ,
4   (atomic_t) usage = ((int) counter = 2 = 0x2),
5   (unsigned int) flags = 0x04208060,
...
6   (int) on_rq = 0 
7   (int) prio = 100 
8   (int) static_prio = 100 
9   (int) normal_prio = 100 
10  (unsigned int) rt_priority = 0 = 0x0,
11  (struct sched_class *) sched_class = 0x80802608 = fair_sched_class -> (
12     (struct sched_class *) next = 0x80802528 = idle_sched_class,
13     (void (*)()) enqueue_task = 0x80150B0C = enqueue_task_fair,
14     (void (*)()) dequeue_task = 0x8014F310 = dequeue_task_fair,
15     (void (*)()) yield_task = 0x8014F248 = yield_task_fair,
...
16     (void (*)()) switched_to = 0x80151C88 = switched_to_fair,
17     (void (*)()) prio_changed = 0x80151C3C = prio_changed_fair,
18     (unsigned int (*)()) get_rr_interval = 0x8014C5D4 = get_rr_interval_fair,
19     (void (*)()) update_curr = 0x8014DB48 = update_curr_fair,

11 번째 줄을 보면 struct task_struct 필드 중 하나인 sched_class는 fair_sched_class 전역 변수 주소를 가리키고 있습니다. 이는 “kworker/0:0H” 프로세스 프로세스가 스케줄러 클래스로 CFS 클래스 스케줄러를 등록했다는 의미입니다.

다음 소절에서는 스케줄러 클래스를 이용해서 프로세스가 스케줄러 클래스 함수를 호출하는 과정을 소개합니다.


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

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

Reference(프로세스 스케줄링)

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

# Reference: For more information on 'Linux Kernel';

디버깅을 통해 배우는 리눅스 커널의 구조와 원리. 1

디버깅을 통해 배우는 리눅스 커널의 구조와 원리. 2



 


핑백

덧글

댓글 입력 영역