云计算百科
云计算领域专业知识百科平台

【Linux内核三十八】进程管理模块:CFS负载均衡(三):组统计update_sg_lb_stats与最忙组挑选update_sd_pick_busiest

内核版本:linux-5.15.140 涉及文件:kernel/sched/fair.c、kernel/sched/sched.h

前言

第 37 篇拆完了 load_balance() 主流程:should_we_balance() 确认本 CPU 是组内代表后,第一件正事就是调用 find_busiest_group() 找出该调度域里最忙的组、并算出该搬多少负载。这个函数是整个负载均衡的"判据核心",但它的实现横跨近 500 行:先是统计(update_sd_lb_stats → update_sg_lb_stats),再是选组(update_sd_pick_busiest),然后是判定(find_busiest_group 本体)与定量(calculate_imbalance),最后才是选队列(find_busiest_queue)。

一篇塞不下,拆成两篇。本篇先拆前半场——统计与选组:

load_balance() kernel/sched/fair.c:10469
└── find_busiest_group() kernel/sched/fair.c:9805
└── update_sd_lb_stats() kernel/sched/fair.c:9529 逐组遍历、汇总域级统计
├── update_group_capacity() kernel/sched/fair.c:8705 本地组:刷新组容量
├── update_sg_lb_stats() kernel/sched/fair.c:8902 单组统计
│ └── group_classify() kernel/sched/fair.c:8873 组类型分级
└── update_sd_pick_busiest() kernel/sched/fair.c:8987 挑最忙组

find_busiest_group() 本体与 calculate_imbalance()、find_busiest_queue() 留给下篇。本篇所有结构体与函数均出自上述源码位置,逐段贴原文。

一、两份统计账本:sg_lb_stats 与 sd_lb_stats

负载均衡的判定不直接读 rq,而是先把散落在各 CPU 上的 PELT 信号(见第 34 篇)聚合成两级统计账本:

/*
* sg_lb_stats – stats of a sched_group required for load_balancing
*/

struct sg_lb_stats {
unsigned long avg_load; /*Avg load across the CPUs of the group */
unsigned long group_load; /* Total load over the CPUs of the group */
unsigned long group_capacity;
unsigned long group_util; /* Total utilization over the CPUs of the group */
unsigned long group_runnable; /* Total runnable time over the CPUs of the group */
unsigned int sum_nr_running; /* Nr of tasks running in the group */
unsigned int sum_h_nr_running; /* Nr of CFS tasks running in the group */
unsigned int idle_cpus;
unsigned int group_weight;
enum group_type group_type;
unsigned int group_asym_packing; /* Tasks should be moved to preferred CPU */
unsigned long group_misfit_task_load; /* A CPU has a task too big for its capacity */
#ifdef CONFIG_NUMA_BALANCING
unsigned int nr_numa_running;
unsigned int nr_preferred_running;
#endif
};

kernel/sched/fair.c:8535

sg_lb_stats 是组级账本(一组 CPU 的合计):group_load/group_util/group_runnable 分别是三路 PELT 信号(load、util、runnable,见第 34 篇 sched_avg)在全组 CPU 上的求和;sum_nr_running 数所有任务,sum_h_nr_running 只数 CFS 任务(h_nr_running 沿层级传播,见第 35 篇);idle_cpus 数空闲 CPU;group_capacity 与 group_weight 来自第 36 篇见过的 sched_group_capacity。最关键的是 group_type——本篇第二节的主角。

/*
* sd_lb_stats – Structure to store the statistics of a sched_domain
* during load balancing.
*/

struct sd_lb_stats {
struct sched_group *busiest;/* Busiest group in this sd */
struct sched_group *local;/* Local group in this sd */
unsigned long total_load;/* Total load of all groups in sd */
unsigned long total_capacity;/* Total capacity of all groups in sd */
unsigned long avg_load;/* Average load across all groups in sd */
unsigned int prefer_sibling; /* tasks should go to sibling first */

struct sg_lb_stats busiest_stat;/* Statistics of the busiest group */
struct sg_lb_stats local_stat;/* Statistics of the local group */
};

kernel/sched/fair.c:8558

sd_lb_stats 是域级账本:在所有组的合计之上,重点记两份组级统计——本地组(env->dst_cpu 所在组)与最忙组。注意 busiest_stat/local_stat 是内嵌的 sg_lb_stats 而非指针,所以初始化函数要特别小心:

