一个睡眠中的进程被唤醒后,应该去哪个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);
cpu
为waker
现在执行的cpu;
new_cpu
以及prev_cpu
为wakee
上次执行的cpu;
want_affine
用于表示是否满足亲和条件,在后续判断(如果满足亲和条件,则将进程唤醒到waker
现在执行的cpu上也是一个比较优的选择);
sync
是用来表示waker
与wakee
之间的关系的。
我们认为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
主要用于更新waker
的wakee_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数目;
将waker
和wakee
中wakee_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;
}
waker
的负载以及cpu负载,在waker
即将执行完成的情况下,cpu负载减去进程负载;waker
的cpu负载加上wakee
的进程负载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):
wake_wide
(依据唤醒进程的强度)以及进程可执行的cpu掩码决定满足waker
执行cpu的亲和性;wake_affine
来判断要不要给唤醒wakee
的cpu一点执行进程的机会;find_idlest_cpu
(慢速路径),进行负载均衡的选择;select_idle_sibling
,在wake_affine
挑选出来的target cpu以及上一次执行的cpu共享cache的调度域中,根据负载选择一个cpu进行执行。Copyright© 2013-2020
All Rights Reserved 京ICP备2023019179号-8