Linux 负载均衡的 cpu_load:CPU 负载历史的跟踪
简介在多核 Linux 服务器、嵌入式集群、云计算虚拟化平台中CPU 负载均衡是调度子系统的核心能力。内核通过负载均衡将任务合理分发到各个 CPU 核心避免部分核心满载、部分核心空闲的资源浪费问题。早期 Linux 内核在实现负载均衡时若仅采用瞬时负载作为判断依据很容易被突发短时任务、IO 抖动、临时进程唤醒等瞬时高负载干扰造成任务盲目迁移、CPU 频繁上下文切换、系统抖动加剧。为解决该问题内核在每个 CPU 的运行队列struct rq中引入了cpu_load [] 数组专门用于分段记录 CPU 不同时间维度的历史负载。该数组基于指数衰减算法保留近一段时间内的负载趋势让负载均衡逻辑能够参考长期稳态负载而非单一瞬时值大幅提升负载判断的准确性与系统稳定性。cpu_load 是理解 Linux 传统 CPU 级负载均衡的入门核心广泛应用于服务器调优、嵌入式实时系统优化、虚拟化 QoS 配置、内核调度策略裁剪等场景。对于 Linux 运维工程师、嵌入式开发人员、内核研发、高校课题研究者而言吃透 cpu_load 数组的存储结构、更新逻辑、取值规则、算法原理不仅能深度理解多核调度架构还能精准排查负载不均、任务频繁漂移、调度抖动等线上问题同时可为调度相关论文、技术报告、内核二次开发提供底层理论与源码支撑。本文从概念、环境、源码实操、问题排查到工程实践全维度拆解内容基于真实内核代码兼顾新手入门与深度调研需求。一、核心概念与术语解析1.1 负载均衡基础概念运行队列 rqLinux 为每一个物理 CPU 核心维护一个独立运行队列struct rq所有就绪态任务都会挂载到对应 CPU 的 rq 中。负载均衡的本质就是在多个 rq 之间迁移任务抹平 CPU 之间的负载差值。瞬时负载某一个时钟节拍tick下当前 CPU 就绪队列中所有可运行任务的权重总和。瞬时负载波动大突发任务会造成数值瞬间冲高无法反映 CPU 真实压力。历史负载通过算法对连续多个 tick 的负载做加权计算形成带有时间衰减特性的负载值体现 CPU一段时间内的稳态压力这也是cpu_load数组的核心作用。1.2 cpu_load 数组核心定义在内核Linux 5.4 及更早主流版本PELT 普及前中struct rq内部定义固定长度的cpu_load数组// kernel/sched/sched.h #define CPU_LOAD_IDX_MAX 5 struct rq { /* 省略其他成员运行任务计数、调度实体、时钟、锁等 */ unsigned int nr_running; // 当前就绪任务数 unsigned long cpu_load[CPU_LOAD_IDX_MAX]; // 核心多级历史负载数组 };宏CPU_LOAD_IDX_MAX 5代表数组固定包含5 个负载样本分别对应不同时间跨度的历史负载。数组下标越小代表统计周期越短、越贴近当前瞬时负载下标越大统计周期越长、越偏向长期稳态负载。数组下标统计周期 (tick)负载含义00当前 tick近似瞬时负载实时性最高18 tick短周期历史负载232 tick中短周期历史负载364 tick中长周期历史负载4128 tick长周期历史负载抗抖动能力最强1.3 指数衰减负载算法 CALC_LOADcpu_load 每一项数值并非简单取平均值而是采用指数滑动平均算法内核通过CALC_LOAD宏实现保证旧负载随时间逐步衰减新负载权重更高。核心公式new_load (old_load * exp active_load) / FIXED_1old_load上一时刻该维度的历史负载active_load当前 tick 的瞬时活跃负载exp衰减系数由统计周期决定FIXED_1定点运算基数内核固定为 2048用于浮点转定点提升计算效率。该算法的优势新负载实时融入统计结果老旧负载缓慢降低影响天然过滤掉毫秒级瞬时毛刺非常适合负载均衡场景。1.4 关键联动函数与术语scheduler_tick时钟节拍中断函数每 1/HZ 秒触发一次cpu_load 的更新入口update_cpu_load遍历并刷新 cpu_load 数组所有成员的核心函数find_busiest_queue负载均衡时读取 cpu_load 数组对比各个 CPU 负载寻找最忙 / 最空闲队列调度域 sched_domain内核将多核 CPU 划分为层级调度域负载均衡在调度域内执行不同层级会选用不同下标的 cpu_load 做判断。二、环境准备2.1 软硬件与版本要求本文基于传统 cpu_load 负载架构非 PELT 单实体负载推荐使用经典长期支持内核避免新版 PELT 机制干扰学习分类版本 / 配置要求操作系统Ubuntu 18.04 / Ubuntu 20.04 64 位Linux 内核Linux 4.19、Linux 5.4LTS 版本cpu_load 逻辑完整、注释丰富硬件架构x86_64 多核 CPU至少 4 核才能观测负载均衡行为内存≥4G编译工具gcc 7.5、make、binutils、libncurses-dev、bison、flex调试工具gdb、ftrace、trace-cmd、perf、htop、mpstat辅助工具stress压力测试工具用于模拟高负载说明Linux 5.8 之后内核全面强化 PELT 机制cpu_load 作用被弱化建议严格选用 4.19/5.4 版本做实验。2.2 环境搭建与内核编译配置1. 安装基础依赖与工具# 更新软件源并安装编译、调试、压测全套依赖 sudo apt update sudo apt install -y \ build-essential libncurses-dev bison flex libssl-dev libelf-dev \ gdb trace-cmd stress sysstat2. 下载指定版本内核源码以 Linux 4.19 LTS 为例wget https://cdn.kernel.org/pub/linux/kernel/v4.x/linux-4.19.tar.xz tar -xf linux-4.19.tar.xz cd linux-4.193. 内核配置保留原生调度、开启调试# 继承当前系统内核配置 cp /boot/config-$(uname -r) .config make menuconfig在图形化配置界面开启以下选项保证调试与原生 cpu_load 逻辑可用CONFIG_DEBUG_KERNELy # 内核总调试开关 CONFIG_SCHED_DEBUGy # 调度器调试 CONFIG_FTRACEy # 函数跟踪观测cpu_load更新流程 CONFIG_SCHED_MCy # 多核负载均衡 CONFIG_SCHED_SMPy # SMP多核支持必选关闭 PELT 相关增强保证传统 cpu_load 生效 取消CONFIG_SMP_PELT相关选项保留经典 CPU 级负载统计。4. 编译、安装并重启内核# 多线程编译nproc为CPU核心数 make -j$(nproc) sudo make modules_install sudo make install sudo update-grub # 重启系统在GRUB菜单选择刚编译的4.19内核 sudo reboot5. 源码路径定位cpu_load 相关核心文件后续代码均来自以下路径kernel/sched/sched.h # rq结构体、cpu_load数组、CALC_LOAD宏定义 kernel/sched/sched.c # update_cpu_load、scheduler_tick 主逻辑 kernel/sched/fair.c # 负载均衡、读取cpu_load做任务迁移逻辑三、应用场景300 字cpu_load 历史负载跟踪机制主要落地于多核服务器、工业嵌入式 Linux、虚拟化 KVM 集群三大场景。在后端业务服务器中大量短连接请求、临时脚本会产生瞬时负载cpu_load 依靠多级历史负载过滤毛刺避免任务频繁跨核迁移降低切换开销。工业嵌入式多核控制器中周期性采集、通信、控制任务交替运行短时 IO 阻塞会造成单 CPU 瞬时空闲cpu_load 的长周期负载值能识别真实稳态负载保证控制任务不被错误迁移。KVM 虚拟化场景下宿主机通过 cpu_load 统计物理核长期负载合理调度虚拟机 vCPU防止单核心瞬时峰值导致虚拟机卡顿。该机制用轻量化算法实现负载趋势判断无需复杂计算兼顾性能与准确性是传统多核 Linux 负载均衡不可或缺的基础组件。四、实际案例与步骤源码 实操 命令4.1 基础源码解析数据结构与核心宏4.1.1 rq 中 cpu_load 数组完整定义文件kernel/sched/sched.h截取关键代码并添加工程化注释/* * 每个CPU私有运行队列SMP架构下每个CPU对应一个rq实例 * 下面仅保留与cpu_load相关核心字段其余成员省略 */ #define CPU_LOAD_IDX_MAX 5 // cpu_load数组长度固定5个维度 struct rq { raw_spinlock_t lock; // 运行队列自旋锁保护并发访问 unsigned int nr_running; // 当前队列中就绪可运行任务总数 /* * 多级历史负载数组共5项对应不同时间窗口的负载加权值 * 下标0当前瞬时负载下标4最长周期历史负载 */ unsigned long cpu_load[CPU_LOAD_IDX_MAX]; /* 省略调度实体、时钟、带宽、中断、DL/RT调度队列等 */ }; // 定点运算基数将浮点运算转为整数运算提升内核执行效率 #define FIXED_1 2048代码说明cpu_load数组是 rq 的内置成员每个 CPU 独立存储互不干扰。数组长度固定为 5内核代码中所有负载均衡逻辑都会按下标选取对应周期的负载值。4.1.2 核心计算宏 CALC_LOAD文件kernel/sched/sched.h指数衰减计算宏cpu_load 数值更新的数学基础/* * 指数滑动平均负载计算宏 * load: 上一周期的历史负载值 * exp: 衰减系数不同时间窗口使用不同系数 * active: 当前tick的瞬时活跃负载 */ #define CALC_LOAD(load, exp, active) \ ( ((load) * (exp)) (active) * (FIXED_1 - (exp)) ) / FIXED_1代码作用该宏统一实现负载衰减计算。旧负载乘以衰减系数新瞬时负载乘以剩余权重求和后除以定点基数得到新的历史负载。整个过程全整数运算无浮点开销适配内核硬实时要求。4.2 核心函数update_cpu_load 负载更新逻辑该函数是cpu_load 数组的唯一更新入口由时钟节拍中断周期性调用。 文件kernel/sched/sched.c/* * update_cpu_load - 更新单个CPU的多级历史负载数组 * rq: 当前CPU对应的运行队列 * active: 当前tick的瞬时活跃负载就绪任务总权重 */ static void update_cpu_load(struct rq *rq, unsigned long active) { int i; // 预定义5个下标对应的衰减系数与时间窗口一一对应 const unsigned long cpu_load_exp[CPU_LOAD_IDX_MAX] { FIXED_1, // 下标0无衰减等价瞬时负载 1892, // 下标18 tick 周期衰减系数 1536, // 下标232 tick 周期衰减系数 1280, // 下标364 tick 周期衰减系数 1024 // 下标4128 tick 长周期衰减系数 }; /* 从短周期到长周期依次更新所有cpu_load项 */ for (i 0; i CPU_LOAD_IDX_MAX; i) { rq-cpu_load[i] CALC_LOAD(rq-cpu_load[i], cpu_load_exp[i], active); } }代码解析数组cpu_load_exp为每个时间维度配置专属衰减系数周期越长系数越小旧负载衰减越快循环遍历 5 个下标逐个调用CALC_LOAD完成负载刷新下标 0 系数等于FIXED_1旧负载权重拉满结果基本等同于瞬时负载。4.3 调用链路时钟节拍触发负载更新scheduler_tick是系统时钟中断函数每一次 tick 都会触发负载更新完整调用链路// kernel/sched/sched.c void scheduler_tick(void) { struct rq *rq this_rq(); // 获取当前CPU的运行队列 unsigned long active_load; // 计算当前瞬时活跃负载就绪任务数加权总和 active_load calc_active_load(rq); // 核心调用刷新当前CPU的cpu_load数组 update_cpu_load(rq, active_load); /* 省略时间片轮转、抢占判断、调度实体更新等逻辑 */ }运行逻辑系统时钟按 HZ 频率默认 100Hz每 10ms 一次触发中断计算瞬时负载后立刻更新多级历史负载保证 cpu_load 数据持续新鲜。4.4 负载均衡场景读取 cpu_load 做任务迁移负载均衡模块在挑选 “繁忙 CPU” 和 “空闲 CPU” 时会根据调度域层级选择不同下标的 cpu_load// kernel/sched/fair.c /* * 负载均衡时获取指定CPU的目标历史负载 * rq: 目标CPU运行队列 * sd: 调度域决定选用哪个时间窗口的负载 */ static unsigned long get_cpu_load(struct rq *rq, struct sched_domain *sd) { // 高层调度域跨NUMA、跨CPU组选用长周期负载抗抖动 if (sd-level SD_LEVEL_SIBLING) return rq-cpu_load[4]; // 128 tick 长周期负载 // 同组CPU调度域选用中周期负载 else if (sd-level SD_LEVEL_SIBLING) return rq-cpu_load[2]; // 32 tick 中周期负载 // 同核心线程调度域选用短周期负载追求实时性 return rq-cpu_load[1]; // 8 tick 短周期负载 }代码作用这是 cpu_load 落地到负载均衡的关键。内核不会固定使用某一个负载值而是根据负载均衡的范围动态选择时间窗口跨大集群均衡看重长期稳态同核心均衡看重短期变化最大化均衡效果。4.5 实操 1压力模拟 观测 CPU 负载变化使用stress工具模拟高负载配合mpstat观察多核负载分布验证负载均衡与 cpu_load 联动。步骤 1后台运行压力测试绑定 CPU 密集任务# 模拟4个CPU密集型进程打满4核CPU stress --cpu 4 步骤 2实时查看每个 CPU 核心的瞬时负载# 每1秒输出一次多核负载统计 mpstat -P ALL 1现象说明初期任务集中在少数 CPU瞬时负载差异大几秒后负载均衡生效任务逐步分散到各个核心各 CPU 使用率趋于平均背后正是 cpu_load 历史负载在持续判断并迁移任务。步骤 3停止压测# 终止所有stress进程 pkill stress4.6 实操 2Ftrace 跟踪 cpu_load 更新函数使用内核 ftrace 跟踪update_cpu_load调用直观验证每一次 tick 都会更新负载数组命令可直接复制执行# 1. 挂载debugfs内核调试文件系统 sudo mount -t debugfs none /sys/kernel/debug # 2. 清空历史跟踪日志 sudo echo /sys/kernel/debug/tracing/trace # 3. 设置需要跟踪的核心函数 sudo echo update_cpu_load /sys/kernel/debug/tracing/set_ftrace_filter sudo echo scheduler_tick /sys/kernel/debug/tracing/set_ftrace_filter # 4. 开启函数跟踪 sudo echo function /sys/kernel/debug/tracing/current_tracer sudo echo 1 /sys/kernel/debug/tracing/tracing_on # 5. 另开终端执行压测制造负载 stress --cpu 2 # 6. 查看实时跟踪日志观测函数调用频率 sudo cat /sys/kernel/debug/tracing/trace # 7. 停止跟踪收尾清理 sudo echo 0 /sys/kernel/debug/tracing/tracing_on sudo pkill stress日志解读日志中会持续打印scheduler_tick→update_cpu_load调用记录调用间隔约 10ms对应默认 100Hz 时钟证明负载数组按节拍稳定更新。4.7 实操 3编写简易内核模块读取 cpu_load 数值编写测试内核模块直接读取当前 CPU 的 5 项 cpu_load 值直观查看多级历史负载差异。步骤 1编写模块代码cpuload_test.c#include linux/init.h #include linux/module.h #include linux/kernel.h #include linux/sched.h #include linux/cpu.h MODULE_LICENSE(GPL); MODULE_AUTHOR(Linux Engineer); MODULE_DESCRIPTION(Read rq-cpu_load array demo); static int __init cpuload_test_init(void) { struct rq *rq; int cpu, idx; // 遍历所有在线CPU for_each_online_cpu(cpu) { rq cpu_rq(cpu); // 获取指定CPU的运行队列 printk( CPU %d cpu_load array \n, cpu); // 打印5个维度的历史负载 for (idx 0; idx CPU_LOAD_IDX_MAX; idx) { printk(cpu_load[%d] %lu\n, idx, rq-cpu_load[idx]); } } return 0; } static void __exit cpuload_test_exit(void) { printk(cpu_load test module unloaded\n); } module_init(cpuload_test_init); module_exit(cpuload_test_exit);步骤 2编写编译文件Makefileobj-m cpuload_test.o KERNELDIR ? /lib/modules/$(shell uname -r)/build PWD : $(shell pwd) all: $(MAKE) -C $(KERNELDIR) M$(PWD) modules clean: $(MAKE) -C $(KERNELDIR) M$(PWD) clean步骤 3编译、加载模块并查看输出# 编译模块 make # 加载内核模块 sudo insmod cpuload_test.ko # 查看内核打印读取cpu_load数值 dmesg | tail -20 # 卸载模块 sudo rmmod cpuload_test make clean实验结论正常空闲状态下cpu_load[0]瞬时负载波动略大cpu_load[4]长周期负载数值最平稳完全符合历史负载的设计目标。五、常见问题与解答Q1为什么内核要设计 5 个不同周期的 cpu_load不直接用平均值解答简单平均值无法区分新旧负载瞬时毛刺会长期影响结果。5 个分级负载对应不同均衡场景短周期用于同核心线程均衡长周期用于跨 NUMA、跨 CPU 组均衡。配合指数衰减算法既保留实时性又过滤抖动兼顾多场景需求。Q2短时突发高负载时cpu_load [0] 很高但负载均衡不迁移任务是什么原因解答负载均衡高层调度域默认读取cpu_load[4]长周期负载。突发负载只会拉高瞬时下标 0长周期负载变化平缓内核判定 CPU 并非长期繁忙因此不执行迁移这正是该机制防抖动的设计初衷。Q3执行 insmod 加载模块时报错unknown symbol CPU_LOAD_IDX_MAX解答CPU_LOAD_IDX_MAX是内核内部宏未导出到模块符号表。解决方案一是将代码整合进内核源码一起编译二是在模块中手动定义#define CPU_LOAD_IDX_MAX 5。Q4ftrace 看不到 update_cpu_load 函数调用解答1. 检查内核是否开启CONFIG_FTRACE和CONFIG_SCHED_DEBUG2. 确认当前内核为 4.19/5.4 传统版本新版 PELT 内核弱化了该函数3. 关闭nohz_full无滴答内核无滴答模式会停止周期性 tick函数不再触发。Q5多核 CPU 负载明显不均但内核不做任务迁移解答优先查看两点① 调度域配置是否关闭负载均衡② 内核选用了长周期 cpu_load短时负载不均不会触发迁移③ 任务被 CPU 亲和性pin绑定到指定核心亲和性优先级高于负载均衡。六、实践建议与最佳实践6.1 源码研读与调试技巧分层阅读源码先看懂CALC_LOAD数学逻辑再跟踪update_cpu_load更新流程最后梳理get_cpu_load在负载均衡中的取值逻辑由点到线再到面。结合 ftracegdb 联合调试ftrace 看函数调用链路gdb 断点update_cpu_load实时查看 5 个 cpu_load 数值变化比静态读代码理解更深刻。区分内核版本4.19 及更早版本以 cpu_load 为主5.8 版本以 PELT 单实体负载为主调研、论文写作需明确版本边界。6.2 线上系统调优最佳实践高并发业务服务器不要盲目调小负载均衡触发阈值充分利用cpu_load长周期特性容忍短时负载波动减少任务漂移。嵌入式实时系统若对抖动极度敏感可在内核编译时调高长周期负载权重进一步弱化瞬时负载影响。CPU 亲和性配合核心业务进程建议绑定 CPU 核心避免负载均衡频繁迁移辅助进程交由 cpu_load 均衡调度。6.3 性能优化与避坑禁止在高频路径中频繁读取cpu_load数组该数组随 tick 持续变化频繁读取会触发 rq 锁竞争影响性能。无滴答nohz内核场景下tick 停止后update_cpu_load不再周期性执行cpu_load 数值会冻结高负载均衡场景建议关闭 nohz。自定义调度策略时不要删除 cpu_load 数组可基于现有 5 级负载扩展规则复用成熟的衰减算法降低开发风险。6.4 问题排查规范遇到负载不均、任务漂移、调度抖动问题排查顺序检查任务 CPU 亲和性用 mpstat、dmesg 观察 cpu_load 多级负载数值用 ftrace 跟踪负载均衡与 update_cpu_load 调用确认调度域、负载均衡开关配置。七、总结与应用延伸本文完整拆解了 Linux 传统负载均衡体系中cpu_load 数组的设计思想、数据结构、指数衰减算法、更新链路、负载取值规则并配套内核源码、实操命令、内核测试模块、调试手段覆盖从理论到落地的全流程。cpu_load 的核心价值是通过多级历史负载 指数滑动平均算法把单一瞬时负载升级为可反映长期趋势的负载体系从底层解决了多核负载均衡被瞬时任务干扰的行业痛点。5 个不同时间窗口的负载设计精准匹配 Linux 分层调度域架构让不同范围的负载均衡都能选用最合适的负载参考值在性能、实时性、稳定性之间做到平衡。从工程应用角度该机制至今仍大量运行在政企服务器、工业嵌入式设备、老旧虚拟化集群中从学习与科研角度cpu_load 是理解 Linux SMP 多核调度、负载统计、调度域架构的经典案例其 “历史数据加权衰减” 的设计思想也可迁移到网络流量统计、系统性能监控等其他模块。建议读者基于本文的内核模块、ftrace 命令、压测脚本反复复现实验修改衰减系数、增减 cpu_load 数组下标观察负载均衡行为变化真正做到吃透原理、灵活运用。在实际项目中结合业务特性合理利用 cpu_load 的抗抖动能力能够显著提升多核 Linux 系统的运行稳定性与资源利用率。