티스토리 뷰

6.2.6 태스크 깨우기: try_to_wake_up (ttwu)

잠들어있던 task 가 깨우기에 적절한 실행 상태인지 체크하고 실행할 cpu 를 결정

6-13 kernel/sched/core.c try_to_wake_up

  • 태스크가 이전 실행되던 cpu 에서 wakeup
  • 새로운 cpu 에서 wakeup
try_to_wake_up {
  ttwu_remote 먼저 시도 6-14
  ttwu_queue 6-15
}

6-14 kernel/sched/core.c

task 가 runqueue 에 이미 들어가있는 상태라면, 현재 task 가 선점할 수 있는지 확인후 스케줄링 요청

static int ttwu_remote(struct task_struct *p, int wake_flags)
{
    if (task_on_rq_queued(p)) { // 큐에 있는지 확인
        update_rq_clock(rq);   // rq clock 갱신
        ttwu_do_wakeup(rq, p, wake_flags); // 코드 6-18
    }
}

6-15 kernel/sched/core.c ttwu_queue

잠들어 있던 task 를 지정된 cpu 에서 실행 될 수 있도록 enqueue 한다.

/*
 * Queue remote wakeups on the target CPU and process them
 * using the scheduler IPI. Reduces rq->lock contention/bounces.
 */
// 이 매크로로 TTWU_QUEUE 피쳐를 사용할지 정함
// rq 에 직접 lock 을 잡지 않고, wake up 할 task 를 큐에 넣어둘 수 있는 피쳐
SCHED_FEAT(TTWU_QUEUE, true)

static void ttwu_queue(struct task_struct *p, int cpu)
{
    struct rq *rq = cpu_rq(cpu);

#if defined(CONFIG_SMP)
    // 이전 cpu 와 현재 cpu 가 캐시를 공유하지 않을때만 사용 가능
    // 같은 cpu 를 사용하거나 캐시를 공유하면 activate 를 바로 호출하도록 하는 것 같다.
    if (sched_feat(TTWU_QUEUE) && !cpus_share_cache(smp_processor_id(), cpu)) {
        sched_clock_cpu(cpu); /* sync clocks x-cpu */
        ttwu_queue_remote(p, cpu); // remote cpu 에서 실행되도록 설정 6-16
        return;
    }
#endif

    ttwu_do_activate(rq, p, 0); // 6-16
}
u64 sched_clock_cpu(int cpu)
{
    struct sched_clock_data *scd;
    u64 clock;

    if (sched_clock_stable()) // 이미 안정적이라면 sched_clock 을 반환
        return sched_clock();

    if (unlikely(!sched_clock_running)) // 아직 sched clock 이 돌지 않은 경우
        return 0ull;

    preempt_disable_notrace();
    scd = cpu_sdc(cpu); // per-cpu sched_clock_data

    if (cpu != smp_processor_id())
        clock = sched_clock_remote(scd); // 나와 상대 cpu 의 clock 을 동일하게 변경
    else
        clock = sched_clock_local(scd); // 내 clock 갱신
    preempt_enable_notrace();

    return clock;
}

6-16 kernel/sched/core.c ttwu_queue_remote

remote cpu 에서 task 가 실행될 수 있도록 설정하기

    if (llist_add(&p->wake_entry, &cpu_rq(cpu)->wake_list)) { // remote cpu 의 wake_list 에 추가, 리스트가 비어있었다면 아래코드 실행
        if (!set_nr_if_polling(rq->idle)) 
            smp_send_reschedule(cpu); // 폴링하지 않으니 직접 인터럽트
        else
            // ipi: inter processor interrupt
            trace_sched_wake_idle_without_ipi(cpu);  // 인터럽트 없이 트레이싱
    }

6-17 kernel/sched/core.c ttwu_do_activate

태스크를 실행 가능하도록 만들기. runqueue 에 넣고,
선점 가능하다면 스케줄링 요청 6-18

static void
ttwu_do_activate(struct rq *rq, struct task_struct *p, int wake_flags)
{
#ifdef CONFIG_SMP
    if (p->sched_contributes_to_load)
        rq->nr_uninterruptible--; // 이제 큐로 들어오기때문에 interrupt 가 가능한 상태라는 듯
        // queue 에 들어갈때 감소 시키고, dequeue 할때 증가시킨다 (deactivate_task)
        // 근데 activate_task 에서 또 감소시키는데?????? -> 최신 버전에서는 activate, deactivate 에서 값을 수정하지 않음
#endif

    ttwu_activate(rq, p, ENQUEUE_WAKEUP | ENQUEUE_WAKING);
    ttwu_do_wakeup(rq, p, wake_flags);
}

