ARM Linux Kernel Hacks

rousalome.egloos.com

포토로그 Kernel Crash


통계 위젯 (화이트)

197239
1625
172594


[라즈베리파이] 프로세스 - 주기적으로 스케줄링 설정 요청(타이머 인터럽트) 4. 프로세스(Process) 관리

리눅스 커널 개발자들은 언제 레이스 컨디션이 발생할까 걱정을 많이 합니다.
그래서 임계 영역을 어느 코드 구간으로 설정하지 고민합니다.

레이스 컨디션은 언제 어디서나 발생할 수 있지만, 이 걱정을 하기 전에 리눅스 커널에서 스케줄링은 언제 어떻게 수행하는지 점검할 필요가 있습니다.

이번에는 스케줄링을 언제 요청하는지 알아봅시다.
1 > 타이머 인터럽트 발생하여 실행 중인 프로세스가 동작을 멈춤
1 > 인터럽트 벡터(el1_irq, el0_irq)가 실행한 후 타이머 인터럽트 핸들러가 실행
1 > 타이머 인터럽트 핸들러가 실행한 후 schedule_tick() 함수를 실행

local_irq_disable() 함수를 호출하지 않은 이상 인터럽트는 언제든 발생해서 실행 중인 코드를 멈출 수 있습니다.
실행 중인 코드를 멈추고 다시 스케줄링 요청 설정을 한다는 이야기입니다.