static inline void init_sd_lb_stats(struct sd_lb_stats *sds)
{
/*
* Skimp on the clearing to avoid duplicate work. We can avoid clearing
* local_stat because update_sg_lb_stats() does a full clear/assignment.
* We must however set busiest_stat::group_type and
* busiest_stat::idle_cpus to the worst busiest group because
* update_sd_pick_busiest() reads these before assignment.
*/

*sds = (struct sd_lb_stats){
.busiest = NULL,
.local = NULL,
.total_load = 0UL,
.total_capacity = 0UL,
.busiest_stat = {
.idle_cpus = UINT_MAX,
.group_type = group_has_spare,
},
};
}

kernel/sched/fair.c:8570

两个初值值得玩味:idle_cpus = UINT_MAX 让任何真实的空闲数都"更小",配合第五节 update_sd_pick_busiest() 中 group_has_spare 分支的 sgs->idle_cpus > busiest->idle_cpus 比较——第一个候选组必然胜出;group_type = group_has_spare 是最低优先级,任何候选组的类型都 ≥ 它。注释也说明了为什么 local_stat 不用清:update_sg_lb_stats() 开头有 memset(sgs, 0, sizeof(*sgs)) 全清。

二、组的六级画像:enum group_type 与 group_classify

统计账本里最要紧的字段是 group_type。内核给每个组打六级标签,枚举值越小优先级越低,选最忙组时直接比大小即可:

/*
* 'group_type' describes the group of CPUs at the moment of load balancing.
*
* The enum is ordered by pulling priority, with the group with lowest priority
* first so the group_type can simply be compared when selecting the busiest
* group. See update_sd_pick_busiest().
*/

enum group_type {
/* The group has spare capacity that can be used to run more tasks. */
group_has_spare = 0,
/*
* The group is fully used and the tasks don't compete for more CPU
* cycles. Nevertheless, some tasks might wait before running.
*/

group_fully_busy,
/*
* SD_ASYM_CPUCAPACITY only: One task doesn't fit with CPU's capacity
* and must be migrated to a more powerful CPU.
*/

group_misfit_task,
/*
* SD_ASYM_PACKING only: One local CPU with higher capacity is available,
* and the task should be migrated to it instead of running on the
* current CPU.
*/

group_asym_packing,
/*
* The tasks' affinity constraints previously prevented the scheduler
* from balancing the load across the system.
*/

group_imbalanced,
/*
* The CPU is overloaded and can't provide expected CPU cycles to all
* tasks.
*/

group_overloaded
};

kernel/sched/fair.c:7831

六级从好到坏:group_has_spare(有富余算力)→ group_fully_busy(满载但够用)→ group_misfit_task(有任务塞不进小核,仅异构容量域)→ group_asym_packing(任务该搬去更优先的 CPU,仅非对称打包域)→ group_imbalanced(此前因亲和性约束均衡失败,挂了"失衡牌")→ group_overloaded(过载,任务抢不到预期算力)。这个顺序就是"值得拉取"的优先级。

打标签的是 group_classify(),判定顺序即优先级从高到低:

static inline enum
group_type group_classify(unsigned int imbalance_pct,
struct sched_group *group,
struct sg_lb_stats *sgs)
{
if (group_is_overloaded(imbalance_pct, sgs))
return group_overloaded;

if (sg_imbalanced(group))
return group_imbalanced;

if (sgs->group_asym_packing)
return group_asym_packing;

if (sgs->group_misfit_task_load)
return group_misfit_task;

if (!group_has_capacity(imbalance_pct, sgs))
return group_fully_busy;

return group_has_spare;
}

kernel/sched/fair.c:8873

其中过载与有余量的判定是镜像的一对:

static inline int sg_imbalanced(struct sched_group *group)
{
return group->sgc->imbalance;
}

kernel/sched/fair.c:8813

/*
* group_has_capacity returns true if the group has spare capacity that could
* be used by some tasks.
* We consider that a group has spare capacity if the * number of task is
* smaller than the number of CPUs or if the utilization is lower than the
* available capacity for CFS tasks.
* For the latter, we use a threshold to stabilize the state, to take into
* account the variance of the tasks' load and to return true if the available
* capacity in meaningful for the load balancer.
* As an example, an available capacity of 1% can appear but it doesn't make
* sense for load balancer because it's in the margin of the utilization
*
*/