static inline void ttwu_activate(struct rq *rq, struct task_struct *p, int en_flags)
{
    activate_task(rq, p, en_flags); // 큐에 넣기
    p->on_rq = TASK_ON_RQ_QUEUED; // rq 에 들어감 플래그 설정

    /* if a worker is waking up, notify workqueue */
    #define PF_WQ_WORKER  0x00000020  /* I'm a workqueue worker */
    if (p->flags & PF_WQ_WORKER)
        wq_worker_waking_up(p, cpu_of(rq));
}

void activate_task(struct rq *rq, struct task_struct *p, int flags)
{
    if (task_contributes_to_load(p))
        rq->nr_uninterruptible--; // 이제 큐로 들어오기때문에 interrupt 가 가능한 상태라는 듯

    enqueue_task(rq, p, flags);
}

6-18 kernel/sched/core.c ttwu_do_wakeup

current task 가 선점가능하다면 스케줄링 요청

static void ttwu_do_wakeup(struct rq *rq, struct task_struct *p, int wake_flags)
{
    check_preempt_curr(rq, p, wake_flags); // 6-19 선점 가능하면 TIF_NEED_RESCHED 설정
    p->state = TASK_RUNNING; // enqueue 됐으니

#ifdef CONFIG_SMP
    if (p->sched_class->task_woken) {
        p->sched_class->task_woken(rq, p); // 콜백
    }

    if (rq->idle_stamp) { // fair 스케줄링용, idle 로 있던 시간을 평균으로 갖고 있는다
        ...
    }
#endif
}

6-19 kernel/sched/core.c check_preempt_curr

선점 가능하면 TIF_NEED_RESCHED 설정

#define sched_class_highest (&stop_sched_class)
#define for_each_class(class) \
   for (class = sched_class_highest; class; class = class->next)

extern const struct sched_class stop_sched_class;
extern const struct sched_class dl_sched_class;
extern const struct sched_class rt_sched_class;
extern const struct sched_class fair_sched_class;
extern const struct sched_class idle_sched_class;

void check_preempt_curr(struct rq *rq, struct task_struct *p, int flags)
{
    const struct sched_class *class;

    if (p->sched_class == rq->curr->sched_class) {
        rq->curr->sched_class->check_preempt_curr(rq, p, flags); // rq 의 스케줄러 클래스와 동일하면 사용
    } else {
        for_each_class(class) { // 우선순위가 높은 class 부터 돌게됨
            if (class == rq->curr->sched_class) // task 의 스케줄링이 rq 보다 낮은경우
                break; // 스케줄 요청하지 않음
            if (class == p->sched_class) { // task 의 스케줄링이 rq 보다 높은 경우
                resched_curr(rq); // 스케줄링 플래그 설정 TIF_NEED_RESCHED
                break;
            }
        }
    }

    /*
     * A queue event has occurred, and we're going to schedule.  In
     * this case, we can save a useless back to back clock update.
     */
    // 이미 clock 갱신했으니 뒤에는 스킵 가능하다 플래그 설정
    if (task_on_rq_queued(rq->curr) && test_tsk_need_resched(rq->curr))
        rq_clock_skip_update(rq, true);
}

6-20 kernel/sched/fair.c

fair 의 check preempt_curr 은 check_preempt_wakeup 을 호출, 각 스케줄러에도 정의되어 있음

