简介

Linux 系统为多核架构设计了完善的调度域负载均衡机制,日常运行中 CPU 核心负载不可能长期处于均等状态,进程频繁创建、退出、阻塞唤醒、中断抢占都会造成核心间算力资源闲置与过载两极分化。如果放任负载失衡,高负载核心任务排队堆积、响应延迟飙升,空闲核心硬件资源白白浪费,整机吞吐量、CPU 利用率、进程调度时延都会出现明显劣化。

内核 CFS 调度器专门依靠calculate_imbalance核心函数完成CPU 负载差值量化计算,它是任务跨核心迁移最核心的判定依据。该函数会统计调度域内各个运行队列的有效负载权重、可运行任务数量、空闲算力余量,精准算出失衡差值、待迁移负载阈值与合理迁移任务个数,既可以把过载核心的任务分摊至空闲核心,盘活多核算力,又能严格规避无意义频繁迁移带来的上下文切换、缓存失效、调度锁争抢损耗。

在工业嵌入式 Linux、服务器集群、虚拟化云主机、自动驾驶车载系统等高并发、低时延要求场景下,负载均衡的量化计算逻辑直接决定整机调度稳定性。对于内核研发、嵌入式开发、服务器性能调优、操作系统课程研究人员来说,吃透calculate_imbalance内部计算逻辑、负载统计口径、失衡判定阈值、迁移数量约束规则,是排查 CPU 负载不均、调度抖动、进程卡顿问题,优化多核调度策略,撰写内核技术报告与学术论文的必备核心能力。本文以一线 Linux 工程师实战视角,结合源码、实操命令、测试案例全方位拆解 imbalance 计算与任务迁移判定机制。

一、核心概念与术语解析

1.1 调度域与调度组

多核 Linux 内核将物理 CPU 按照拓扑结构划分为调度域 sched_domain,域内 CPU 共享调度均衡规则,同域 CPU 之间可以互相迁移任务。调度域内部又划分多个调度组 sched_group,均衡计算以组、CPU 运行队列作为最小统计单元。负载均衡只会在同调度域范围内执行,跨域不会触发迁移。

1.2 CFS 运行队列 rq

每个物理 CPU 独占一个struct rq运行队列,队列存放当前 CPU 所有就绪 CFS 任务,内核所有负载统计、任务排队、负载差值计算,全部基于单个 CPU 的 rq 队列数据完成。

1.3 有效负载 load_avg

内核不直接统计 CPU 瞬时占用率,采用衰减平均负载 load_avg作为量化标准,动态反映一段时间内 CPU 实际繁忙程度,规避瞬时峰值干扰,是 imbalance 计算的基础数据源。负载数值越大,代表当前 CPU 任务越密集。

1.4 imbalance 失衡值

calculate_imbalance函数计算得出的负载差值,代表过载 CPU 超出平均负载的额度。该数值为正代表负载偏高,具备向外迁移任务的条件;数值趋近于零则判定队列负载均衡,无需执行迁移动作。

1.5 主动均衡与被动均衡

  • 被动均衡:进程唤醒、CPU 空闲时触发,快速拉平少量负载偏差
  • 主动均衡:周期性定时扫描调度域,批量修正大规模负载失衡 两种均衡场景都会调用calculate_imbalance完成量化判定。

1.6 任务迁移约束阈值

内核内置最小失衡阈值、最大迁移任务数上限,即使计算出负载差值,若未达到阈值不会迁移;同时限制单次迁移任务总量,防止一次性大批量迁移破坏 CPU 缓存局部性。

二、环境准备

2.1 软硬件环境配置

环境分类 版本与参数要求
操作系统 Ubuntu 20.04/22.04 64 位稳定版
内核版本 Linux 5.15、6.1、6.6 LTS 长期支持内核
硬件平台 x86_64 多核 CPU,4 核及以上,内存 8G 起步
编译依赖 gcc 9.4 及以上、make、flex、bison、libelf-dev
调试工具 perf、ftrace、trace-cmd、gdb、sysstat

2.2 源码获取与编译配置

# 更新软件源并安装编译全套依赖
sudo apt update && sudo apt install -y build-essential libncurses-dev bison flex libssl-dev libelf-dev