static inline bool
group_has_capacity(unsigned int imbalance_pct, struct sg_lb_stats *sgs)
{
if (sgs->sum_nr_running < sgs->group_weight)
return true;

if ((sgs->group_capacity * imbalance_pct) <
(sgs->group_runnable * 100))
return false;

if ((sgs->group_capacity * 100) >
(sgs->group_util * imbalance_pct))
return true;

return false;
}

/*
* group_is_overloaded returns true if the group has enough tasks on the
* cpus but still can't provide expected CPU cycles for each of them.
* We consider that a group is overloaded when the sum of the utilization
* of the CPUs is greater than the capacity of the group or the sum of the
* runnable of the CPUs is greater than the capacity of the group
*/

static inline bool
group_is_overloaded(unsigned int imbalance_pct, struct sg_lb_stats *sgs)
{
if (sgs->sum_nr_running <= sgs->group_weight)
return false;

if ((sgs->group_capacity * 100) <
(sgs->group_util * imbalance_pct))
return true;

if ((sgs->group_capacity * imbalance_pct) <
(sgs->group_runnable * 100))
return true;

return false;
}

kernel/sched/fair.c:8818~8870`

两个函数都遵循同一套三层判定,只是比较方向相反:

  • 任务数粗筛:sum_nr_running 与组内 CPU 数(group_weight)比。任务数 ≤ CPU 数 → 不可能过载(每 CPU 至多一个任务,直接 return false);任务数 < CPU 数 → 必然有富余。这一层最快,放最前。
  • util 判据:group_util(实际用掉的算力)与 group_capacity 比,容差由 imbalance_pct 控制(第 36 篇 sd_init 设的 117,SMT 层 110)。过载要求 util 超容量 17% 以上;有余量反过来要求容量比 util 大 17% 以上。
  • runnable 判据:group_runnable(排队需求)与容量比。runnable 超容 17% 也算过载。
  • imbalance_pct 制造的滞回区很关键:util 在容量的 ±17% 区间内时既不算过载也不算有余量——group_is_overloaded 返回 false、group_has_capacity 也返回 false,最终落进 group_fully_busy。这防止了边界负载在两个状态间抖动,导致均衡器来回搬任务。注释里那句"an available capacity of 1% can appear but it doesn’t make sense"说的就是:容量余 1% 不值得拉任务过来。

    sg_imbalanced 读的是 group->sgc->imbalance——第 37 篇 LBF_SOME_PINNED 善后时在父域组上立的"失衡牌"(sgc->imbalance = 1),下篇 find_busiest_group() 见到这个类型会直接 force_balance 绕过常规检查。

    三、update_sg_lb_stats:单组逐 CPU 统计

    static inline void update_sg_lb_stats(struct lb_env *env,
    struct sched_group *group,
    struct sg_lb_stats *sgs,
    int *sg_status)
    {
    int i, nr_running, local_group;

    memset(sgs, 0, sizeof(*sgs));

    local_group = cpumask_test_cpu(env->dst_cpu, sched_group_span(group));

    for_each_cpu_and(i, sched_group_span(group), env->cpus) {
    struct rq *rq = cpu_rq(i);

    sgs->group_load += cpu_load(rq);
    sgs->group_util += cpu_util(i);
    sgs->group_runnable += cpu_runnable(rq);
    sgs->sum_h_nr_running += rq->cfs.h_nr_running;

    nr_running = rq->nr_running;
    sgs->sum_nr_running += nr_running;

    if (nr_running > 1)
    *sg_status |= SG_OVERLOAD;

    if (cpu_overutilized(i))
    *sg_status |= SG_OVERUTILIZED;

    #ifdef CONFIG_NUMA_BALANCING
    sgs->nr_numa_running += rq->nr_numa_running;
    sgs->nr_preferred_running += rq->nr_preferred_running;
    #endif
    /*
    * No need to call idle_cpu() if nr_running is not 0
    */

    if (!nr_running && idle_cpu(i)) {
    sgs->idle_cpus++;
    /* Idle cpu can't have misfit task */
    continue;
    }

    if (local_group)
    continue;

    /* Check for a misfit task on the cpu */
    if (env->sd->flags & SD_ASYM_CPUCAPACITY &&
    sgs->group_misfit_task_load < rq->misfit_task_load) {
    sgs->group_misfit_task_load = rq->misfit_task_load;
    *sg_status |= SG_OVERLOAD;
    }
    }

    /* Check if dst CPU is idle and preferred to this group */
    if (env->sd->flags & SD_ASYM_PACKING &&
    env->idle != CPU_NOT_IDLE &&
    sgs->sum_h_nr_running &&
    sched_asym_prefer(env->dst_cpu, group->asym_prefer_cpu)) {
    sgs->group_asym_packing = 1;
    }

    sgs->group_capacity = group->sgc->capacity;

    sgs->group_weight = group->group_weight;

    sgs->group_type = group_classify(env->sd->imbalance_pct, group, sgs);

    /* Computing avg_load makes sense only when group is overloaded */
    if (sgs->group_type == group_overloaded)
    sgs->avg_load = (sgs->group_load * SCHED_CAPACITY_SCALE) /
    sgs->group_capacity;
    }

    kernel/sched/fair.c:8902

    逐段拆:

    1. 遍历范围:for_each_cpu_and(i, sched_group_span(group), env->cpus) 取组 span 与均衡候选 CPU 集合的交集——env->cpus 在第 37 篇里已被 LBF_ALL_PINNED 善后逐轮剔除搬不动的 CPU。

    2. 三路信号累加:cpu_load()、cpu_runnable() 直接读 cfs_rq 的 PELT 均值:

    static unsigned long cpu_load(struct rq *rq)
    {
    return cfs_rq_load_avg(&rq->cfs);
    }

    kernel/sched/fair.c:5989

    static unsigned long cpu_runnable(struct rq *rq)
    {
    return cfs_rq_runnable_avg(&rq->cfs);
    }

    kernel/sched/fair.c:6025

    cpu_util() 略复杂(fair.c:6742),取 cfs_rq->avg.util_avg 与"当前 runnable 任务的估计利用率之和"的较大值——刚睡醒的大任务不会因为衰减中的 util 而被低估,这是 EAS/DVFS 的需求,此处不展开。

    3. 两个状态位:sg_status 是本域统计期间的聚合标志,nr_running > 1 置 SG_OVERLOAD,任一 CPU cpu_overutilized() 置 SG_OVERUTILIZED。这两个宏定义在:

    #define SG_OVERLOAD0x1 /* More than one runnable task on a CPU. */
    #define SG_OVERUTILIZED0x2 /* One or more CPUs are over-utilized. */

    kernel/sched/sched.h:785

    它们不是组统计,而是回写给 root_domain 的全局指示器(第四节),供唤醒选核路径(check_preempt_wakeup 之后的 select_task_rq_fair)快速判断系统是否过载。

    4. 空闲计数:!nr_running && idle_cpu(i) 才计入 idle_cpus——nr_running 为 0 是必要条件,idle_cpu() 再确认 rq->curr 是 idle 且无待处理唤醒,避免把刚要跑任务的 CPU 误判为空闲。注释"Idle cpu can’t have misfit task"顺带 continue 掉了后面的 misfit 检查。

    5. misfit 检查只对非本地组做:local_group 时直接 continue。misfit 记录全组最大的 rq->misfit_task_load——本地组不需要知道自己的 misfit(均衡方向是把任务从最忙组拉到本地)。

    6. asym_packing 特判:目的 CPU 空闲且比本组首选 CPU(group->asym_prefer_cpu,第 36 篇拓扑构建时算好)更受偏爱时,标记 group_asym_packing = 1,group_classify() 据此返回 group_asym_packing 类型。

    7. 收尾三级跳:抄入组容量与组权重 → group_classify() 定型 → 仅当过载时计算 avg_load = group_load * 1024 / group_capacity。注释点明:avg_load 只在过载比较时有意义(第五节 group_overloaded 分支拿它 tie-break),非过载组不算除法,省下一次开销。

    四、update_sd_lb_stats:遍历组环、汇总域账本

    static inline void update_sd_lb_stats(struct lb_env *env, struct sd_lb_stats *sds)
    {
    struct sched_domain *child = env->sd->child;
    struct sched_group *sg = env->sd->groups;
    struct sg_lb_stats *local = &sds->local_stat;
    struct sg_lb_stats tmp_sgs;
    unsigned long sum_util = 0;
    int sg_status = 0;

    do {
    struct sg_lb_stats *sgs = &tmp_sgs;
    int local_group;

    local_group = cpumask_test_cpu(env->dst_cpu, sched_group_span(sg));
    if (local_group) {
    sds->local = sg;
    sgs = local;

    if (env->idle != CPU_NEWLY_IDLE ||
    time_after_eq(jiffies, sg->sgc->next_update))
    update_group_capacity(env->sd, env->dst_cpu);
    }

    update_sg_lb_stats(env, sg, sgs, &sg_status);

    if (local_group)
    goto next_group;

    if (update_sd_pick_busiest(env, sds, sg, sgs)) {
    sds->busiest = sg;
    sds->busiest_stat = *sgs;
    }

    next_group:
    /* Now, start updating sd_lb_stats */
    sds->total_load += sgs->group_load;
    sds->total_capacity += sgs->group_capacity;

    sum_util += sgs->group_util;
    sg = sg->next;
    } while (sg != env->sd->groups);

    /* Tag domain that child domain prefers tasks go to siblings first */
    sds->prefer_sibling = child && child->flags & SD_PREFER_SIBLING;

    if (env->sd->flags & SD_NUMA)
    env->fbq_type = fbq_classify_group(&sds->busiest_stat);

    if (!env->sd->parent) {
    struct root_domain *rd = env->dst_rq->rd;

    /* update overload indicator if we are at root domain */
    WRITE_ONCE(rd->overload, sg_status & SG_OVERLOAD);

    /* Update over-utilization (tipping point, U >= 0) indicator */
    WRITE_ONCE(rd->overutilized, sg_status & SG_OVERUTILIZED);
    trace_sched_overutilized_tp(rd, sg_status & SG_OVERUTILIZED);
    } else if (sg_status & SG_OVERUTILIZED) {
    struct root_domain *rd = env->dst_rq->rd;

    WRITE_ONCE(rd->overutilized, SG_OVERUTILIZED);
    trace_sched_overutilized_tp(rd, SG_OVERUTILIZED);
    }

    update_idle_cpu_scan(env, sum_util);
    }

    kernel/sched/fair.c:9529

    1. do-while 走组环:sg = sg->next 直到回到 env->sd->groups——第 36 篇建的环形链表在这里派上用场,遍历不需要额外容器。

    2. 本地组特殊处理:判到 env->dst_cpu 在组 span 内,记下 sds->local,且统计直接写进 sds->local_stat(不用 tmp 再拷贝)。同时趁机刷新组容量:update_group_capacity()(第 36 篇已拆)沿子域把各 CPU 当前容量(扣掉 RT/DL/irq 占用后)累进 sgc->capacity。刷新有节流——CPU_NEWLY_IDLE 均衡太频繁,须等 sgc->next_update 到点(周期均衡与 newidle 均衡的取舍)。

    3. 非本地组交给 update_sd_pick_busiest() 竞选最忙组,胜出则整体拷入 sds->busiest_stat = *sgs(结构体赋值,这也是第一节说内嵌而非指针的原因)。

    4. 域级汇总:无论本地与否,total_load、total_capacity、sum_util 一路累加——下篇 find_busiest_group() 的"域平均负载" sds->avg_load 就从这两个 total 来。

    5. prefer_sibling 标记:子域带 SD_PREFER_SIBLING(典型:MC 层希望 LLC 尽量空)时,本层均衡倾向于把任务摊到兄弟组,下篇 calculate_imbalance() 会用它切换到按任务数对半分。

    6. NUMA 场景的 fbq 分类:fbq_classify_group() 按 NUMA 任务占比把最忙组分为 regular/remote/all 三类,写进 env->fbq_type,供 find_busiest_queue() 跳过"放错了节点"的队列(本篇不展开)。

    7. 回写 root_domain 指示器:rd->overload(有 CPU 上多于一个 runnable 任务)只在根域层(!env->sd->parent)写,且是全量赋值——每轮周期均衡都会重新采样;rd->overutilized 则在根域全量写、在非根层只置位不清零(else if 分支)。overutilized 是 EAS(能量感知调度)的总开关信号:一旦任何一个域置位,整个系统退回常规负载均衡。

    8. update_idle_cpu_scan 收尾:这个函数(fair.c:9452)用一条抛物线把 LLC 域的 nr_idle_scan 提示刷新掉——当 LLC 利用率升高时按 y = 1024 – x²·pct²/(10000·1024) 缩减唤醒路径 select_idle_cpu() 的扫描 CPU 数,利用率到 85%(即 imbalance_pct=117 的倒数阈值)时归零停止扫描。仅周期均衡时写(newidle 太频繁,写共享 cache line 代价高),且需开 SIS_UTIL 特性。

    五、update_sd_pick_busiest:竞选最忙组

    static bool update_sd_pick_busiest(struct lb_env *env,
    struct sd_lb_stats *sds,
    struct sched_group *sg,
    struct sg_lb_stats *sgs)
    {
    struct sg_lb_stats *busiest = &sds->busiest_stat;

    /* Make sure that there is at least one task to pull */
    if (!sgs->sum_h_nr_running)
    return false;

    /*
    * Don't try to pull misfit tasks we can't help.
    * We can use max_capacity here as reduction in capacity on some
    * CPUs in the group should either be possible to resolve
    * internally or be covered by avg_load imbalance (eventually).
    */

    if (sgs->group_type == group_misfit_task &&
    (!capacity_greater(capacity_of(env->dst_cpu), sg->sgc->max_capacity) ||
    sds->local_stat.group_type != group_has_spare))
    return false;

    if (sgs->group_type > busiest->group_type)
    return true;

    if (sgs->group_type < busiest->group_type)
    return false;

    /*
    * The candidate and the current busiest group are the same type of
    * group. Let check which one is the busiest according to the type.
    */

    switch (sgs->group_type) {
    case group_overloaded:
    /* Select the overloaded group with highest avg_load. */
    if (sgs->avg_load <= busiest->avg_load)
    return false;
    break;

    case group_imbalanced:
    /*
    * Select the 1st imbalanced group as we don't have any way to
    * choose one more than another.
    */

    return false;

    case group_asym_packing:
    /* Prefer to move from lowest priority CPU's work */
    if (sched_asym_prefer(sg->asym_prefer_cpu, sds->busiest->asym_prefer_cpu))
    return false;
    break;

    case group_misfit_task:
    /*
    * If we have more than one misfit sg go with the biggest
    * misfit.
    */

    if (sgs->group_misfit_task_load < busiest->group_misfit_task_load)
    return false;
    break;

    case group_fully_busy:
    /*
    * Select the fully busy group with highest avg_load. In
    * theory, there is no need to pull task from such kind of
    * group because tasks have all compute capacity that they need
    * but we can still improve the overall throughput by reducing
    * contention when accessing shared HW resources.
    *
    * XXX for now avg_load is not computed and always 0 so we
    * select the 1st one.
    */

    if (sgs->avg_load <= busiest->avg_load)
    return false;
    break;

    case group_has_spare:
    /*
    * Select not overloaded group with lowest number of idle cpus
    * and highest number of running tasks. We could also compare
    * the spare capacity which is more stable but it can end up
    * that the group has less spare capacity but finally more idle
    * CPUs which means less opportunity to pull tasks.
    */

    if (sgs->idle_cpus > busiest->idle_cpus)
    return false;
    else if ((sgs->idle_cpus == busiest->idle_cpus) &&
    (sgs->sum_nr_running <= busiest->sum_nr_running))
    return false;

    break;
    }

    /*
    * Candidate sg has no more than one task per CPU and has higher
    * per-CPU capacity. Migrating tasks to less capable CPUs may harm
    * throughput. Maximize throughput, power/energy consequences are not
    * considered.
    */

    if ((env->sd->flags & SD_ASYM_CPUCAPACITY) &&
    (sgs->group_type <= group_fully_busy) &&
    (capacity_greater(sg->sgc->min_capacity, capacity_of(env->dst_cpu))))
    return false;

    return true;
    }

    kernel/sched/fair.c:8987

    这是本篇最精彩的函数,竞选规则分四层:

    第 1 层:一票否决。sum_h_nr_running == 0(组里没有 CFS 任务)直接出局。misfit 类型也有一票否决:本地 CPU 容量不比该组最大容量明显更大(capacity_greater 宏,fair.c:122,(cap1)*1024 > (cap2)*1078,即 cap1 须超出约 5% 才算"更大"),或本地组自己都没有富余——拉过来也解决不了 misfit,白搬。

    第 2 层:类型比大小。group_type 枚举值大的直接胜出(过载 > 失衡牌 > ……),这正是第一节注释"ordered by pulling priority"的用意——选最忙组退化成一次整数比较。

    第 3 层:同型 tie-break,每种类型有自己的"更忙"标准:

    • group_overloaded:比 avg_load(第三节里只在过载时才计算的除法结果);
    • group_imbalanced:拿第一个(没有可比性);
    • group_asym_packing:比谁的 asym_prefer_cpu 优先级更低(更低优先 CPU 上的活更该搬走);
    • group_misfit_task:比最大的 misfit 负载;
    • group_fully_busy:注释坦诚说理——满载组任务算力本来就够,理论上不用拉,但拉一点能减少共享硬件资源(cache)争用,提升吞吐;不过 avg_load 此刻恒为 0(第三节只在 overloaded 时算),所以实际选第一个,注释里留着 XXX 标记;
    • group_has_spare:比 idle_cpus 少的(空闲 CPU 少 = 更"满"),平手再比 sum_nr_running 多的。注释解释了为什么不用更稳定的"富余容量"比:容量小但空闲 CPU 多的组,反而更不值得从它拉任务——均衡的本质是消灭空闲 CPU。

    第 4 层:吞吐保护。异构容量域上,候选组虽然只有"每 CPU 至多一个任务"的轻度负载,但其最小容量 CPU 都比本地 CPU 强 5% 以上时,把任务拉到弱 CPU 上会损害吞吐——直接否决。注释明确"Maximize throughput, power/energy consequences are not considered"。

    小结

  • 两级账本:sg_lb_stats(组级:三路 PELT 信号求和 + 任务数 + 空闲数 + 类型)与 sd_lb_stats(域级:本地组与最忙组两份组账本 + 全域 total/avg),init_sd_lb_stats 用"最坏初值"(idle_cpus=UINT_MAX、group_type=group_has_spare)保证首个候选组必然当选。
  • 六级类型画像:group_type 按"值得拉取的优先级"排序(has_spare → fully_busy → misfit → asym_packing → imbalanced → overloaded),group_classify 依序判定;imbalance_pct(默认 117)在过载/有余量之间制造 ±17% 的滞回区,落入其中的组是 group_fully_busy。
  • update_sg_lb_stats 逐 CPU 累加三路信号、置 SG_OVERLOAD/SG_OVERUTILIZED 状态位、记 misfit 与 asym_packing、数空闲 CPU,最后定型 group_type;avg_load 只在过载时才做除法。
  • update_sd_lb_stats 沿组环 do-while 遍历:本地组顺路节流刷新 update_group_capacity,非本地组竞选最忙组,全域累加 total;根域层回写 rd->overload/rd->overutilized 指示器(overutilized 是 EAS 的总开关);update_idle_cpu_scan 用抛物线调唤醒扫描提示。
  • update_sd_pick_busiest 四层竞选:无任务/misfit 无解一票否决 → 类型比大小 → 同型 tie-break(各类型各有一把尺)→ 异构域吞吐保护。
  • 统计与选组完成后,sds 里已经躺着完整的候选名单。下篇进入 find_busiest_group() 本体:六种本地×最忙类型组合的决策矩阵、calculate_imbalance() 怎么把"该不该搬"翻译成"搬多少"(migrate_load/util/task/misfit 四种模式),以及 find_busiest_queue() 在最忙组内挑出具体源队列。


    系列回链:load_balance 主流程与 lb_env 见[第 37 篇];sched_group/sched_group_capacity 结构与组环链见[第 36 篇];PELT 三路信号(load/util/runnable)见[第 34 篇];h_nr_running 层级传播与 removed 机制见[第 35 篇];sched_asym_prefer 与 asym_prefer_cpu 见[第 36 篇]。

    赞(0)
    未经允许不得转载:网硕互联帮助中心 » 【Linux内核三十八】进程管理模块:CFS负载均衡(三):组统计update_sg_lb_stats与最忙组挑选update_sd_pick_busiest
    分享到: 更多 (0)

    评论 抢沙发

    评论前必须登录!