Linux 负载均衡的 push/pull 模型:过载 CPU 与空闲 CPU 的协同
本文深入解析Linux内核多核负载均衡机制中的Push推送与Pull拉取协同模型。在SMP架构下,Linux通过调度域划分CPU核心层级,当检测到CPU负载不均时,Push模式由过载核心主动分流任务,Pull模式由空闲核心主动抓取任务,实现多核间动态负载平衡。文章详细剖析了相关内核数据结构、核心源码实现,并提供了压测程序、性能观测工具和调试方法。该机制广泛应用于云服务器、嵌入式工控、自动驾驶等领域
简介
在多核 SMP 架构服务器、嵌入式多核心工控板、车载域控处理器中,Linux 系统时刻面临 CPU 核心负载不均的问题。部分核心持续高负载跑满业务进程,其余核心长期处于低负载甚至空闲状态,不仅浪费硬件算力资源,还会拉高进程调度时延、引发业务卡顿、多核功耗失衡。
为解决多核算力分配失衡问题,Linux 内核调度域体系下设计了Push 推送、Pull 拉取双模式负载均衡协同机制。Push 模式由负载超标 CPU 主动向外推送就绪任务,Pull 模式由空闲 CPU 主动从繁忙核心抓取可迁移任务,两种模式相互配合、分工互补,动态完成多核之间任务打散与重分配。
这套负载均衡模型是 Linux SMP 调度的核心骨架,广泛支撑云服务器集群算力调度、工业多轴控制、自动驾驶多任务并行、容器虚拟化资源调度等业务。对于内核开发、嵌入式实时开发、服务器性能调优、云计算运维工程师而言,吃透 Push/Pull 触发时机、迁移判定规则、调度域层级协作逻辑,是排查 CPU 负载倾斜、优化多核并发性能、解决进程卡死调度异常、定制内核调度策略的核心能力。本文从基础概念、环境搭建、源码拆解、实操验证、问题排查到工程规范完整讲解,内容可直接用于技术报告撰写、论文调研与线上线下项目落地。
一、核心概念与术语解析
1.1 SMP 对称多处理器架构
系统拥有多个物理 CPU 核心,所有核心共享内存、外设资源,任务可在任意核心间调度切换,Linux 默认基于 SMP 架构做全局负载均衡,单核心负载不会孤立运行。
1.2 调度域与调度组
内核将物理 CPU 按照 NUMA 节点、物理封装、核心层级划分为多层sched_domain 调度域,同级域内 CPU 具备任务迁移资格。
- 调度域:划定负载均衡的作用范围,均衡只会在域内 CPU 之间发生
- 调度组:域内多个 CPU 组成分组,统计组内整体负载水位,判断过载阈值
1.3 运行队列 rq
每个 CPU 核心独占一个struct rq运行队列,存放当前核心就绪、运行、休眠待唤醒的所有进程,负载统计、任务迁移均以单个 rq 为最小单元。
1.4 Push 推送均衡模型
主动推送:本地 CPU 运行队列负载超出系统预设阈值,判定为过载状态,主动扫描同调度域内空闲、低负载 CPU,筛选满足迁移条件的就绪任务,主动推送至目标核心运行队列。 触发场景:单核心 CPU 使用率持续偏高、就绪任务堆积、长时间无法流转。
1.5 Pull 拉取均衡模型
被动拉取:本地 CPU 处于空闲、低负载状态,主动遍历周边高负载 CPU 运行队列,抓取可迁移、无绑定限制的就绪任务,拉取到自身队列执行。 触发场景:CPU 空闲无任务、周期性均衡扫描、进程唤醒抢占时机。
1.6 负载权重与迁移代价
内核不再单纯统计 CPU 使用率,以任务平均负载权重衡量繁忙程度;同时计算任务迁移开销,避免频繁跨核心切换造成缓存失效、上下文切换损耗,只有收益大于代价才允许迁移。
1.7 任务迁移约束
CPU 亲和性绑定、实时高优先级任务、独占硬件资源进程,默认禁止跨核心迁移,保障核心业务运行稳定性。
二、环境准备
2.1 软硬件环境规格
| 环境类别 | 版本与配置参数 |
|---|---|
| 操作系统 | Ubuntu 20.04/22.04 LTS 64 位 |
| 内核版本 | Linux 5.15、6.1、6.6 长期稳定版,主流商用生产内核 |
| 硬件架构 | x86_64 多核 CPU,建议 4 核及以上,满足 SMP 均衡观测条件 |
| 内存硬盘 | 8G 及以上内存,50G 空闲存储,适配内核编译压测 |
| 编译工具 | gcc 9.4+、make、binutils、libelf-dev |
| 调试分析工具 | perf、htop、mpstat、trace-cmd、ftrace、gdb |
2.2 开发环境部署命令
# 更新软件源并安装全套编译调试依赖
sudo apt update && sudo apt install -y build-essential libncurses-dev bison flex libssl-dev libelf-dev gdb perf htop sysstat
2.3 内核源码获取与编译配置
# 下载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_SMP=y # 开启多核对称处理
CONFIG_SCHED_MC=y # 多核核心间负载均衡
CONFIG_SCHED_SMT=y # 超线程核心均衡调度
CONFIG_FTRACE=y # 跟踪Push/Pull函数调用
CONFIG_DEBUG_KERNEL=y # 内核调试开关
2.4 核心源码路径定位
负载均衡 Push/Pull 核心逻辑全部存放以下文件
kernel/sched/sched.h # 调度域、运行队列、负载结构体定义
kernel/sched/fair.c # CFS普通进程Push/Pull均衡主逻辑
kernel/sched/sched.c # 全局均衡入口、触发调度流程
三、应用场景
Push 与 Pull 协同负载均衡机制是多核设备算力调度的基础支撑。在后端云服务器场景,大量 Web 请求、数据库查询进程随机抢占 CPU,极易出现单核心满载其余核心闲置,Push 主动分流过载进程、Pull 抓取闲置算力,平稳整机负载,保障网站访问响应速度。工业嵌入式多核控制器中,电机驱动、数据采集、逻辑运算多类实时任务混跑,双模型均衡避免控制任务扎堆卡顿,保证设备运行精度。容器集群环境下,容器进程打散分配至不同 CPU 核心,依托 Push/Pull 动态调整负载,提升整机资源利用率。同时在多核虚拟机、车载计算平台中,该机制持续平衡核心负载,减少调度抖动,最大化发挥多核硬件性能。
四、实际案例与源码原理剖析
4.1 核心结构体定义代码
// kernel/sched/sched.h
// CPU单个运行队列结构体
struct rq {
unsigned int cpu; // 归属CPU编号
struct sched_avg avg; // 核心负载统计平均值
struct cfs_rq cfs; // CFS普通进程运行队列
struct rt_rq rt; // 实时进程运行队列
struct dl_rq dl; // Deadline实时进程队列
unsigned long nr_running; // 队列就绪任务总数
};
// 调度域结构体,负载均衡作用边界
struct sched_domain {
struct sched_domain *parent; // 上层调度域
struct sched_group *groups; // 域内CPU分组
int imbalance_pct; // 判定CPU过载阈值百分比
unsigned int span; // 域内包含所有CPU掩码
};
// 调度分组,统计组内整体负载
struct sched_group {
struct sched_group *next;
unsigned long group_load; // 分组总负载权重
unsigned int cpu_capacity; // 分组CPU算力上限
};
代码说明:rq 记录单核心负载状态,sched_domain 划定均衡范围,两个结构体是 Push、Pull 任务迁移的数据基础。
4.2 Pull 拉取模式核心源码实现
空闲 CPU 主动拉取高负载任务,内核核心函数load_balance为均衡统一入口,Pull 是空闲核主动发起均衡。
// kernel/sched/fair.c
static int load_balance(int this_cpu, struct rq *this_rq,
int idle, struct sched_domain *sd,
enum cpu_idle_type idle_type)
{
struct sched_group *group;
struct rq *busyrq;
int nr_moved = 0;
unsigned long imbalance;
// 遍历调度域内所有高负载CPU分组
for (group = sd->groups; group; group = group->next)
{
// 计算分组负载差值,判断是否存在负载倾斜
imbalance = group->group_load - this_rq->avg.load_avg;
if (imbalance <= 0)
continue;
// 找到组内负载最高的运行队列
busyrq = find_busiest_queue(group, this_cpu);
if (!busyrq)
continue;
// 空闲CPU从繁忙队列拉取可迁移任务
nr_moved += pull_task(this_rq, busyrq, this_cpu);
}
return nr_moved;
}
代码作用:空闲 CPU 触发 Pull 均衡,遍历周边繁忙核心,计算负载差值,合法任务直接抓取至本地队列,快速填补空闲算力。
4.3 Push 推送模式核心源码实现
过载 CPU 主动向外推送任务,本地负载超标时主动扫描空闲核心,发起任务推送分流。
// kernel/sched/fair.c
static void push_task(struct rq *src_rq, struct rq *dst_rq, struct task_struct *p)
{
// 移出源过载CPU运行队列
dequeue_task(src_rq, p, DEQUEUE_SLEEP);
// 修改任务归属CPU标记
set_task_cpu(p, dst_rq->cpu);
// 加入目标空闲CPU运行队列
enqueue_task(dst_rq, p, ENQUEUE_WAKEUP);
// 触发目标队列调度刷新
resched_curr(dst_rq);
}
// 过载CPU主动推送均衡主逻辑
static void active_push_balance(struct rq *rq, struct sched_domain *sd)
{
struct sched_group *group;
struct rq *idle_rq;
struct task_struct *p;
// 负载超过阈值,检索域内空闲CPU
idle_rq = find_idle_queue(sd, rq->cpu);
if (!idle_rq)
return;
// 筛选可迁移普通就绪任务
p = pick_push_task(rq, idle_rq);
if (p)
{
// 执行任务推送分流
push_task(rq, idle_rq, p);
}
}
代码解析:Push 由繁忙 CPU 主动执行,筛选无绑定、低迁移损耗任务,推送至空闲核心,快速降低本地 CPU 负载压力。
4.4 Push 与 Pull 协同调度判断函数
内核根据 CPU 当前状态自动选择均衡模式,实现双模型协同工作:
// kernel/sched/fair.c
void check_for_migration(struct rq *rq)
{
struct sched_domain *sd = rq->sd;
// CPU处于空闲状态,启用Pull拉取模式
if (rq->nr_running == 0)
{
load_balance(rq->cpu, rq, 1, sd, CPU_IDLE);
return;
}
// CPU负载超标,启用Push推送模式
if (rq->avg.load_avg > sd->imbalance_pct)
{
active_push_balance(rq, sd);
}
}
逻辑说明:空闲核走 Pull 拉取,过载核走 Push 推送,两种模式互不冲突,全方位平衡多核负载。
4.5 编写压测程序制造负载倾斜
编写多线程 CPU 压测代码,人为造出单核高负载、其余核空闲场景,观测均衡效果
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
#include <sys/sysinfo.h>
// 死循环占用CPU资源
void *cpu_stress(void *arg)
{
while(1)
{
// 空循环持续消耗CPU算力
}
return NULL;
}
int main()
{
pthread_t tid;
int cpu_core = get_nprocs();
printf("系统CPU核心总数:%d\n",cpu_core);
// 仅创建2个压测线程,绑定单核心产生负载倾斜
pthread_create(&tid,NULL,cpu_stress,NULL);
pthread_create(&tid,NULL,cpu_stress,NULL);
while(1)
{
sleep(2);
}
return 0;
}
编译与运行命令:
gcc stress.c -o stress -lpthread
# 后台运行压测程序
./stress &
4.6 命令行观测多核负载均衡状态
# 每秒输出一次多核CPU负载状态
mpstat -P ALL 1
# 图形化实时查看CPU核心占用
htop
4.7 Ftrace 跟踪 Push/Pull 内核函数调用
# 挂载调试文件系统
sudo mount -t debugfs none /sys/kernel/debug
# 清空跟踪日志
sudo echo > /sys/kernel/debug/tracing/trace
# 筛选均衡核心函数
sudo echo load_balance >> /sys/kernel/debug/tracing/set_ftrace_filter
sudo echo active_push_balance >> /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 cat /sys/kernel/debug/tracing/trace
日志可直观看到空闲核 Pull、过载核 Push 函数交替调用,验证双模型协同运行。
五、常见问题与解答
Q1 Push 推送和 Pull 拉取两种均衡模式优先级如何划分?
答:空闲 CPU 优先执行 Pull 拉取主动抢任务,CPU 过载时触发 Push 推送分流任务;周期性全局均衡同时兼容两种模式,不存在固定优先级,根据核心实时负载自动切换。
Q2 绑定 CPU 亲和性的进程,是否会参与 Push/Pull 任务迁移?
答:不会。设定亲和性的进程被锁定指定核心,内核迁移逻辑会直接跳过该类任务,避免业务进程随意切换核心引发异常。
Q3 频繁任务迁移反而降低性能,内核如何规避无效迁移?
答:内核计算缓存失效、上下文切换等迁移代价,仅当负载均衡收益远大于损耗时才允许迁移;同时限制单轮均衡迁移任务数量,防止频繁切换拖慢整机性能。
Q4 调度域层级划分异常,会造成负载无法均衡吗?
答:会。调度域跨度配置过小,只能小范围均衡;跨度过大则均衡开销飙升,合理的域层级是 Push/Pull 正常协同的前提。
Q5 实时 RT、DL 调度任务是否参与普通 CFS 负载均衡?
答:不参与。实时任务拥有独立运行队列,Push/Pull 均衡仅针对普通 CFS 进程,保障高优先级实时任务不受负载迁移干扰。
六、实践建议与最佳实践
-
性能调优实操技巧 业务压测排查负载倾斜时,先用 mpstat、htop 定位满载与空闲核心,再通过 ftrace 跟踪 Push/Pull 函数调用,快速判断均衡机制是否正常触发。
-
业务进程部署规范 核心时延敏感业务尽量绑定固定 CPU 核心,规避任务频繁迁移;批量无状态后台进程不设置亲和性,交由 Push/Pull 自动均衡打散,提升资源利用率。
-
内核参数优化建议 根据设备核心数量调整过载阈值 imbalance_pct,高并发服务器适当调高阈值减少均衡次数;嵌入式设备调低阈值,及时平衡少量负载偏差。
-
故障排查顺序 负载不均排查顺序:确认 SMP 多核开关→查看调度域划分→跟踪 Push/Pull 函数调用→检查进程亲和性约束→评估迁移损耗,逐层定位问题根源。
-
定制调度开发规范 二次开发均衡逻辑时,保留 Push 主动分流、Pull 主动抓取基础架构,不要删除双模型协同逻辑,避免出现单核负载堆积无法疏导的问题。
七、总结与应用延伸
本文完整拆解 Linux 多核负载均衡Push 推送、Pull 拉取协同工作模型,从基础概念、环境搭建、结构体源码、核心调度函数、压测试验、日志跟踪到工程优化全面讲解。Push 由过载 CPU 主动分流任务缓解负载压力,Pull 由空闲 CPU 主动抓取任务填充算力空缺,二者相互配合,实现 SMP 系统多核负载动态平衡。
这套机制是 Linux 多核调度的核心基础,支撑云服务器、嵌入式工控、自动驾驶、容器虚拟化等主流场景稳定运行。负载均衡直接决定整机 CPU 利用率、进程调度时延、业务运行稳定性。掌握 Push/Pull 触发规则、任务迁移判定、调度域协作逻辑,既能解决日常运维中 CPU 负载倾斜、程序卡顿问题,也可支撑内核裁剪、调度策略定制、学术论文研究工作。
建议读者利用本文压测代码与跟踪命令,手动制造负载偏差观测均衡变化,修改内核过载阈值参数对比性能差异,将双模型均衡原理落地到实际项目性能优化中。
openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构
更多推荐

所有评论(0)