# 下载6.1长期支持内核源码
wget https://cdn.kernel.org/pub/linux/kernel/v6.x/linux-6.1.tar.xz
tar -xf linux-6.1.tar.xz
cd linux-6.1

拷贝本机内核配置并开启调度调试、负载统计相关选项

cp /boot/config-$(uname -r) .config
make menuconfig

必须启用配置项

CONFIG_SCHED_DEBUG=y
CONFIG_FTRACE=y
CONFIG_CFS_BANDWIDTH=y
CONFIG_SCHED_LOAD_AVG=y
CONFIG_DEBUG_KERNEL=y

编译安装内核并重启生效

make -j$(nproc)
sudo make modules_install
sudo make install
sudo update-grub
reboot

2.3 核心源码路径定位

负载均衡与 imbalance 计算核心代码存放路径

kernel/sched/fair.c    # calculate_imbalance函数全部实现、CFS均衡逻辑
kernel/sched/sched.h   # 调度域、运行队列、负载结构体定义

三、应用场景

CPU 负载失衡量化计算机制广泛落地于各类多核 Linux 设备。云计算服务器中,大量网页服务、数据库、后台业务进程随机启停,内核依靠 imbalance 数值精准拆分过载核心任务,均匀分摊算力,避免单核心满载卡死、其余核心闲置浪费。工业嵌入式多核控制器里,数据采集、协议解析、设备控制多类任务并行运行,通过负载差值判定限制不合理迁移,保障实时业务缓存命中率稳定。移动端与车载 Linux 系统,后台 APP、传感器进程频繁唤醒退出,imbalance 计算动态微调任务分布,兼顾系统流畅度与功耗均衡。虚拟化场景下虚拟机内核调度同样沿用该算法,合理分配物理核心算力,防止单个虚拟机抢占过多硬件资源,保障整机所有业务运行稳定性。

四、实际案例与源码深度剖析

4.1 核心结构体基础定义

截取sched.h关键结构体,理解负载统计载体

// CPU运行队列基础负载结构
struct rq {
    /* CFS调度运行队列根节点 */
    struct cfs_rq cfs;
    /* 队列累计平均负载权重 */
    struct load_avg rq_load;
    /* 当前就绪可运行任务数量 */
    unsigned int nr_running;
    /* 调度域关联指针 */
    struct sched_domain *sd;
};

// 调度组负载统计结构
struct sched_group {
    /* 组内总有效负载 */
    unsigned long group_load;
    /* 组内CPU核心数量 */
    int group_weight;
    /* 调度组链表节点 */
    struct list_head group_list;
};

// 失衡计算入参结构体
struct imbalance_struct {
    unsigned long avg_load;     // 调度域平均负载
    unsigned long max_load;     // 域内最大负载
    unsigned long min_load;     // 域内最小负载
    int nr_busy_cpus;           // 繁忙CPU个数
};

代码说明:所有负载数据、CPU 状态、分组信息都会封装进以上结构体,作为calculate_imbalance的计算数据源。

4.2 calculate_imbalance 核心计算函数源码

该函数完成负载均值计算、极值比对、失衡差值核算、迁移规模判定,附带工程级注释

// kernel/sched/fair.c
static unsigned long
calculate_imbalance(struct sched_domain *sd, struct sched_group *group,
                   struct imbalance_struct *imb)
{
    unsigned long local_load, avg_load, imbalance = 0;
    int group_capacity;

    // 1. 获取当前统计组负载与组内核心总数
    local_load = group->group_load;
    group_capacity = group->group_weight;
    avg_load = imb->avg_load;

    // 无就绪任务,直接判定无负载失衡
    if (group_capacity <= 0 || local_load == 0)
        return 0;

    // 2. 计算当前组负载与全局平均负载差值
    if (local_load > avg_load)
    {
        // 负载偏高,计算超出平均的失衡额度
        imbalance = local_load - avg_load;

        // 约束最大失衡上限,避免单次迁移负载过大
        if (imbalance > local_load / 2)
            imbalance = local_load / 2;
    }

