// 寻找sched domain中最忙的group // 函数参数: // sd:待查找的sched domain // this_cpu:当前正在对其执行负载均衡的cpu // imbalance:为达到平衡需要移动的权重 // idle:this_cpu当前的状态 // sd_idle: sd空闲状态 // cpus:可作为源cpu的集合 // balance:指示this_cpu是否适合负载均衡 // 返回值: // 如果存在不均衡,返回最忙的group // 否则,如果用户建议power-savings balance,返回最不忙的group, // 通过将其中cpus的进程移动到本group,使其idle // 函数任务: // 1.计算sd的负载信息 // 2.根据统计信息,决定是否进行负载均衡 // 2.1 this_cpu不适合在sd中进行均衡,则返回 // 2.2 没有最忙的group,或者最忙group可运行进程数为0,则返回 // 2.3 this_cpu的负载大于sd的平均负载,则返回 // 2.4 负载没有达到进行均衡的阈值 // 2.4.1 imbalance_pct,进行负载均衡的阈值 // 2.5 存在失衡,计算最忙的group // 2.6 返回最忙的group // 3.如果sd负载没有失衡,计算是否可以通过负载均衡来省电 // 3.1 返回最不忙的group 1.1 static struct sched_group *find_busiest_group(struct sched_domain *sd, int this_cpu, unsigned long *imbalance, enum cpu_idle_type idle, int *sd_idle, const struct cpumask *cpus, int *balance) { struct sd_lb_stats sds; memset(&sds, 0, sizeof(sds)); //计算sd的负载 update_sd_lb_stats(sd, this_cpu, idle, sd_idle, cpus, balance, &sds); //this_cpu不适合在sd中进行均衡,则返回 if (!(*balance)) goto ret; //没有最忙的group,或者最忙group可运行进程数为0,则返回 if (!sds.busiest || sds.busiest_nr_running == 0) goto out_balanced; //this_cpu的负载大于最忙的cpu,则返回 if (sds.this_load >= sds.max_load) goto out_balanced; //sd的平均权重 sds.avg_load = (SCHED_LOAD_SCALE * sds.total_load) / sds.total_pwr; //this_cpu的负载大于sd的平均负载,则返回 if (sds.this_load >= sds.avg_load) goto out_balanced; //imbalance_pct,进行负载均衡的阈值 if (100 * sds.max_load <= sd->imbalance_pct * sds.this_load) goto out_balanced; //存在失衡,计算失衡信息 calculate_imbalance(&sds, this_cpu, imbalance); //返回最忙的group return sds.busiest; out_balanced: //没有明显的失衡,检查是否可以进行通过负载均衡省电 if (check_power_save_busiest_group(&sds, this_cpu, imbalance)) return sds.busiest; ret: *imbalance = 0; return NULL; } // 计算sched domain负载均衡统计信息 // 函数参数: // sd:待计算负载统计信息的sd // this_cpu:当前正在对其执行负载均衡的cpu // idle:this_cpu的idle状态 // sd_idle:sd的idle状态 // cpu:可作为源cpu的掩码 // balance:指示是否应该进行负载均衡 // sds:保存统计信息的变量 // 函数任务: // 1.遍历sched domain中所有的group // 1.1 计算当前group的负载信息 // 1.2 如果this_cpu在当前group,并且当前group已经均衡,则退出 // 1.3 更新sched domain的负载统计 // 1.3.1 sds->total_load统计所有group的负载 // 1.3.2 sds->total_pwr统计所有group的cpu power // 1.4 如果sched domain的子domain设置了SD_PREFER_SIBLING标志 // 1.4.1 说明sched domain的sibling之间移动进程 // 1.4.2 降低本group的group_capacity,之后将所有多余进程移动到其他sibling // 1.5 如果this_cpu属于当前group,更新sched domain中关于this_cpu所在group的记录信息 // 1.6 如果当前group是sched domain中负载最重的group,记录group的负载信息 // 1.6.1 sds->max_load,记录sched domain内负载最重group的负载量 // 1.6.2 sds->busiest,记录sched domain内负载最重group的编号 // 1.7 更新sched domain power saving的信息 // 注: // sched domain下所有sched group组织成环形链表的形式。 1.2 static inline void update_sd_lb_stats(struct sched_domain *sd, int this_cpu, enum cpu_idle_type idle, int *sd_idle, const struct cpumask *cpus, int *balance, struct sd_lb_stats *sds) { struct sched_domain *child = sd->child; struct sched_group *group = sd->groups; struct sg_lb_stats sgs; int load_idx, prefer_sibling = 0; if (child && child->flags & SD_PREFER_SIBLING) prefer_sibling = 1; //初始化power saving的信息 init_sd_power_savings_stats(sd, sds, idle); load_idx = get_sd_load_idx(sd, idle); //遍历sched domain中所有的group do { int local_group; //判断this_cpu是否当前group中 local_group = cpumask_test_cpu(this_cpu, sched_group_cpus(group)); memset(&sgs, 0, sizeof(sgs)); //计算当前group的负载信息 update_sg_lb_stats(sd, group, this_cpu, idle, load_idx, sd_idle, local_group, cpus, balance, &sgs); //this_cpu在本group内,并且group已经均衡,则返回 if (local_group && !(*balance)) return; //更新sched domain的负载统计信息 sds->total_load += sgs.group_load; sds->total_pwr += group->cpu_power; //sched domain中的子sched domain设置SD_PREFER_SIBLING,标识sched domain的sibling之间移动进程 //降低本group的group_capacity,将所有多余进程移动到其他sibling if (prefer_sibling) sgs.group_capacity = min(sgs.group_capacity, 1UL); //this_cpu属于group,更新sched domain中关于this_cpu所在group的记录信息 if (local_group) { //sds->this_load,this_cpu所在group的负载 sds->this_load = sgs.avg_load; //sds->this,this_cpu所在的group sds->this = group; sds->this_nr_running = sgs.sum_nr_running; sds->this_load_per_task = sgs.sum_weighted_load; //当前group的平均负载大于sched domain中已遍历group的最大的负载 //当前group就绪进程的个数大于group的容量,或者group设置了imb标识 } else if (sgs.avg_load > sds->max_load && (sgs.sum_nr_running > sgs.group_capacity || sgs.group_imb)) { //更新sched domain中用于记录具有最大负载group的信息 sds->max_load = sgs.avg_load; sds->busiest = group; sds->busiest_nr_running = sgs.sum_nr_running; sds->busiest_group_capacity = sgs.group_capacity; sds->busiest_load_per_task = sgs.sum_weighted_load; sds->group_imb = sgs.group_imb; } //更新sched domain power saving的信息 update_sd_power_savings_stats(group, sds, local_group, &sgs); //继续遍历下一个group group = group->next; } while (group != sd->groups); } // 计算sched group的负载信息 // 函数参数: // sd,group所在的sched domain // group,当前要计算的group // this_cpu,当前对其进行负载均衡的cpu // idle,this_cpu的idle状态 // load_idx,Load index of sched_domain of this_cpu for load calc // sd_idle,group所在sched domain的idle状态 // local_group,指示当前group是否包含this_cpu // cpus,可选为源cpu的掩码集合 // balance:指示是否应该进行负载均衡 // sgs,收集统计信息的变量 // 函数任务: // 1.遍历group中的候选cpu // 1.1 如果cpu有进程运行,更新sched domain为非idle状态 // 1.2 获取cpu的历史负载load // 1.2.1 如果this_cpu在group内,返回max(cpu->cpu_load[load_idx], rq->load.weight) // 1.2.2 如果this_cpu不在group内,返回min(cpu->cpu_load[load_idx], rq->load.weight) // 1.3 统计group的负载 // 1.3.1 sgs->group_load, group的历史负载 // 1.3.2 sgs->sum_nr_running, group中进程总数 // 1.3.3 sgs->sum_weighted_load, group的当前负载 // 2.更新sched domain的cpu power // 2.1 sched domain的cpu power保存在其sd->groups->cpu_power中(即domain包含的第一个group的cpu_power字段) // 2.2 sd->groups->cpu_power等于子domain的cpu power总和 // 3.更新cpu的平均负载 // 3.1 公式 avg_load = group_load/group->cpu_power // 4.计算group内进程当前平均负载 // 4.1 公式 avg_load_per_task = sgs->sum_weighted_load / sgs->sum_nr_running // 5.如果group内cpu最大、最小负载悬殊,设置标识标识group内不均衡 // 5.1 公式 (max_cpu_load - min_cpu_load) > 2*avg_load_per_task // 6.计算sched group的group_capacity,即能接纳的进程容量 // 注: // cpu power用于表示cpu group的能力,不同层次的cpu group具有不同的计算公式: // cpu domain: cpu_power = SCHED_LOAD_SCALE // physical domain:SCHED_LOAD_SCALE+SCHED_LOAD_SCALE*(cpus_weight(cpumask)-1)/10 // 其中cpus_weight计算物理cpu里的逻辑核个数(超线程) // node domain:在相同node domain下所有physical domain的cpu power的总和 1.3 static inline void update_sg_lb_stats(struct sched_domain *sd, struct sched_group *group, int this_cpu, enum cpu_idle_type idle, int load_idx, int *sd_idle, int local_group, const struct cpumask *cpus, int *balance, struct sg_lb_stats *sgs) { unsigned long load, max_cpu_load, min_cpu_load; int i; unsigned int balance_cpu = -1, first_idle_cpu = 0; unsigned long avg_load_per_task = 0; //this_cpu在当前group if (local_group) balance_cpu = group_first_cpu(group); //最大、最小负载 max_cpu_load = 0; min_cpu_load = ~0UL; //遍历group中的候选cpu for_each_cpu_and(i, sched_group_cpus(group), cpus) { struct rq *rq = cpu_rq(i); //非idle状态,并且有进程 if (*sd_idle && rq->nr_running) *sd_idle = 0; //this_cpu在group内 if (local_group) { //当前cpu为idle,并且为发现的第一个idle cpu if (idle_cpu(i) && !first_idle_cpu) { first_idle_cpu = 1; //balance_cpu记录this_cpu所在group内的第一个idle cpu balance_cpu = i; } //返回cpu i当前负载和load_idx历史负载记录两者最大的 load = target_load(i, load_idx); } else //this_cpu不在group内 { //返回cpu i当前负载和load_idx历史负载记录两者最大的 load = source_load(i, load_idx); //max_cpu_load,min_cpu_load记录最大、最小负载 if (load > max_cpu_load) max_cpu_load = load; if (min_cpu_load > load) min_cpu_load = load; } //计算group的统计量 //load_idx,历史负载统计 sgs->group_load += load; //sched group中进程总数 sgs->sum_nr_running += rq->nr_running; //cpu_rq(cpu)->load.weight,当前tick的负载统计 sgs->sum_weighted_load += weighted_cpuload(i); } //只有当前domain的first idle cpu和first cpu(busiest)合适做load balance //CPU_NEWLY_IDLE类型的load balance将总是被允许 if (idle != CPU_NEWLY_IDLE && local_group && balance_cpu != this_cpu) { //不需要做负载均衡 *balance = 0; return; } //更新group的cpu power update_group_power(sd, this_cpu); //计算group的平均负载 sgs->avg_load = (sgs->group_load * SCHED_LOAD_SCALE) / group->cpu_power; //计算group内进程平均负载 // sgs->sum_nr_running记录group内进程总数 if (sgs->sum_nr_running) avg_load_per_task = sgs->sum_weighted_load / sgs->sum_nr_running; //如果group内最大负载、最小负载悬殊,表示组内不均衡 if ((max_cpu_load - min_cpu_load) > 2*avg_load_per_task) sgs->group_imb = 1; //计算sched group的group_capacity,即能接纳的进程容量 sgs->group_capacity = DIV_ROUND_CLOSEST(group->cpu_power, SCHED_LOAD_SCALE); }
//参考 负载均衡(二)中的论文