Arm Linux Kernel Hacks

rousalome.egloos.com

포토로그 Kernel Crash


통계 위젯 (화이트)

43111
637
415419


[리눅스커널] 스케줄링: 깨우는 프로세스를 런큐에 Enqueue하는 동작 10. 프로세스 스케줄링

프로세스를 깨우는 핵심 동작은 ttwu_do_activate() 함수에서 실행합니다.
소스 코드를 보면서 세부 동작을 살펴볼까요? 
[https://github.com/raspberrypi/linux/blob/rpi-4.19.y/kernel/sched/core.c]
1 static void
2 ttwu_do_activate(struct rq *rq, struct task_struct *p, int wake_flags,
3  struct rq_flags *rf)
4 {
5 int en_flags = ENQUEUE_WAKEUP | ENQUEUE_NOCLOCK;
...
6 ttwu_activate(rq, p, en_flags);
7 ttwu_do_wakeup(rq, p, wake_flags, rf);
8 }

ttwu_do_activate() 함수에서 호출하는 ttwu_activate() 함수와 ttwu_activate() 함수는 각각 다음과 같은 역할을 수행합니다.
ttwu_activate(): 깨울 프로세스를 런큐에 Enqueue
ttuw_do_wakeup(): 현재 CPU를 점유하면서 실행 중인 프로세스 struct thread_info 구조체 flags 필드를 TIF_NEED_RESCHED 로 변경

이어서 ttwu_activate() 함수 코드를 분석합시다.
[https://github.com/raspberrypi/linux/blob/rpi-4.19.y/kernel/sched/core.c]
1 static inline void ttwu_activate(struct rq *rq, struct task_struct *p, int en_flags)
2 {
3 activate_task(rq, p, en_flags);
4 p->on_rq = TASK_ON_RQ_QUEUED;
5
6 /* If a worker is waking up, notify the workqueue: */
7 if (p->flags & PF_WQ_WORKER)
8 wq_worker_waking_up(p, cpu_of(rq));
9 }

3번 째 줄 코드를 보면 activate_task() 함수를 호출합니다.

이어서 activate_task() 함수 코드를 보겠습니다.
[https://github.com/raspberrypi/linux/blob/rpi-4.19.y/kernel/sched/core.c]
01 void activate_task(struct rq *rq, struct task_struct *p, int flags)
02 {
03 if (task_contributes_to_load(p))
04 rq->nr_uninterruptible--;
05
06 enqueue_task(rq, p, flags);
07 }

activate_task() 함수를 실행하면 enqueue_task() 함수를 호출해 런큐에 해당 프로세스를 Enqueue합니다.

다음으로 enqueue_task() 함수를 보겠습니다.
[https://github.com/raspberrypi/linux/blob/rpi-4.19.y/kernel/sched/core.c]
01 static inline void enqueue_task(struct rq *rq, struct task_struct *p, int flags)
02 {
03 if (!(flags & ENQUEUE_NOCLOCK))
04 update_rq_clock(rq);
05
06 if (!(flags & ENQUEUE_RESTORE))
07 sched_info_queued(rq, p);
08
09 p->sched_class->enqueue_task(rq, p, flags);
10 }


17 번째 코드는 스케줄러 클래스를 통해 스케줄러 세부 함수에 접근합니다. 세부 동작 원리는 10.3 스케줄러 클래스 절을 참고합시다.

 
17번째 코드를 보겠습니다.
17 p->sched_class->enqueue_task(rq, p, flags);

 프로세스 태스트 디스크립터 sched_class 필드에 저장된 스케줄러 클래스 메소드인 enqueue_task() 함수 호출합니다.

여기서 한 가지 의문이 생깁니다. 

    17번째 줄 코드에서 enqueue_task 함수 포인터를 실행한다. 구체적으로 어떤 함수를 
    호출할까?

프로세스 종류별로 등록된 스케줄러 클래스에 따라 17번째 줄 코드에서 서로 다른 함수를 호출합니다.
일반 프로세스: enqueue_task_fair() 함수
RT(Real Time) 프로세스: enqueue_task_rt() 함수 

이렇게 커널 코드를 분석하다 만나는 가장 큰 걸림돌이 함수 포인터 코드인 것 같습니다.

이제 ttwu_do_wakeup() 함수 코드를 살펴보겠습니다. 
[https://github.com/raspberrypi/linux/blob/rpi-4.19.y/kernel/sched/core.c]
1 static void ttwu_do_wakeup(struct rq *rq, struct task_struct *p, int wake_flags,
   struct rq_flags *rf)
3 {
4 check_preempt_curr(rq, p, wake_flags);
5 p->state = TASK_RUNNING;
6 trace_sched_wakeup(p);

4 번째 줄 코드부터 보겠습니다.
check_preempt_curr() 함수를 호출해 다음 동작을 실행합니다. 

    current 프로세스에 TIF_NEED_RESCHED 플래그 설정한다.

check_preempt_curr() 함수에서 스케줄러 클래스 조건을 보고 resched_curr() 함수를 호출합니다. resched_curr() 함수를 실행하면 current 프로세스의 struct thread_info 구조체 flags 필드를 TIF_NEED_RESCHED로 설정합니다.

이어서 resched_curr() 함수 코드를 볼까요?
[https://github.com/raspberrypi/linux/blob/rpi-4.19.y/kernel/sched/core.c479]
1 void resched_curr(struct rq *rq)
2 {
3 struct task_struct *curr = rq->curr;
4 int cpu;
5
6 lockdep_assert_held(&rq->lock);
7
8 if (test_tsk_need_resched(curr))
9 return;
10
11 cpu = cpu_of(rq);
12
13 if (cpu == smp_processor_id()) {
14 set_tsk_need_resched(curr);
15 set_preempt_need_resched();
16 return;
17 }

8번째 줄 코드는 현재 실행 중인 프로세스 태스크 디스크립터 주소로 struct thread_info 구조체 flags 필드가 TIF_NEED_RESCHED 인지 점검합니다.
[https://github.com/raspberrypi/linux/blob/rpi-4.19.y/include/linux/sched.h]
static inline int test_tsk_need_resched(struct task_struct *tsk)
{
return unlikely(test_tsk_thread_flag(tsk,TIF_NEED_RESCHED));
}

current_thread_info()->flag가 TIF_NEED_RESCHED 이면 중복해서 스케줄링 요청으로 하는 것이니 9번째 줄 ‘return;’ 코드를 실행해서 함수를 바로 종료합니다.

다음 11번째 줄 코드를 보겠습니다.
11 cpu = cpu_of(rq);

cpu_of() 함수를 호출해 런큐의 CPU번호를 cpu 지역 변수에 저장합니다.

다음 13번째 줄 조건문을 보겠습니다. smp_processor_id() 함수를 호출해 현재 실행 중인 CPU 번호와 런큐 CPU 번호가 같은지 체크합니다. 일반적인 상황에서 두 CPU번호는 같습니다.

다음 14번째 줄에서 호출하는 set_tsk_need_resched() 함수 구현부 코드를 보겠습니다.
[https://github.com/raspberrypi/linux/blob/rpi-4.19.y/include/linux/sched.h]
static inline void set_tsk_need_resched(struct task_struct *tsk)
{
set_tsk_thread_flag(tsk,TIF_NEED_RESCHED);
}

프로세스 struct thread_info 구조체 preempt_count를 TIF_NEED_RESCHED로 설정합니다.

TIF_NEED_RESCHED 매크로는 ARMv7 및 ARMv8 아키텍처에서 모두 1입니다.
[https://github.com/raspberrypi/linux/blob/rpi-4.19.y/arch/arm64/include/asm/thread_info.h#L80]
#define TIF_NEED_RESCHED 1

[https://github.com/raspberrypi/linux/blob/rpi-4.19.y/arch/arm/include/asm/thread_info.h]
#define TIF_NEED_RESCHED 1

현재 CPU에서 실행 중인 프로세스의 current_thread_info()->flag를 TIF_NEED_RESCHED로 설정한다는 것은 뭘 의미할까요?  

    이 프로세스는 선점 스케줄링돼야 할 프로세스이다.

current_thread_info()->flag가 TIF_NEED_RESCHED인 프로세스는 인터럽트나 시스템 콜 핸들링 직후 바로 선점 스케줄링으로 CPU를 비우게 됩니다. 

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

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

Reference(프로세스 스케줄링)

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

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

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

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







핑백

덧글

댓글 입력 영역