const struct sched_class fair_sched_class = {
    .check_preempt_curr    = check_preempt_wakeup,

다음 스케줄시 우선적으로 next 가 될 수 있도록 설정

static void check_preempt_wakeup(struct rq *rq, struct task_struct *p, int wake_flags)
{
    struct task_struct *curr = rq->curr;
    struct sched_entity *se = &curr->se, *pse = &p->se;
    struct cfs_rq *cfs_rq = task_cfs_rq(curr);
    int scale = cfs_rq->nr_running >= sched_nr_latency; // enqueue 된 태스크 수가 latency 보다 큰 경우 (너무 많음)
    int next_buddy_marked = 0;

    if (unlikely(se == pse)) // 같은 스케줄 엔티티인 경우
        return;

    if (unlikely(throttled_hierarchy(cfs_rq_of(pse)))) // 요청의 개수를 제한
        return;

    if (sched_feat(NEXT_BUDDY) && scale && !(wake_flags & WF_FORK)) { // 다음 스케줄링때 새로 생성된 태스크를 우선 선점
        set_next_buddy(pse);
        next_buddy_marked = 1;
    }

    if (test_tsk_need_resched(curr)) // 이미 RESCHED 플래그 설정된 경우
        return;

    if (unlikely(curr->policy == SCHED_IDLE) && // 현재 돌고있던건 IDLE 인데
        likely(p->policy != SCHED_IDLE)) // 새로 오는 task 는 IDLE 이 아니면 선점
        goto preempt;

    if (unlikely(p->policy != SCHED_NORMAL) || !sched_feat(WAKEUP_PREEMPTION)) // SCHED 가 IDLE,BATCH 거나 옵션이 꺼져있으면 선점 불가
        return;

    // 양쪽 sched entity 를 부모를 찾아가면서 같은 그룹일때까지 변경
    // vruntime 을 같은 그룹에서 하고싶어서 그런가?
    find_matching_se(&se, &pse); 
    update_curr(cfs_rq_of(se)); // vruntime 등 돌아간 시간을 계산
    BUG_ON(!pse);
    if (wakeup_preempt_entity(se, pse) == 1) { // pse 가 더 돌아야 하는지. 예를들어 이전 task 인 se 가 예상보다 많이 돌았다면 스케줄링 필요
        if (!next_buddy_marked)
            set_next_buddy(pse);
        goto preempt;
    }

    return;

preempt:
    resched_curr(rq); // TIF_NEED_RESCHED 설정

    // last buddy 가 될수있는지 체크. last buddy 는 next buddy 이후에 우선 선점될지 여부
    // idle 스레드인 경우나 deque 되어있을때는 우선적으로 실행할 필요 없음
    if (unlikely(!se->on_rq || curr == rq->idle))
        return;

    // 이전 그룹을 last task 로 등록
    if (sched_feat(LAST_BUDDY) && scale && entity_is_task(se))
        set_last_buddy(se);
}

6-21 kernel/sched/core.c set_task_cpu

cpu 에서 task 가 실행될 수 있도록 설정


void set_task_cpu(struct task_struct *p, unsigned int new_cpu)
{
    if (task_cpu(p) != new_cpu) { // cpu 가 달라진다면
        if (p->sched_class->migrate_task_rq) // 콜백 등록된거 있는지 보고 실행한다음에
            p->sched_class->migrate_task_rq(p);
        p->se.nr_migrations++; // sched entity 에 마이그레이션 수 올려주고
    }

    __set_task_cpu(p, new_cpu); // 6-22
}

6-22 kernel/sched/sched.h __set_task_cpu

static inline void __set_task_cpu(struct task_struct *p, unsigned int cpu)
{
    set_task_rq(p, cpu);
#ifdef CONFIG_SMP
    /*
     * After ->cpu is set up to a new value, task_rq_lock(p, ...) can be
     * successfuly executed on another CPU. We must ensure that updates of
     * per-task data have been completed by this moment.
     */
    smp_wmb(); // smp 에 쓰기연산 순서 보장
    task_thread_info(p)->cpu = cpu; // ti 에 cpu 등록
    p->wake_cpu = cpu; // wake_cpu 등록
#endif
}

task 에 cpu 에 맞는 rq 설정
static inline void set_task_rq(struct task_struct *p, unsigned int cpu)
{
#if defined(CONFIG_FAIR_GROUP_SCHED) || defined(CONFIG_RT_GROUP_SCHED)
    struct task_group *tg = task_group(p);
#endif

#ifdef CONFIG_FAIR_GROUP_SCHED
    set_task_rq_fair(&p->se, p->se.cfs_rq, tg->cfs_rq[cpu]);
    p->se.cfs_rq = tg->cfs_rq[cpu];
    p->se.parent = tg->se[cpu];
#endif

#ifdef CONFIG_RT_GROUP_SCHED
    p->rt.rt_rq  = tg->rt_rq[cpu];
    p->rt.parent = tg->rt_se[cpu];
#endif
}
댓글