进程唤醒时,应该去哪个cpu?

362次阅读  |  发布于3年以前

一个睡眠中的进程被唤醒后,应该去哪个cpu上去执行呢?

为方便称呼,执行唤醒流程的进程是waker,被唤醒进程是wakee。

显而易见,选择cpu是一个非常复杂的问题。

我们从try_to_wake_up-->select_task_rq此条路径出发,简单了解一下睡眠进程被唤醒过程中的选核思路。

select_task_rq函数会选择wakee的调度类中定义的select_task_rq去执行。

这里以CFS中定义的select_task_rq_fair函数为例进行分析。

先看函数开始部分:

static int
 select_task_rq_fair(struct task_struct *p, int prev_cpu, int sd_flag, int wake_flags)
 {
 struct sched_domain *tmp, *sd = NULL;
 int cpu = smp_processor_id();
 int new_cpu = prev_cpu;
 int want_affine = 0;
 int sync = (wake_flags & WF_SYNC) && !(current->flags & PF_EXITING);

cpuwaker现在执行的cpu;

new_cpu以及prev_cpuwakee上次执行的cpu;

want_affine用于表示是否满足亲和条件,在后续判断(如果满足亲和条件,则将进程唤醒到waker现在执行的cpu上也是一个比较优的选择);

sync是用来表示wakerwakee之间的关系的。

我们认为waker与wakee之间有两种关系:sync,以及non-sync。

sync——waker在唤醒wakee的时候知道自己很快进入睡眠状态,故最好不要进行抢占。

non-sync——没有同步关系,唤醒的时候,可以尝试触发一次调度。

在这里,sync本质上是放宽了对waker的cpu的idle的判断条件。

接着分析:

 if (sd_flag & SD_BALANCE_WAKE) {
 record_wakee(p);

 if (sched_energy_enabled()) {
 new_cpu = find_energy_efficient_cpu(p, prev_cpu);
 if (new_cpu >= 0)
 return new_cpu;
 new_cpu = prev_cpu;
 }

 want_affine = !wake_wide(p) && cpumask_test_cpu(cpu, p->cpus_ptr);
 }

进程唤醒可以视为一次主动的BALANCE。

record_wakee主要用于更新wakerwakee_flips,用于后续的判断。

 static void record_wakee(struct task_struct *p)
 {
 /*
  * Only decay a single time; tasks that have less then 1 wakeup per
  * jiffy will not have built up many flips.
  */
 if (time_after(jiffies, current->wakee_flip_decay_ts + HZ)) {
 current->wakee_flips >>= 1;
 current->wakee_flip_decay_ts = jiffies;
 }

 if (current->last_wakee != p) {
 current->last_wakee = p;
 current->wakee_flips++;
 }
 }

task_struct中有三个成员:

wakee_flips_decay_ts表示上次进行衰减的时刻;

last_wakee为上次由它唤醒的进程;

wakee_flips度量由它唤醒进程的数量,wakee_flips一秒之前的计数全部除以2,有点衰减的那味儿了。

sched_energy_enabled分支的部分,与功耗EAS调度器有关,暂不分析。

可以明确的是,find_energy_efficient_cpu用于在开启功耗模型下寻找功耗最低的cpu去执行。

wake_wide函数检测waker/wakee是否符合wake_affine模型。(最终用want_affine表示)

wake_wide支持以及waker所在的cpu可以执行wakee时,want_affine生效。

符合wake_affine模型则优先去waker现在执行以及wakee上次执行的cpus中进行选择(放宽对waker执行cpu的条件,最后从二者中挑选一个target cpu)。

static int wake_wide(struct task_struct *p)
 {
 unsigned int master = current->wakee_flips;
 unsigned int slave = p->wakee_flips;
 int factor = __this_cpu_read(sd_llc_size);

 if (master < slave)
 swap(master, slave);
 if (slave < factor || master < slave * factor)
 return 0;
 return 1;
 }

sd_llc_domain为当前sched_domain中能够共享cache的CPU数目;

wakerwakeewakee_flips中较大的称为master,较小的称为slave

当较小的wakee_flips或者较大的wakee_flips与较小的wakee_flips的比值小于sd_llc_size是可以作为wake_affine生效的一部分判断依据。

继续看select_task_rq_fair函数:

 rcu_read_lock();
 for_each_domain(cpu, tmp) {
 /*
  * If both 'cpu' and 'prev_cpu' are part of this domain,
  * cpu is a valid SD_WAKE_AFFINE target.
  */
 if (want_affine && (tmp->flags & SD_WAKE_AFFINE) &&
     cpumask_test_cpu(prev_cpu, sched_domain_span(tmp))) {
 if (cpu != prev_cpu)
 new_cpu = wake_affine(tmp, p, cpu, prev_cpu, sync);

 sd = NULL; /* Prefer wake_affine over balance flags */
 break;
 }

 if (tmp->flags & sd_flag)
 sd = tmp;
 else if (!want_affine)
 break;
 }

waker执行的cpu调度域逐步向上,进行如下判断:

want_affine成立,并且调度域满足亲和性条件,并且waker的cpu与wakee上次执行cpu在同一调度域中时,sd为NULL。

如果条件均不满足,则sd置为最后满足sd_flag的调度域。

如果满足want_affine,退出。

在满足第1个条件,且waker执行的cpu与wakee上次执行cpu不相等的情况下,执行wake_affine进行选择。

wake_affine本质上是在waker现在执行的cpu以及wakee上次执行的cpu进行对比,决定要不要给waker现在执行的cpu一点机会。

static int wake_affine(struct sched_domain *sd, struct task_struct *p,
        int this_cpu, int prev_cpu, int sync)
 {
 int target = nr_cpumask_bits;

 if (sched_feat(WA_IDLE))
 target = wake_affine_idle(this_cpu, prev_cpu, sync);

 if (sched_feat(WA_WEIGHT) && target == nr_cpumask_bits)
 target = wake_affine_weight(sd, p, this_cpu, prev_cpu, sync);

 schedstat_inc(p->se.statistics.nr_wakeups_affine_attempts);
 if (target == nr_cpumask_bits)
 return prev_cpu;

 schedstat_inc(sd->ttwu_move_affine);
 schedstat_inc(p->se.statistics.nr_wakeups_affine);
 return target;
 }

首先,如果waker执行的cpu(此时waker为idle,中断唤醒wakee)为空闲或者很快进入空闲态或者wakee上次执行的cpu为空闲,则通过wake_affine_idle可以找到一个合适的target cpu。

 static int
 wake_affine_idle(int this_cpu, int prev_cpu, int sync)
 {
 if (available_idle_cpu(this_cpu) && cpus_share_cache(this_cpu, prev_cpu))
 return available_idle_cpu(prev_cpu) ? prev_cpu : this_cpu;

 if (sync && cpu_rq(this_cpu)->nr_running == 1)
 return this_cpu;

 return nr_cpumask_bits;
 }

如果waker的cpu空闲(中断唤醒)并且waker执行的cpu与wakee上次执行的cpu共享cache时:

wakee上次执行的cpu也空闲,那么回到上次执行的cpu,否则是waker的cpu。

否则sync为1(表示waker很快要退出)并且只有一个进程在执行,那么返回waker执行的cpu。

如果在wake_affine_idle中没有找到target cpu,则进入wake_affine_weight中继续寻找。

 static int
 wake_affine_weight(struct sched_domain *sd, struct task_struct *p,
    int this_cpu, int prev_cpu, int sync)
 {
 s64 this_eff_load, prev_eff_load;
 unsigned long task_load;

 this_eff_load = cpu_load(cpu_rq(this_cpu));

 if (sync) {
 unsigned long current_load = task_h_load(current);

 if (current_load > this_eff_load)
 return this_cpu;

 this_eff_load -= current_load;
 }

 task_load = task_h_load(p);

 this_eff_load += task_load;
 if (sched_feat(WA_BIAS))
 this_eff_load *= 100;
 this_eff_load *= capacity_of(prev_cpu);

 prev_eff_load = cpu_load(cpu_rq(prev_cpu));
 prev_eff_load -= task_load;
 if (sched_feat(WA_BIAS))
 prev_eff_load *= 100 + (sd->imbalance_pct - 100) / 2;
 prev_eff_load *= capacity_of(this_cpu);

 if (sync)
 prev_eff_load += 1;

 return this_eff_load < prev_eff_load ? this_cpu : nr_cpumask_bits;
 }
  1. 计算waker的负载以及cpu负载,在waker即将执行完成的情况下,cpu负载减去进程负载;
  2. waker的cpu负载加上wakee的进程负载
  3. wakee的cpu负载减去wakee的进程负载(此处是假设进程从上次执行的cpu迁移到waker执行的cpu)

最终,哪个cpu负载小,最后return哪个cpu。

如果sd不为空,则进入find_idlest_cpu这条慢速路径。

否则,进入select_idle_sibling快速路径。

 if (unlikely(sd)) {
 /* Slow path */
 new_cpu = find_idlest_cpu(sd, p, cpu, prev_cpu, sd_flag);
 } else if (sd_flag & SD_BALANCE_WAKE) { /* XXX always ? */
 /* Fast path */

 new_cpu = select_idle_sibling(p, prev_cpu, new_cpu);

 if (want_affine)
 current->recent_used_cpu = cpu;
 }
 rcu_read_unlock();

 return new_cpu;
 }

find_idlest_cpu为SMP负载均衡的常用函数,即选择负载最小的cpu去执行。

select_idle_sibling在之前挑选出来的target cpu与上一次执行的cpu(有可能taget与上一次执行是一个cpu)共享cache的调度域中,根据负载选择一个最合适的cpu去执行。

总结

cpu的选择,主要有以下关键点(满足SD_BALANCE_WAKE):

Copyright© 2013-2020

All Rights Reserved 京ICP备2023019179号-8