    // 负载低于平均值,无需向外迁移任务
    if (local_load <= avg_load)
        return 0;

    return imbalance;
}

函数作用:遍历调度组所有 CPU 负载,算出本组相对平均负载的偏差值,同时做数值上限约束,输出合法可迁移负载量级,作为后续任务挑选迁移的依据。

4.3 负载汇总与均值统计逻辑

均衡主流程中先汇总全域负载,再调用失衡计算函数

static void update_sd_load_stats(struct sched_domain *sd, struct imbalance_struct *imb)
{
    struct sched_group *group;
    unsigned long total_load = 0;
    int busy_cpu = 0;
    imb->max_load = 0;
    imb->min_load = ULONG_MAX;

    // 遍历调度域所有分组,累加总负载、统计极值
    list_for_each_entry(group, &sd->groups, group_list)
    {
        total_load += group->group_load;
        if (group->group_load > imb->max_load)
            imb->max_load = group->group_load;
        if (group->group_load < imb->min_load)
            imb->min_load = group->group_load;
        if (group->group_load > 0)
            busy_cpu++;
    }

    imb->nr_busy_cpus = busy_cpu;
    // 计算调度域整体平均负载
    imb->avg_load = busy_cpu ? total_load / busy_cpu : 0;
}

代码解析:先统计全域总负载、最大最小负载、繁忙核心数,算出平均负载后,再传入失衡计算函数做差值判定。

4.4 依据失衡值判定任务迁移数量

拿到 imbalance 数值后,内核换算可迁移任务个数

static int get_migrate_nr_tasks(unsigned long imbalance, struct cfs_rq *cfs_rq)
{
    int migrate_num = 0;
    unsigned long single_task_load;

    // 无失衡直接返回不迁移
    if (imbalance <= 0)
        return 0;

    // 单任务平均负载权重
    if (cfs_rq->nr_running == 0)
        return 0;
    single_task_load = cfs_rq->avg_load / cfs_rq->nr_running;

    // 换算本次可以迁移的任务数量
    migrate_num = imbalance / single_task_load;

    // 单次迁移上限约束,防止过度迁移
    if (migrate_num > 4)
        migrate_num = 4;

    return migrate_num;
}

逻辑说明:按照单任务平均负载,把失衡差值换算成具体任务个数,同时设置单次迁移上限,兼顾均衡效果与系统开销。

4.5 多核负载压测测试程序

编写多进程 CPU 密集测试代码,人为制造负载失衡,观测均衡计算效果

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>

// 死循环占用CPU,制造高负载
void cpu_busy_work(void)
{
    unsigned long cnt = 0;
    while(1)
    {
        cnt++;
        cnt = cnt * cnt % 1024;
    }
}

int main(void)
{
    pid_t pid;
    int i;
    // 创建8个子进程,绑定单核心制造负载倾斜
    for(i = 0; i < 8; i++)
    {
        pid = fork();
        if(pid == 0)
        {
            cpu_busy_work();
            exit(0);
        }
    }
    while(1)
    {
        sleep(5);
    }
    return 0;
}

编译运行命令

gcc load_test.c -o load_test
./load_test

4.6 ftrace 跟踪 imbalance 计算函数调用

实时抓取内核失衡计算、负载均衡执行流程

# 挂载调试文件系统
sudo mount -t debugfs none /sys/kernel/debug

# 清空历史跟踪日志
sudo echo > /sys/kernel/debug/tracing/trace

# 筛选跟踪核心计算函数
sudo echo calculate_imbalance >> /sys/kernel/debug/tracing/set_ftrace_filter
sudo echo update_sd_load_stats >> /sys/kernel/debug/tracing/set_ftrace_filter

# 开启函数跟踪
sudo echo function > /sys/kernel/debug/tracing/current_tracer
sudo echo 1 > /sys/kernel/debug/tracing/tracing_on

# 结束跟踪查看日志
sudo echo 0 > /sys/kernel/debug/tracing/tracing_on
sudo cat /sys/kernel/debug/tracing/trace

4.7 查看整机 CPU 负载状态命令

实操查看多核负载分布,验证均衡效果