먼저 scheduler_tick() 이란 함수를 분석합시다.
[https://elixir.bootlin.com/linux/v4.14.49/source/kernel/sched/core.c#L3003]
1 void scheduler_tick(void)
2 {
3 int cpu = smp_processor_id();
4 struct rq *rq = cpu_rq(cpu);
5 struct task_struct *curr = rq->curr;
6 struct rq_flags rf;
7
8 sched_clock_tick();
9
10 rq_lock(rq, &rf);
11
12 update_rq_clock(rq);
13 curr->sched_class->task_tick(rq, curr, 0);
14 cpu_load_update_active(rq);

위 함수에서 13번째 줄 코드를 봅시다.
13 curr->sched_class->task_tick(rq, curr, 0);

실행 중인 프로세스 태스크 디스크립터를 통해 스케줄 클래스 함수를 호출하는 동작입니다.

Trace32로 "curr->sched_class->task_tick" 구조체에서 task_tick이란 멤버가 어떤 함수를 기르키는지 확인해 보겠습니다. 
  (struct task_struct *) (struct task_struct*)0xFFFFFFC5E9DB5580 = 0xFFFFFFC5E9DB5580 = 
    (struct thread_info) thread_info = ((long unsigned int) flags = 2 = 0x2, (mm_segment_t) addr_lim
    (long int) state = 0 = 0x0,
    (void *) stack = 0xFFFFFFC5E9D60000 = __efistub__end+0x2E0F944000,
    (atomic_t) usage = ((int) counter = 2 = 0x2),
    (unsigned int) flags = 4194368 = 0x00400040,
    (unsigned int) ptrace = 0 = 0x0,
    (struct llist_node) wake_entry = ((struct llist_node *) next = 0x0 = ),
    (int) on_cpu = 1 = 0x1,
    (unsigned int) cpu = 2 = 0x2,
    (unsigned int) wakee_flips = 0 = 0x0,
    (long unsigned int) wakee_flip_decay_ts = 0 = 0x0,
    (struct task_struct *) last_wakee = 0x0 = ,
    (int) wake_cpu = 2 = 0x2,
    (int) on_rq = 1 = 0x1,
    (int) prio = 120 = 0x78,
    (int) static_prio = 120 = 0x78,
    (int) normal_prio = 120 = 0x78,
    (unsigned int) rt_priority = 0 = 0x0,
    (struct sched_class *) sched_class = 0xFFFFFF97D7F09A28 = fair_sched_class -> (
      (struct sched_class *) next = 0xFFFFFF97D7F09960 = idle_sched_class,
      (void (*)()) enqueue_task = 0xFFFFFF97D6AFC4A8 = enqueue_task_fair,
      (void (*)()) dequeue_task = 0xFFFFFF97D6AF4530 = dequeue_task_fair,
      (void (*)()) yield_task = 0xFFFFFF97D6AF4368 = yield_task_fair,
      (bool (*)()) yield_to_task = 0xFFFFFF97D6AF6D7C = yield_to_task_fair,
      (void (*)()) check_preempt_curr = 0xFFFFFF97D6AF4198 = check_preempt_wakeup,
      (struct task_struct * (*)()) pick_next_task = 0xFFFFFF97D6B00C4C = pick_next_task_fair,
      (void (*)()) put_prev_task = 0xFFFFFF97D6AF6D38 = put_prev_task_fair,
      (int (*)()) select_task_rq = 0xFFFFFF97D6AF9B0C = select_task_rq_fair,
      (void (*)()) migrate_task_rq = 0xFFFFFF97D6AF74DC = migrate_task_rq_fair,
      (void (*)()) task_woken = 0x0 = ,
      (void (*)()) set_cpus_allowed = 0xFFFFFF97D6AE3C9C = set_cpus_allowed_common,
      (void (*)()) rq_online = 0xFFFFFF97D6AF3DE0 = rq_online_fair,
      (void (*)()) rq_offline = 0xFFFFFF97D6AF3E28 = rq_offline_fair,
      (void (*)()) set_curr_task = 0xFFFFFF97D6AF2FCC = set_curr_task_fair,
      (void (*)()) task_tick = 0xFFFFFF97D6AFB49C = task_tick_fair,
      (void (*)()) task_fork = 0xFFFFFF97D6AF43F4 = task_fork_fair,
      (void (*)()) task_dead = 0xFFFFFF97D6AF7470 = task_dead_fair,
      (void (*)()) switched_from = 0xFFFFFF97D6AF2094 = switched_from_fair,
      (void (*)()) switched_to = 0xFFFFFF97D6AF3BC4 = switched_to_fair,
      (void (*)()) prio_changed = 0xFFFFFF97D6AF0390 = prio_changed_fair,
      (unsigned int (*)()) get_rr_interval = 0xFFFFFF97D6AEDD44 = get_rr_interval_fair,
      (void (*)()) update_curr = 0xFFFFFF97D6AF4090 = update_curr_fair,
      (void (*)()) task_change_group = 0xFFFFFF97D6AF3C6C = task_change_group_fair,
      (void (*)()) fixup_walt_sched_stats = 0xFFFFFF97D6AED3D0 = walt_fixup_sched_stats_fair),

위 디버깅 정보에서 task_tick() 이란 함수 포인터는 task_tick_fair() 함수를 지정하고 있습니다.
이 프로세스는 sched_class로 fair_sched_class Fair 스케줄 클래스를 지정하고 있습니다.

task_tick_fair() 함수 코드를 보겠습니다.
[https://elixir.bootlin.com/linux/v4.14.49/source/kernel/sched/fair.c#L9044]
1 static void task_tick_fair(struct rq *rq, struct task_struct *curr, int queued)
2 {
3 struct cfs_rq *cfs_rq;
4 struct sched_entity *se = &curr->se;
5
6 for_each_sched_entity(se) {
7 cfs_rq = cfs_rq_of(se);
8 entity_tick(cfs_rq, se, queued);
9 }
10
...
}

10번째 줄 코드를 보면 entity_tick() 함수를 호출합니다.
각각 스케줄링 엔티디를 처리하는 겁니다.

entity_tick() 함수 구현부를 봅시다.
[https://elixir.bootlin.com/linux/v4.14.49/source/kernel/sched/fair.c#L3990]
1 static void
2 entity_tick(struct cfs_rq *cfs_rq, struct sched_entity *curr, int queued)
3 {
4 update_curr(cfs_rq);
5
6 update_load_avg(curr, UPDATE_TG);
7 update_cfs_shares(curr);
8
9 #ifdef CONFIG_SCHED_HRTICK
10 if (queued) {
11 resched_curr(rq_of(cfs_rq));
12 return;
13 }

11번째 줄 코드를 보면 스케줄링 요청을 설정하는 동작을 수행하는 resched_curr() 함수를 호출합니다.

resched_curr() 함수 코드를 봅시다.
[https://elixir.bootlin.com/linux/v4.14.49/source/kernel/sched/core.c#L479]
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.preempt_count 값이 TIF_NEED_RESCHED 인지 점검합니다.
[https://elixir.bootlin.com/linux/v4.14.49/source/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));
}

struct thread_info.preempt_count 값이 TIF_NEED_RESCHED 이면 중복해서 스케줄링 요청으로 하는 것이니 9번째 줄 return; 코드를 실행해서 함수를 바로 빠져 나옵니다.
[https://elixir.bootlin.com/linux/v4.14.49/source/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 매크로는 ARM64 및 ARM32 아키텍처 모두 1입니다.
[https://elixir.bootlin.com/linux/v4.14.49/source/arch/arm64/include/asm/thread_info.h#L80]
#define TIF_NEED_RESCHED 1

[https://elixir.bootlin.com/linux/v4.14.49/source/arch/arm/include/asm/thread_info.h]
#define TIF_NEED_RESCHED 1

다시 정리합시다.
 
만약 bash란 프로세스가 kmalloc() 이란 함수를 호출해서 메모리 할당 중에 타이머 인터럽트가 발생했다고 가정합시다.
bash란 프로세스의 struct thread_info->preempt_count가 0x0입니다.

bash 프로세스 실행 중...
1> kmalloc() 함수 실행 중
2> 타이머 인터럽트 발생
3> 다음 함수 흐름으로 struct thread_info.preempt_count를 TIF_NEED_RESCHED로 설정
     : scheduler_tick() -> task_tick_fair() -> entity_tick() -> resched_curr()
4> 타이머 인터럽트 처리를 마치고 다시 kmalloc() 함수 실행

반복하지만 위 코드 흐름에서 스케줄링이 되는 것이 아니라 스케줄링 요청을 한다는 겁니다. (헷갈리면 안됩니다.)

이렇게 struct thread_info.preempt_count를 TIF_NEED_RESCHED로 설정하면 언제 스케줄링 될까요?
A> wake_up_process(), wake_up_state() 혹은 default_wake_function() 함수가 실행할 때... check_preempt_curr() 함수에서 struct thread_info.preempt_count이 TIF_NEED_RESCHED이면 스케줄링 수행을 결정합니다.

B> 인터럽트 발생 시... 
struct thread_info.preempt_count이 TIF_NEED_RESCHED이면 스케줄링 수행을 결정합니다.

이 코드는 언제 실행하는지 다음 시간에 분석하겠습니다.

덧글

댓글 입력 영역