- Overview
本文主要分析deadline的loadbalance相关,主要包含如下问题:
a.load balance的方式
b. load balance的时机 - Load balance的方式
方式一:push task
上述代码比较简单,经历如下的步骤:/* * See if the non running -deadline tasks on this rq * can be sent to some other CPU where they can preempt * and start executing. */ static int push_dl_task(struct rq *rq) { struct task_struct *next_task; struct rq *later_rq; int ret = 0; if (!rq->dl.overloaded) return 0; next_task = pick_next_pushable_dl_task(rq);//从rq中选一个task出来,push if (!next_task) return 0; retry: if (WARN_ON(next_task == rq->curr)) //不能直接选curr task return 0; /* * If next_task preempts rq->curr, and rq->curr * can move away, it makes sense to just reschedule * without going further in pushing next_task. */ if (dl_task(rq->curr) && dl_time_before(next_task->dl.deadline, rq->curr->dl.deadline) && rq->curr->nr_cpus_allowed > 1) {//如果next task可以抢占当前curr的,则不做Push resched_curr(rq); return 0; } /* We might release rq lock */ get_task_struct(next_task); /* Will lock the rq it'll find */ later_rq = find_lock_later_rq(next_task, rq);//选择一个合适的rq出来 if (!later_rq) {//如果当前的task找不到合适的新的cpu,则尝试找下一个可以推送的task。 struct task_struct *task; /* * We must check all this again, since * find_lock_later_rq releases rq->lock and it is * then possible that next_task has migrated. */ task = pick_next_pushable_dl_task(rq); if (task == next_task) { /* * The task is still there. We don't try * again, some other CPU will pull it when ready. */ goto out; } if (!task) /* No more tasks */ goto out; put_task_struct(next_task); next_task = task; goto retry; } deactivate_task(rq, next_task, 0);//将当前的task dequeue出来, set_task_cpu(next_task, later_rq->cpu);//设置当前的task新的cpu /* * Update the later_rq clock here, because the clock is used * by the cpufreq_update_util() inside __add_running_bw(). */ update_rq_clock(later_rq); activate_task(later_rq, next_task, ENQUEUE_NOCLOCK);//enqueue入新的rq当中 ret = 1; resched_curr(later_rq);//被选中的rq重新调度 double_unlock_balance(rq, later_rq); out: put_task_struct(next_task); return ret; }
a. 从当前的rq中选择一个可以Push出去的task
如下的code所示,选择deadline靠前的task
如果这个新选出来的task可能有机会抢占正在运行的task,则重新选择。static struct task_struct *pick_next_pushable_dl_task(struct rq *rq) { struct task_struct *p; if (!has_pushable_dl_tasks(rq)) return NULL; p = rb_entry(rq->dl.pushable_dl_tasks_root.rb_leftmost, struct task_struct, pushable_dl_tasks);//选择deadline排在前面的task BUG_ON(rq->cpu != task_cpu(p)); BUG_ON(task_current(rq, p)); BUG_ON(p->nr_cpus_allowed <= 1); BUG_ON(!task_on_rq_queued(p)); BUG_ON(!dl_task(p)); return p; }
b. 为这个task选择合适的cpu
如下code所示,这里又走到之前选核策略上面了。因为选核策略已经兼顾了均衡。
c. 将要push的task 从当前的rq中dequeue出来,并enqueue入新的rq当中。/* Locks the rq it finds */ static struct rq *find_lock_later_rq(struct task_struct *task, struct rq *rq) { struct rq *later_rq = NULL; int tries; int cpu; for (tries = 0; tries < DL_MAX_TRIES; tries++) { cpu = find_later_rq(task); if ((cpu == -1) || (cpu == rq->cpu)) break; later_rq = cpu_rq(cpu); if (later_rq->dl.dl_nr_running && !dl_time_before(task->dl.deadline, later_rq->dl.earliest_dl.curr)) { /* * Target rq has tasks of equal or earlier deadline, * retrying does not release any lock and is unlikely * to yield a different result. */ later_rq = NULL; break; } /* Retry if something changed. */ if (double_lock_balance(rq, later_rq)) { if (unlikely(task_rq(task) != rq || !cpumask_test_cpu(later_rq->cpu, task->cpus_ptr) || task_running(rq, task) || !dl_task(task) || !task_on_rq_queued(task))) { double_unlock_balance(rq, later_rq); later_rq = NULL; break; } } /* * If the rq we found has no -deadline task, or * its earliest one has a later deadline than our * task, the rq is a good one. */ if (!later_rq->dl.dl_nr_running || dl_time_before(task->dl.deadline, later_rq->dl.earliest_dl.curr)) break; /* Otherwise we try again. */ double_unlock_balance(rq, later_rq); later_rq = NULL; } return later_rq; }
方式二:pull task
综上所述:pull task会去将其他cpu中排队的deadline靠前的task拉到当前cpu中执行。分两种情况:static void pull_dl_task(struct rq *this_rq) { int this_cpu = this_rq->cpu, cpu; struct task_struct *p; bool resched = false; struct rq *src_rq; u64 dmin = LONG_MAX; if (likely(!dl_overloaded(this_rq)))//此处是判断系统overloaded,并不是判断当前rq。所以,只有系统发生overloaded才会考虑将其他cpu 上面loading重的task pull过来,以减轻其他cpu loading return; /* * Match the barrier from dl_set_overloaded; this guarantees that if we * see overloaded we must also see the dlo_mask bit. */ smp_rmb(); for_each_cpu(cpu, this_rq->rd->dlo_mask) {//dlo_mask表示,发生overloaded的cpu。所以,此处从overloaded的cpu中选择做均衡。 if (this_cpu == cpu) continue; src_rq = cpu_rq(cpu); /* * It looks racy, abd it is! However, as in sched_rt.c, * we are fine with this. */ if (this_rq->dl.dl_nr_running && dl_time_before(this_rq->dl.earliest_dl.curr, src_rq->dl.earliest_dl.next)) continue; //如果当前cpu中当前task的deadline比src中的task先到,则不做pull。因为拉过来也要排队。所以能够被pull 的rq的条件为:当前rq没有dl task或者有rq的排队task dl比当前rq中正在执行dl靠前。即,主动被其他rq中的dl靠前的Task抢占。 /* Might drop this_rq->lock */ double_lock_balance(this_rq, src_rq); /* * If there are no more pullable tasks on the * rq, we're done with it. */ if (src_rq->dl.dl_nr_running <= 1) goto skip; p = pick_earliest_pushable_dl_task(src_rq, this_cpu);//将src_rq中排队的靠前的task拉过来。 /* * We found a task to be pulled if: * - it preempts our current (if there's one), * - it will preempt the last one we pulled (if any). */ if (p && dl_time_before(p->dl.deadline, dmin) && (!this_rq->dl.dl_nr_running || dl_time_before(p->dl.deadline, this_rq->dl.earliest_dl.curr))) { WARN_ON(p == src_rq->curr); WARN_ON(!task_on_rq_queued(p)); /* * Then we pull iff p has actually an earlier * deadline than the current task of its runqueue. */ if (dl_time_before(p->dl.deadline, src_rq->curr->dl.deadline)) goto skip; resched = true; deactivate_task(src_rq, p, 0); set_task_cpu(p, this_cpu); activate_task(this_rq, p, 0); dmin = p->dl.deadline; //后面只有比较当前dl更小才能被拉过来 /* Is there any other task even earlier? */ } skip: double_unlock_balance(this_rq, src_rq); } if (resched) resched_curr(this_rq); }
1.当前rq中的dl task为空,则将所有overload cpu中dl 最靠前的task拉过来
2.当前rq中的dl task不空,则将所有overload cpu中dl 最靠前且比当前rq中的正在running的task dl靠前的Task拉过来。
cpu overload的判断标准:
所以overloaded判断的条件为:static void update_dl_migration(struct dl_rq *dl_rq) { if (dl_rq->dl_nr_migratory && dl_rq->dl_nr_running > 1) { if (!dl_rq->overloaded) { dl_set_overload(rq_of_dl_rq(dl_rq)); dl_rq->overloaded = 1; } } else if (dl_rq->overloaded) { dl_clear_overload(rq_of_dl_rq(dl_rq)); dl_rq->overloaded = 0; } }
runable的task多于一个并且可以被迁移的Task也多于一个。可以被迁移指的是task 允许的cpu超过1个。因为running的task中有一些只能跑在当前cpu当中。 - Load均衡的时机
a. pick next task时,注册回调函数,在执行:__schedule,rt_mutex_setprio,__sched_setscheduler中进行。
b.pick next task时,请各调度器做一次balance,这时候dl class会尝试pull。所以,从这个地方可以看出,选task时,是从所有runqueue中选最快要到期的。 - Summary
本文主要跟踪了dl的均衡机制,均衡机制主要是在选task的时候,要么进行push,要么进行pull进行,其目标是deadline最先到达的Task优先执行。