文章目录
- 一、最忙CPU组查找函数`find_busiest_group`
-
- [1. 函数原型和参数](#1. 函数原型和参数)
- [2. 变量初始化](#2. 变量初始化)
- [3. 调度组遍历和负载统计](#3. 调度组遍历和负载统计)
- 4.负载标准化和power调整
- 5.最忙组选择
- 6.检查是否需要平衡
- 7.不平衡量计算
- 8.小不平衡量处理
- 9.不平衡量转化为实际任务数
- 10.特殊空闲状态处理
- 11.完整算法流程
- 二、最忙运行队列查找函数`find_busiest_queue`
-
- [1. 函数原型和目的](#1. 函数原型和目的)
- [2. 变量初始化](#2. 变量初始化)
- [3. 循环遍历组内所有CPU](#3. 循环遍历组内所有CPU)
- [4. 负载计算和比较](#4. 负载计算和比较)
一、最忙CPU组查找函数find_busiest_group
c
static struct sched_group *
find_busiest_group(struct sched_domain *sd, int this_cpu,
unsigned long *imbalance, enum idle_type idle)
{
struct sched_group *busiest = NULL, *this = NULL, *group = sd->groups;
unsigned long max_load, avg_load, total_load, this_load, total_pwr;
max_load = this_load = total_load = total_pwr = 0;
do {
unsigned long load;
int local_group;
int i, nr_cpus = 0;
local_group = cpu_isset(this_cpu, group->cpumask);
avg_load = 0;
for_each_cpu_mask(i, group->cpumask) {
if (local_group)
load = target_load(i);
else
load = source_load(i);
nr_cpus++;
avg_load += load;
}
if (!nr_cpus)
goto nextgroup;
total_load += avg_load;
total_pwr += group->cpu_power;
avg_load = (avg_load * SCHED_LOAD_SCALE) / group->cpu_power;
if (local_group) {
this_load = avg_load;
this = group;
goto nextgroup;
} else if (avg_load > max_load) {
max_load = avg_load;
busiest = group;
}
nextgroup:
group = group->next;
} while (group != sd->groups);
if (!busiest || this_load >= max_load)
goto out_balanced;
avg_load = (SCHED_LOAD_SCALE * total_load) / total_pwr;
if (this_load >= avg_load ||
100*max_load <= sd->imbalance_pct*this_load)
goto out_balanced;
*imbalance = min(max_load - avg_load, avg_load - this_load);
*imbalance = (*imbalance * min(busiest->cpu_power, this->cpu_power))
/ SCHED_LOAD_SCALE;
if (*imbalance < SCHED_LOAD_SCALE - 1) {
unsigned long pwr_now = 0, pwr_move = 0;
unsigned long tmp;
if (max_load - this_load >= SCHED_LOAD_SCALE*2) {
*imbalance = 1;
return busiest;
}
pwr_now += busiest->cpu_power*min(SCHED_LOAD_SCALE, max_load);
pwr_now += this->cpu_power*min(SCHED_LOAD_SCALE, this_load);
pwr_now /= SCHED_LOAD_SCALE;
tmp = SCHED_LOAD_SCALE*SCHED_LOAD_SCALE/busiest->cpu_power;
if (max_load > tmp)
pwr_move += busiest->cpu_power*min(SCHED_LOAD_SCALE,
max_load - tmp);
tmp = SCHED_LOAD_SCALE*SCHED_LOAD_SCALE/this->cpu_power;
if (max_load < tmp)
tmp = max_load;
pwr_move += this->cpu_power*min(SCHED_LOAD_SCALE, this_load + tmp);
pwr_move /= SCHED_LOAD_SCALE;
if (pwr_move < pwr_now + SCHED_LOAD_SCALE / 8)
goto out_balanced;
*imbalance = 1;
return busiest;
}
*imbalance = (*imbalance + 1) / SCHED_LOAD_SCALE;
return busiest;
out_balanced:
if (busiest && (idle == NEWLY_IDLE ||
(idle == SCHED_IDLE && max_load > SCHED_LOAD_SCALE)) ) {
*imbalance = 1;
return busiest;
}
*imbalance = 0;
return NULL;
}
这个函数是Linux内核负载均衡器的核心,负责在调度域中找到最繁忙的CPU组,并计算需要迁移的任务量
1. 函数原型和参数
c
static struct sched_group *
find_busiest_group(struct sched_domain *sd, int this_cpu,
unsigned long *imbalance, enum idle_type idle)
参数:
sd
:当前调度域this_cpu
:当前CPU编号imbalance
:输出参数,需要迁移的负载量idle
:当前CPU的空闲状态
返回值:最忙的调度组,NULL表示无需负载均衡
2. 变量初始化
c
struct sched_group *busiest = NULL, *this = NULL, *group = sd->groups;
unsigned long max_load, avg_load, total_load, this_load, total_pwr;
max_load = this_load = total_load = total_pwr = 0;
关键变量:
busiest
:指向最忙的调度组this
:指向当前CPU所在的调度组max_load
:最忙组的负载this_load
:当前组的负载total_load
:所有组的总负载total_pwr
:所有组的总计算能力
3. 调度组遍历和负载统计
c
do {
unsigned long load;
int local_group;
int i, nr_cpus = 0;
local_group = cpu_isset(this_cpu, group->cpumask);
本地组判断:
c
local_group = cpu_isset(this_cpu, group->cpumask);
- 检查当前CPU是否属于这个调度组
- 本地组和远程组采用不同的负载计算策略
3.1.组内CPU负载累加
c
avg_load = 0;
for_each_cpu_mask(i, group->cpumask) {
if (local_group)
load = target_load(i);
else
load = source_load(i);
nr_cpus++;
avg_load += load;
}
for_each_cpu_mask
宏:
- 遍历调度组中所有的CPU
group->cpumask
:位图,标识该组包含哪些CPUi
:循环变量,表示当前CPU编号
3.2.负载计算函数差异
**target_load()
**
实现
c
static inline unsigned long target_load(int cpu)
{
runqueue_t *rq = cpu_rq(cpu);
unsigned long load_now = rq->nr_running * SCHED_LOAD_SCALE;
return max(rq->cpu_load, load_now);
}
特点:
- 获取的负载值取 平均负载值 和 瞬时负载值的较大值
- 受瞬时负载值影响较大
- 得到的负载值大,迁移阈值高
**source_load()
**
实现
c
static inline unsigned long source_load(int cpu)
{
runqueue_t *rq = cpu_rq(cpu);
unsigned long load_now = rq->nr_running * SCHED_LOAD_SCALE;
return min(rq->cpu_load, load_now);
}
特点:
- 获取的负载值取 平均负载值 和 瞬时负载值的较小值
- 受瞬时负载值影响较小
- 得到的负载值小,迁移阈值低
4.负载标准化和power调整
c
#define SCHED_LOAD_SCALE 128UL /* increase resolution of load */
total_load += avg_load;
total_pwr += group->cpu_power;
/* Adjust by relative CPU power of the group */
avg_load = (avg_load * SCHED_LOAD_SCALE) / group->cpu_power;
变量含义:
total_load
:所有调度组的原始负载总和total_pwr
:所有调度组的计算能力总和group->cpu_power
:该组的相对计算能力
text
标准化负载 = (原始负载 × 缩放因子) / CPU计算能力
将不同性能CPU的负载转换为可比较的标准单位
5.最忙组选择
c
if (local_group) {
this_load = avg_load;
this = group;
goto nextgroup;
} else if (avg_load > max_load) {
max_load = avg_load;
busiest = group;
}
local_group
- 记录本地组的负载到
this_load
avg_load > max_load
- 找到负载最高的远程组作为
busiest
6.检查是否需要平衡
c
if (!busiest || this_load >= max_load)
goto out_balanced;
avg_load = (SCHED_LOAD_SCALE * total_load) / total_pwr;
if (this_load >= avg_load ||
100*max_load <= sd->imbalance_pct*this_load)
goto out_balanced;
条件1:!busiest
c
!busiest // 没有找到最忙的组
含义:只有一个本地调度组
条件2:this_load >= max_load
c
this_load >= max_load // 当前组负载 ≥ 最忙组负载
含义:当前CPU所在组的负载已经不低于最忙组的负载
- 如果当前组已经比最忙组更忙,迁移任务只会让情况更糟
全局平均负载计算
c
avg_load = (SCHED_LOAD_SCALE * total_load) / total_pwr;
计算公式:
text
全局平均负载 = (缩放因子 × 总原始负载) / 总计算能力
计算整个调度域的负载
c
if (this_load >= avg_load ||
100*max_load <= sd->imbalance_pct*this_load)
goto out_balanced;
条件3:this_load >= avg_load
c
this_load >= avg_load // 当前组负载 ≥ 全局平均负载
含义:当前组已经达到或超过全局平均负载水平
- 当前组已经"贡献"了应有的负载份额
条件4:100\*max_load <= sd->imbalance_pct\*this_load
c
100 * max_load <= sd->imbalance_pct * this_load
重新排列更易理解:
c
max_load <= (sd->imbalance_pct / 100) * this_load
实际含义:
text
最忙组负载 ≤ 不平衡百分比 × 当前组负载
典型配置:
c
sd->imbalance_pct = 125 // 25%的不平衡容忍度
计算示例:
text
当前组负载 (this_load): 100
最忙组负载 (max_load): 120
不平衡百分比: 125%
计算: 120 <= (125/100) × 100 = 125
条件成立: 120 <= 125 → 跳转到平衡状态
设计原理 :容忍适度不平衡
- 允许一定程度的负载差异存在
- 避免因微小不平衡触发昂贵的迁移操作
- 在性能和完美平衡间取得权衡
6.1.不执行负载均衡的四种情况:
- 无候选组 :
!busiest
- 说明只有一个本地组
- 当前组更忙 :
this_load >= max_load
- 迁移会使情况更糟
- 当前组已达平均 :
this_load >= avg_load
- 当前组已承担公平份额
- 不平衡在容忍范围内 :
max_load ≤ 125% × this_load
- 差异太小,不值得迁移
7.不平衡量计算
c
*imbalance = min(max_load - avg_load, avg_load - this_load);
/* How much load to actually move to equalise the imbalance */
*imbalance = (*imbalance * min(busiest->cpu_power, this->cpu_power))
/ SCHED_LOAD_SCALE;
这段代码计算了需要迁移的负载量
7.1.基础不平衡量计算
c
*imbalance = min(max_load - avg_load, avg_load - this_load);
计算两个方向的不平衡量,取较小值:
max_load - avg_load
:最忙组超出平均的负载量avg_load - this_load
:当前组低于平均的负载量
7.1.1.为什么取最小值?
防止过度迁移:
确保可行性:
- 迁移量不能超过当前组的接收能力
- 迁移量不能超过最忙组的可释放量
7.2.功率调整计算
c
*imbalance = (*imbalance * min(busiest->cpu_power, this->cpu_power))
/ SCHED_LOAD_SCALE;
计算公式:
text
实际迁移量 = (基础不平衡量 × 最小CPU功率) / 缩放因子
按较弱CPU的能力调整:
- 迁移量受限于两个组中计算能力较低的一方
- 确保迁移后的负载分布考虑实际硬件能力
7.3.在负载均衡流程中的位置
完整决策链:
text
1. 找到最忙组 ✓
2. 检查是否需要平衡 ✓
3. 计算基础不平衡量 ✓
4. 按CPU功率调整 ✓
5. 转换为实际任务数
6. 选择具体任务迁移
8.小不平衡量处理
c
if (*imbalance < SCHED_LOAD_SCALE - 1) {
unsigned long pwr_now = 0, pwr_move = 0;
unsigned long tmp;
if (max_load - this_load >= SCHED_LOAD_SCALE*2) {
*imbalance = 1;
return busiest;
}
pwr_now += busiest->cpu_power*min(SCHED_LOAD_SCALE, max_load);
pwr_now += this->cpu_power*min(SCHED_LOAD_SCALE, this_load);
pwr_now /= SCHED_LOAD_SCALE;
tmp = SCHED_LOAD_SCALE*SCHED_LOAD_SCALE/busiest->cpu_power;
if (max_load > tmp)
pwr_move += busiest->cpu_power*min(SCHED_LOAD_SCALE,
max_load - tmp);
tmp = SCHED_LOAD_SCALE*SCHED_LOAD_SCALE/this->cpu_power;
if (max_load < tmp)
tmp = max_load;
pwr_move += this->cpu_power*min(SCHED_LOAD_SCALE, this_load + tmp);
pwr_move /= SCHED_LOAD_SCALE;
if (pwr_move < pwr_now + SCHED_LOAD_SCALE / 8)
goto out_balanced;
*imbalance = 1;
}
这段代码处理小规模不平衡的特殊情况,进行经济性分析以决定是否值得迁移
8.1.条件判断入口
c
if (*imbalance < SCHED_LOAD_SCALE - 1) {
含义 :当计算出的不平衡量小于127时(SCHED_LOAD_SCALE=128
)
设计意图:
- 不平衡量很小,可能不值得迁移
- 需要进行成本收益分析
- 避免因微小不平衡触发昂贵的迁移操作
8.2.严重不平衡的特殊处理
c
if (max_load - this_load >= SCHED_LOAD_SCALE*2) {
*imbalance = 1;
return busiest;
}
条件分析:
c
max_load - this_load >= 256 // SCHED_LOAD_SCALE*2
含义:最忙组与当前组的负载差异很大(≥256)
特殊处理:
- 即使计算出的
*imbalance
很小 - 也强制迁移至少1个任务(
*imbalance = 1
) - 立即返回最忙组
设计原理:当负载差异很大时,即使精细计算显示迁移量小,也优先解决明显的不平衡
8.3.当前系统吞吐量计算
c
pwr_now += busiest->cpu_power * min(SCHED_LOAD_SCALE, max_load);
pwr_now += this->cpu_power * min(SCHED_LOAD_SCALE, this_load);
pwr_now /= SCHED_LOAD_SCALE;
计算公式:
text
当前吞吐量 = (busiest_power × min(128, max_load) + this_power × min(128, this_load)) / 128
数学原理:
- 每个CPU的贡献受其计算能力和负载限制
min(SCHED_LOAD_SCALE, load)
:负载上限为128- 除以
SCHED_LOAD_SCALE
得到标准化吞吐量
8.4.迁移后吞吐量计算
第一部分:最忙组减少的负载
c
tmp = SCHED_LOAD_SCALE * SCHED_LOAD_SCALE / busiest->cpu_power;
if (max_load > tmp)
pwr_move += busiest->cpu_power * min(SCHED_LOAD_SCALE, max_load - tmp);
计算tmp
:
c
tmp = 128 × 128 / busiest_cpu_power
含义:最忙组减少一个标准负载单位(128)对应的负载量
设计原理:考虑CPU功率差异,高性能CPU减少相同负载需要迁移更多任务
第二部分:当前组增加的负载
c
tmp = SCHED_LOAD_SCALE * SCHED_LOAD_SCALE / this->cpu_power;
if (max_load < tmp)
tmp = max_load;
pwr_move += this->cpu_power * min(SCHED_LOAD_SCALE, this_load + tmp);
计算逻辑:
- 计算当前组增加一个标准负载单位对应的负载量
- 如果
max_load
较小,使用实际值 - 累加到迁移后吞吐量
最终吞吐量计算
c
pwr_move /= SCHED_LOAD_SCALE;
含义:将迁移后的吞吐量标准化。
8.5.经济性决策
c
if (pwr_move < pwr_now + SCHED_LOAD_SCALE / 8)
goto out_balanced;
条件分析:
text
迁移后吞吐量 < 当前吞吐量 + 16 (128/8)
设计原理 :只有迁移能带来至少1/8 CPU的吞吐量提升时才执行迁移
决策逻辑:
- 如果收益太小,放弃迁移
- 否则,设置最小迁移量(
*imbalance = 1
)
9.不平衡量转化为实际任务数
c
*imbalance = (*imbalance + 1) / SCHED_LOAD_SCALE;
- 实际任务数 = (负载量 + 1) / 缩放因子
+1
确保至少迁移1个任务
10.特殊空闲状态处理
c
out_balanced:
if (busiest && (idle == NEWLY_IDLE ||
(idle == SCHED_IDLE && max_load > SCHED_LOAD_SCALE)) ) {
*imbalance = 1;
return busiest;
}
空闲CPU的特殊规则:
NEWLY_IDLE
:刚变空闲,积极迁移SCHED_IDLE
:调度器空闲,只有负载较重时才迁移
11.完整算法流程
text
1. 遍历所有调度组,计算负载
2. 找到最忙的远程组和当前组
3. 检查系统是否已经平衡
4. 计算需要迁移的不平衡量
5. 对小不平衡量进行经济性分析
6. 考虑空闲状态的积极迁移
7. 返回最忙组和需要迁移的负载量
二、最忙运行队列查找函数find_busiest_queue
c
static runqueue_t *find_busiest_queue(struct sched_group *group)
{
unsigned long load, max_load = 0;
runqueue_t *busiest = NULL;
int i;
for_each_cpu_mask(i, group->cpumask) {
load = source_load(i);
if (load > max_load) {
max_load = load;
busiest = cpu_rq(i);
}
}
return busiest;
}
这个函数在给定的调度组内查找负载最高的单个CPU运行队列
1. 函数原型和目的
c
static runqueue_t *find_busiest_queue(struct sched_group *group)
功能:在调度组的所有CPU中,找到负载最高的那个CPU的运行队列
参数:
group
:要搜索的调度组
2. 变量初始化
c
unsigned long load, max_load = 0;
runqueue_t *busiest = NULL;
int i;
变量说明:
load
:当前CPU的负载值max_load
:遇到的最大负载值busiest
:指向最忙运行队列的指针i
:循环计数器,表示CPU编号
3. 循环遍历组内所有CPU
c
for_each_cpu_mask(i, group->cpumask) {
for_each_cpu_mask
宏:
- 遍历
group->cpumask
位图中设置的所有CPU group->cpumask
:位图,标识该调度组包含哪些CPU
4. 负载计算和比较
c
load = source_load(i);
if (load > max_load) {
max_load = load;
busiest = cpu_rq(i);
}
source_load(i)
函数:
- 返回CPU i的负载 ,取 **平均负载 **和 瞬时负载的较小值
- 对负载变化不敏感,防止出现乒乓效应
cpu_rq(i)
函数:
- 返回CPU i对应的运行队列指针
- 每个CPU有自己独立的运行队列