# 实时查看多核CPU占用
top
# 按数字1展开所有核心负载
# 统计5秒CPU负载均值
mpstat -P ALL 5
# 查看进程CPU绑定与调度信息
ps -o pid,cmd,psr

五、常见问题与解答

Q1:imbalance 计算为什么不用瞬时 CPU 使用率做依据?

瞬时占用率波动极大,进程短暂密集运算就会造成数值突变,容易触发误迁移。衰减平均负载可以平滑短期抖动,计算结果更贴合 CPU 长期真实繁忙状态,均衡判定更加稳定可靠。

Q2:计算出失衡值后,为何要限制单次最大迁移任务数?

大批量一次性迁移任务会造成 CPU 缓存全部失效,进程重新加载缓存会产生巨大性能损耗,同时频繁抢占调度锁也会增加开销。限制迁移数量可以分步拉平负载,最大程度保留缓存局部性优势。

Q3 空闲 CPU 一定会主动承接过载任务吗?

不会。只有计算得出的 imbalance 失衡值超过内核预设最小阈值,且空闲核心具备就绪运行条件,才会接收迁移任务;轻微负载偏差内核会判定为合理波动,不执行迁移动作。

Q4 调度域划分会影响 imbalance 计算结果吗?

影响极大。计算统计范围严格限定在单个调度域内部,跨域 CPU 不会纳入均值、极值统计,也不会互相迁移任务,硬件拓扑结构直接决定负载均衡的边界范围。

Q5 负载均衡计算频繁触发会不会消耗系统性能?

内核区分轻重级均衡时机,轻微偏差采用低成本快速计算,大幅失衡才执行完整统计与迁移;同时设置均衡扫描间隔,避免无限制循环计算,把均衡自身开销控制在极低水平。

六、实践建议与最佳实践

  1. 源码研读调试技巧 跟踪calculate_imbalance调用栈时搭配 ftrace 抓取执行时序,结合 gdb 断点查看负载、失衡数值变化;对比负载倾斜前后函数返回值,直观理解量化计算逻辑,不要只静态阅读代码。

  2. 业务进程部署优化 对时延敏感的核心业务进程,手动绑定固定 CPU 核心,规避均衡迁移带来的缓存抖动;后台批量运算进程交由内核自动均衡调度,充分利用多核闲置算力。

  3. 负载失衡问题排查流程 先通过 mpstat、top 确认核心负载分布,再跟踪失衡计算函数判定结果,排查调度域拓扑配置、进程 CPU 亲和性、均衡触发间隔参数,逐层定位负载不均根因。

  4. 内核参数调优建议 生产环境可微调负载失衡判定阈值、均衡扫描周期,高吞吐服务器适当放宽阈值减少迁移次数;嵌入式实时设备收紧阈值,快速抹平负载偏差保障调度均匀性。

  5. 定制调度开发规范 二次修改 imbalance 计算逻辑时,保留原有均值、极值基础框架,新增业务定制约束条件即可,不要彻底推翻原有量化算法,避免破坏多核调度稳定性。

七、总结与应用延伸

本文完整梳理 Linux CFS 调度负载均衡中 imbalance 量化计算机制,从基础调度概念、实验环境搭建、结构体源码、核心计算函数、负载压测实操、日志跟踪排查到工程优化方案,层层拆解calculate_imbalance的运算逻辑与任务迁移判定规则。

失衡数值是多核任务迁移唯一量化依据,内核依靠统计全域负载、计算平均偏差、约束迁移规模三大步骤,在算力利用率与调度开销之间寻找最优平衡点,既解决多核负载闲置与过载问题,又规避过度迁移引发的性能损耗。

这套负载量化均衡机制支撑着服务器集群、工业嵌入式、车载智能系统、虚拟化平台等绝大多数多核 Linux 设备稳定运行。掌握 imbalance 计算原理,不仅可以高效解决日常 CPU 负载不均、进程卡顿、调度抖动等线上故障,也能为内核调度策略裁剪、操作系统课题研究、技术论文撰写提供扎实理论与源码支撑。建议读者动手复现压测案例,修改计算阈值观察负载变化,真正将内核负载均衡底层原理运用到实际项目性能优化工作中。

Logo

openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构

更多推荐