// 尋找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所在group的負載大於最忙group的負載,則返回
// 2.4 計算sched domain的平均負載
// 公式:(SCHED_LOAD_SCALE * sds.total_load) / sds.total_pwr
// 2.4.1 如果this_group的負載大於等於平均負載,則返回
// 2.5 this_cpu所在group的負載閾值超過了最忙group的負載閾值,則返回
// this_cpu所在group的負載閾值計算公式:sd->imbalance_pct * sds.this_load
// 2.6 運行到此處,說明存在失衡,計算失衡的負載量(即需要移動的負載數)
// 2.7 返回最忙的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所在group的負載大於最忙group的負載,則返回
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所在group負載大於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 group->group_load, group的歷史負載
// 1.3.2 group->sum_nr_running, group中進程總數
// 1.3.3 group->sum_weighted_load, group的當前負載
// 2.更新group的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.計算group的平均負載
// 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);
}
//參考 負載均衡(二)中的論文