/* file: kernel/sched/fair.c */
static int load_balance(int this_cpu, struct rq *this_rq,
struct sched_domain *sd, enum cpu_idle_type idle,
int *continue_balancing) {
int ld_moved, cur_ld_moved, active_balance = 0;
struct sched_group *group;
struct rq *busiest;
struct cpumask *cpus = this_cpu_cpumask_var_ptr(load_balance_mask);
/* lb_env 用来封装负载均衡的上下文信息 */
struct lb_env env = {
.sd = sd,
.dst_cpu = this_cpu,
.dst_rq = this_rq,
.dst_grpmask = sched_group_span(sd->groups),
.idle = idle,
.loop_break = sched_nr_migrate_break,
.cpus = cpus,
.fbq_type = all,
.tasks = LIST_HEAD_INIT(env.tasks),
};
/* 1.
* 判断是否需要在当前调度域的调度组之间做负载均衡
*
判断的逻辑是当前CPU(即第一个参数:this_cpu)是否空闲并需要从其它地方拉一点任务过来
*/
if (!should_we_balance(&env)) {
/* 如果不需要做负载均衡,则退出并将continue_balancing置0,
* 该变量在上层函数中会用到,用以判断要不要继续遍历上层调度域 */
, *continue_balancing = 0;
goto out_balanced;
}
/* 找到当前调度域中最繁忙的调度组,主要思路是对比各个调度组的负载、算力以及CPU利用率,以此来确定哪个调度组最繁忙
*/
group = find_busiest_group(&env);
if (!group) {
goto out_balanced;
}
/* 从最繁忙的调度组中找出最繁忙的运行队列,从该队列中拉取任务到当前CPU以平衡负载
*/
busiest = find_busiest_queue(&env, group);
if (!busiest) {
goto out_balanced;
}
/* 设置负载均衡的源CPU及源队列 */
env.src_cpu = busiest->cpu;
env.src_rq = busiest;
/* 将从源队列拉取任务 */
cur_ld_moved = detach_tasks(&env);
if (cur_ld_moved) {
/* 将拉取出来的任务放置到目标队列上,目标队列即当前CPU的运行队列 */
attach_tasks(&env);
ld_moved += cur_ld_moved;
}
/* 删除了大量的代码 */
}
这里我们删除了绝大多数处理细节的代码,仅保留了最核心的逻辑,调度器做负载均衡的目的是为了提高CPU的总体利用率,因此在迁移任务时需要考虑各种因素,避免引入额外的性能开销。上面代码中的每一步都包含了非常多的细节,我们在这里最关心总体逻辑,不再一一深入。例如函数 detach_tasks
在选取目标任务时,就会判断任务是否可以被迁移到其它CPU, 一个场景是如果任务在其当前的CPU上的缓存还是热(cache-hot)的,那么将其迁移到其它CPU便会带来额外的性能开销,这就违背了做负载均衡的最初动机,因此调度器就不会对这类任务做迁移。
/* file: kernel/sched/fair.c */
static void rebalance_domains(struct rq *rq, enum cpu_idle_type idle) {
int continue_balancing = 1;
int cpu = rq->cpu;
int busy = idle != CPU_IDLE && !sched_idle_cpu(cpu);
unsigned long interval;
struct sched_domain *sd;
for_each_domain(cpu, sd) {
/* 调用函数load_balance时会将该变量指针传递进去,如果没有必要继续往上遍历调度域,则会将该变量置0
*/
if (!continue_balancing) {
break;
}
/* 计算出当前调度域负载均衡的时间间隔,这里会考虑CPU是否繁忙以及调度域的时间间隔区间
*/
interval = get_sd_balance_interval(sd, busy);
/* 如果当前时间已经超过负载均衡的时间间隔,则触发负载均衡操作 */
if (time_after_eq(jiffies, sd->last_balance + interval)) {
/* 调用 load_balance 对调度域 sd 进行负载均衡 */
if (load_balance(cpu, rq, sd, idle, &continue_balancing)) {
idle = idle_cpu(cpu) ? CPU_IDLE : CPU_NOT_IDLE;
busy = idle != CPU_IDLE && !sched_idle_cpu(cpu);
}
/* 设置本次负载均衡的时间,也就是最近一次负载均衡的时间 */
sd->last_balance = jiffies;
/* 由于进行了负载均衡,动态计算时间间隔的各种因子可能发生了变化,因此这里重新计算一次
*/
interval = get_sd_balance_interval(sd, busy);
}
}
}
/* file: kernel/sched/fair.c */
#define for_each_domain(cpu, __sd) \
for (__sd = rcu_dereference_check_sched_domain(cpu_rq(cpu)->sd); __sd; \
__sd = __sd->parent)