Linux 负载均衡的 cpu_util:CPU 利用率的精准评估
简介在多核 Linux 系统中CPU 负载均衡、动态调频调压DVFS是保障系统吞吐、响应延迟与功耗平衡的两大核心能力。传统top、mpstat等工具读取/proc/stat计算的 CPU 使用率仅做宏观时间片统计存在滞后性、粒度粗的问题无法满足内核调度决策的精细化需求。为此Linux 内核引入cpu_util核心底层为util_avg指标依托 PELTPer-Entity Load Tracking实体级负载追踪算法以滑动加权方式实时量化 CPU 及调度实体的真实资源占用比例。cpu_util 贯穿内核两大核心模块一是多核负载均衡调度器依靠该指标判断各 CPU 核心的繁忙程度决策任务迁移避免部分核心过载、部分核心空闲的负载倾斜问题二是CPU 频率调节schedutil调频策略直接读取 cpu_util 数值动态拉升或降低主频兼顾性能与功耗。无论是服务器集群、嵌入式终端、工控设备还是移动异构多核平台cpu_util 都是底层调度与能效管理的核心数据源。对于 Linux 内核研发、嵌入式开发、性能调优工程师而言吃透 cpu_util 的计算逻辑、更新时机、数据流转与应用规则是理解多核调度、定位负载不均、优化调度延迟、调优功耗策略的必备技能。本文从基础概念、环境搭建、源码解析、实操验证、问题排查到工程实践全维度展开配套可直接运行的代码、调试命令内容可支撑技术报告、毕业论文撰写以及线上线下故障排查工作。一、核心概念与术语解析1.1 区分三类负载指标很多开发者会混淆load_avg、util_avg(cpu_util)与系统负载这里做明确界定也是理解后续内容的基础load_avg调度负载值和任务优先级、权重强相关统计任务处于可运行态的时间占比主要用于 CFS 调度器的公平调度与传统负载均衡偏向 “任务竞争激烈程度”。util_avg / cpu_utilCPU 利用率指标与任务优先级无关仅统计调度实体在 CPU 上实际运行的时间占比纯粹表征硬件资源消耗是本文研究核心。内核中对外暴露的cpu_util本质就是运行队列聚合后的util_avg。系统 Load/proc/loadavg展示的 1/5/15 分钟平均可运行任务数是系统全局负载宏观体现不用于内核实时调度决策。1.2 PELT 实体级负载追踪算法cpu_util 的底层计算依赖 PELT 算法该算法采用指数加权滑动平均模型特点如下时间窗口默认采用固定时间窗口对历史数据做指数衰减越久远的运行记录权重越低保证指标实时性统计粒度以调度实体sched_entity为最小统计单元再逐级聚合到运行队列cfs_rq、CPU 运行队列rq核心优势支持周期性时钟Tick与无时钟NO_HZ两种内核模式在休眠、低功耗场景下依然能精准更新利用率。1.3 核心结构体与关键字段1.3.1 调度实体 sched_entity每个 CFS 任务对应一个sched_entity内置负载统计结构体// kernel/sched/sched.h struct sched_entity { /* CFS调度核心字段虚拟运行时间 */ u64 vruntime; /* PELT负载统计结构体 */ struct sched_avg avg; // 其他CFS相关字段省略 };1.3.2 负载统计结构体 sched_avg承载单个任务的负载与利用率数据是 cpu_util 的数据源// kernel/sched/pelt.h struct sched_avg { /* 负载累加值用于计算load_avg */ u64 load_sum; /* 利用率累加值用于计算util_avg */ u64 util_sum; /* 可运行状态时间累加值 */ u64 runnable_sum; /* 最终输出平均负载、平均利用率 */ u32 load_avg; u32 util_avg; u32 runnable_avg; /* 上一次更新时间戳 */ u64 last_update_time; };关键字段说明util_avg即为单个任务的 CPU 利用率多个任务聚合后得到cfs_rq-avg.util_avg也就是内核接口中常说的cpu_util。1.3.3 CFS 运行队列 cfs_rq每个 CPU 核心维护独立的cfs_rq聚合当前核心所有 CFS 任务的负载数据// kernel/sched/fair.h struct cfs_rq { struct rb_root tasks_timeline; /* 整个运行队列的聚合负载与利用率 */ struct sched_avg avg; // 队列任务计数、节流控制等字段省略 };1.4 调度域与负载均衡基础多核系统中内核通过sched_domain调度域划分 CPU 拓扑NUMA 节点、CPU 簇、物理核心负载均衡分为三类触发场景周期性均衡内核定时器定时扫描调度域内所有 CPU对比 cpu_util迁移过载核心的任务空闲均衡CPU 进入 idle 空闲态时主动拉取其他繁忙核心的任务唤醒均衡新任务创建、休眠任务唤醒时依据 cpu_util 选择最优 CPU 绑定。1.5 调度调频 schedutilLinux 主流 CPU 调频策略schedutil不再依赖传统的硬件采样直接读取cfs_rq-avg.util_avg根据 CPU 利用率阶梯式调整主频利用率高则升频保障性能利用率低则降频降低功耗cpu_util 是调频决策的唯一依据。二、环境准备2.1 软硬件环境清单本文基于主流长期支持内核开发调试兼容物理机与虚拟机具体配置如下环境分类版本 / 配置要求补充说明操作系统Ubuntu 20.04 / 22.04 64 位推荐原生物理机虚拟机需开启多核Linux 内核5.15 LTS、6.1 LTS、6.6 LTS三个版本 PELT、cpu_util 逻辑完全一致硬件架构x86_64 多核 CPU≥4 核单核无法验证负载均衡效果编译工具gcc 9.4、make、binutils用于编译内核、编写测试程序调试工具ftrace、perf、gdb、trace-cmd跟踪 cpu_util 更新函数、采样利用率依赖库libncurses-dev、bison、flex、libelf-dev内核编译必备依赖2.2 环境部署步骤2.2.1 安装基础编译与调试工具执行以下命令批量安装依赖可直接复制运行# 更新软件源 sudo apt update sudo apt upgrade -y # 安装编译、调试全套工具 sudo apt install build-essential libncurses-dev bison flex \ libssl-dev libelf-dev gdb trace-cmd perf -y2.2.2 下载并编译 Linux 6.1 LTS 内核该版本为工业、服务器场景主流版本源码逻辑稳定# 下载内核源码 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.12.2.3 内核配置开启调度调试与 PELT# 继承当前系统内核配置 cp /boot/config-$(uname -r) .config # 图形化配置界面 make menuconfig必须开启的核心配置项保证 cpu_util 可追踪、调试CONFIG_PELTy # 启用PELT负载追踪cpu_util依赖 CONFIG_SCHED_DEBUGy # 调度器调试开关 CONFIG_FTRACEy # 函数跟踪观测util更新流程 CONFIG_SCHEDUTILy # 启用schedutil调频策略 CONFIG_NO_HZ_COMMONy # 支持无时钟模式验证极端场景 CONFIG_DEBUG_KERNELy # 内核调试配置完成后保存退出。2.2.4 编译、安装内核并重启# 多线程编译nproc为CPU核心数加速编译 make -j$(nproc) # 安装内核模块 sudo make modules_install # 安装新内核镜像 sudo make install # 更新系统引导项 sudo update-grub执行完成后重启服务器在 GRUB 引导界面选择新编译的Linux 6.1内核进入。2.2.5 源码路径定位后续解析、调试均基于以下路径提前熟记PELT 算法、util_avg 计算核心kernel/sched/pelt.cCFS 运行队列聚合逻辑kernel/sched/fair.c负载均衡决策逻辑kernel/sched/sched.h、kernel/sched/balancing.cschedutil 调频逻辑kernel/sched/cpufreq_schedutil.c三、应用场景cpu_util 作为内核统一的 CPU 利用率指标在工业生产、服务器运维、嵌入式设备三大领域落地广泛。在云服务器集群中调度器依托各核心 cpu_util 数值做精细化负载均衡避免单机 CPU 核心负载分化保障虚拟机、容器服务的响应时延稳定同时配合 schedutil 调频降低整机功耗。在工业嵌入式异构多核平台Big.LITTLE 架构中系统根据任务计算强度对应的 cpu_util将高负载任务调度至大核、轻负载任务调度至小核兼顾实时性与续航。在高性能计算集群中运维人员通过抓取内核 cpu_util 原始数据替代传统工具做细粒度性能分析定位 CPU 密集型任务、线程漂移、负载不均等问题。此外移动端、车载操作系统的功耗优化模块也完全基于 cpu_util 动态调整 CPU 主频与核心上下线是软硬件协同优化的核心桥梁。四、实际案例与源码深度剖析本章节从底层计算源码、数据聚合逻辑、内核接口调用、用户态测试、ftrace 动态跟踪五个维度展开所有代码均可直接编译、运行。4.1 PELT 算法util_avg 核心计算源码___update_load_sum是更新util_sum与util_avg的底层函数位于kernel/sched/pelt.c也是 cpu_util 的数据源头/** * ___update_load_sum - 更新调度实体的利用率累加值 * delta: 距离上一次更新的时间差纳秒 * curr: 当前调度实体 * running: 标记任务是否处于CPU运行态 */ static void ___update_load_sum(u64 delta, struct sched_avg *sa, int running) { u64 decay; u32 scale; /* 1. 计算历史数据衰减系数指数加权滑动平均核心 */ decay pelt_decay_sample(delta); scale pelt_scale_from_decay(decay); /* 2. 对历史util_sum做衰减弱化久远数据的影响 */ sa-util_sum (sa-util_sum * scale) PELT_SHIFT; /* 3. 仅当任务在CPU上运行时才累加运行时间到util_sum */ if (running) sa-util_sum delta PELT_SHIFT; }代码说明delta两次统计之间的时间间隔单位纳秒由内核时钟或任务状态切换触发计算decay/scale指数衰减系数时间越久历史利用率数据权重越低保证指标实时性running是关键判断任务仅在运行态才会累加 util_sum休眠、就绪态不会计入这也是 cpu_util 只统计真实 CPU 占用的原因。4.2 计算最终 util_avg单任务利用率在pelt.c中___update_load_avg将累加值换算为最终的平均利用率util_avg/** * ___update_load_avg - 将sum累加值换算为平均利用率util_avg * sa: 调度实体的sched_avg结构体 */ static void ___update_load_avg(struct sched_avg *sa) { /* 换算为0~1024区间的利用率值SCHED_CAPACITY_SCALE 1024 */ sa-util_avg (sa-util_sum * SCHED_CAPACITY_SCALE) PELT_AVG_SHIFT; }代码说明 内核约定SCHED_CAPACITY_SCALE 1024即util_avg取值范围0 ~ 10240CPU 完全空闲无任务运行1024CPU 满载资源被完全占用 该统一标尺让负载均衡、调频模块可以无差别读取和对比利用率。4.3 队列聚合单 CPU 核心 cpu_util 生成逻辑单个任务的util_avg需要逐级聚合到cfs_rq最终形成整个 CPU 核心的 cpu_util代码位于kernel/sched/fair.c/** * __update_load_avg_cfs_rq - 聚合队列内所有任务的util生成CPU级利用率 * rq: 当前CPU的CFS运行队列 * delta: 时间差值 */ void __update_load_avg_cfs_rq(struct cfs_rq *cfs_rq, u64 delta) { struct sched_avg *sa cfs_rq-avg; /* 1. 更新队列整体的util_sum逻辑同单任务 */ ___update_load_sum(delta, sa, cfs_rq-nr_running 0); /* 2. 计算队列最终util_avg即该CPU核心的cpu_util */ ___update_load_avg(sa); }核心逻辑当队列中有运行任务nr_running 0则标记为running状态累加运行时间最终cfs_rq-avg.util_avg就是调度器使用的CPU 核心利用率cpu_util。4.4 内核对外接口cpu_util 读取函数内核负载均衡、schedutil 调频模块通过专用接口获取指定 CPU 的 cpu_util源码kernel/sched/cpufreq_schedutil.c/** * cpu_util - 获取指定CPU的当前利用率 * cpu: CPU核心编号 * 返回值0 ~ 1024 的利用率数值 */ unsigned long cpu_util(int cpu) { struct rq *rq cpu_rq(cpu); struct cfs_rq *cfs_rq rq-cfs; /* 原子读取避免数据竞争 */ return READ_ONCE(cfs_rq-avg.util_avg); }使用场景该函数是全内核通用接口负载均衡模块调用它对比多个 CPU 的繁忙程度schedutil 调频模块调用它计算目标运行主频。4.5 编写用户态测试程序压测 CPU 并观测利用率变化编写 CPU 密集型测试程序绑定指定 CPU 核心人为制造负载差异用于验证 cpu_util 与负载均衡效果。代码cpu_stress.c#define _GNU_SOURCE #include stdio.h #include stdlib.h #include unistd.h #include pthread.h #include sched.h // 线程数量模拟多任务压测 #define THREAD_NUM 4 // 线程执行函数死循环消耗CPU资源 void *cpu_load_func(void *arg) { (void)arg; while(1) { // 空循环纯CPU密集运算 __asm__ __volatile__(nop); } return NULL; } int main(int argc, char *argv[]) { pthread_t tid[THREAD_NUM]; cpu_set_t cpuset; int ret, i; int bind_cpu 0; // 绑定任务到CPU0制造单核高负载 printf(Start CPU stress test, bind all task to CPU%d\n, bind_cpu); // 初始化CPU掩码绑定到指定核心 CPU_ZERO(cpuset); CPU_SET(bind_cpu, cpuset); // 设置当前进程亲和性固定运行在CPU0 ret sched_setaffinity(getpid(), sizeof(cpu_set_t), cpuset); if(ret ! 0) { perror(sched_setaffinity failed); return -1; } // 创建多个压力线程 for(i 0; i THREAD_NUM; i) { ret pthread_create(tid[i], NULL, cpu_load_func, NULL); if(ret ! 0) { perror(pthread_create failed); return -1; } } // 等待线程永不退出 for(i 0; i THREAD_NUM; i) { pthread_join(tid[i], NULL); } return 0; }编译与运行命令# 编译代码链接线程库 gcc cpu_stress.c -o cpu_stress -lpthread # 后台运行压力程序绑定CPU0制造高负载 ./cpu_stress 实操说明运行后 CPU0 会被占满其余核心空闲内核会根据 cpu_util 的差值触发负载均衡。4.6 Ftrace 跟踪 cpu_util 更新流程使用 ftrace 跟踪util_avg更新、cpu_util读取的内核函数直观观测数据流转命令可直接复制# 1. 挂载debugfs内核调试文件系统 sudo mount -t debugfs none /sys/kernel/debug # 2. 清空历史跟踪日志 sudo echo /sys/kernel/debug/tracing/trace # 3. 设置需要跟踪的核心函数 sudo echo ___update_load_sum /sys/kernel/debug/tracing/set_ftrace_filter sudo echo ___update_load_avg /sys/kernel/debug/tracing/set_ftrace_filter sudo echo cpu_util /sys/kernel/debug/tracing/set_ftrace_filter # 4. 开启函数跟踪模式 sudo echo function /sys/kernel/debug/tracing/current_tracer # 5. 启动跟踪 sudo echo 1 /sys/kernel/debug/tracing/tracing_on # 等待10秒让数据持续采集 sleep 10 # 6. 停止跟踪 sudo echo 0 /sys/kernel/debug/tracing/tracing_on # 7. 查看完整跟踪日志 sudo cat /sys/kernel/debug/tracing/trace日志解读日志中可以看到___update_load_sum周期性被调用时钟 Tick 触发CPU0 对应的函数调用频率远高于其他核心对应其高利用率同时cpu_util会被负载均衡、调频模块频繁调用读取数值。4.7 传统工具对比cpu_util 与 /proc/stat 使用率执行以下命令查看传统 CPU 使用率对比内核 cpu_util# 实时查看CPU整体使用率1秒刷新一次 mpstat -P ALL 1 # 查看/proc/stat原始时间片数据 cat /proc/stat差异总结mpstat基于历史时间片计算存在数百毫秒延迟而 cpu_util 由 PELT 实时更新延迟在微秒级更适合内核实时决策。五、常见问题与解答Q1cpu_util 取值范围是 0~1024为什么不是百分比 0~100解答内核使用SCHED_CAPACITY_SCALE1024作为基准值1024 是 2 的整数次幂移位运算替代浮点运算大幅降低内核计算开销。换算百分比公式(cpu_util * 100) / 1024。该设计是内核为兼顾性能与精度的经典取舍。Q2任务处于就绪态、阻塞态时util_avg 会更新吗解答不会。util_sum仅在任务实际运行在 CPU 上时才累加时间。就绪态等待 CPU、阻塞态等待 IO / 信号不会计入 util_avg这也是 cpu_util 只表征 “真实 CPU 占用” 的核心特性和统计可运行时间的load_avg形成区分。Q3多核场景下绑定 CPU 亲和性后cpu_util 不再均衡正常吗解答属于正常现象。sched_setaffinity手动绑定线程 / 进程到指定核心后内核负载均衡会跳过该任务对应 CPU 的 cpu_util 会持续偏高。若需要自动均衡解除 CPU 亲和性即可。Q4NO_HZ 无时钟模式下cpu_util 还能正常更新吗解答可以。PELT 算法做了专门适配无时钟模式下依靠任务状态切换唤醒、阻塞、切换触发 util 数据更新不会因为关闭周期性时钟导致指标失效适配嵌入式低功耗场景。Q5ftrace 跟踪不到 cpu_util 函数调用是什么原因解答优先排查三点1. 未开启CONFIG_FTRACE和CONFIG_SCHED_DEBUG内核配置2. 没有挂载debugfs文件系统3. 系统使用了其他调频策略如 performance未启用schedutil导致cpu_util接口极少被调用。Q6为什么两个 CPU 核心负载cpu_util差距很大内核却不迁移任务解答负载均衡存在阈值保护避免频繁任务迁移造成开销。只有当核心间 cpu_util 差值超过内核预设阈值且满足调度域规则、任务迁移代价评估后才会触发迁移。另外实时任务、绑定亲和性的任务不会被迁移。六、实践建议与最佳实践6.1 调试与观测技巧分层排查原则排查负载不均问题时先通过mpstat做宏观判断再用ftrace跟踪cpu_util更新函数最后读取cfs_rq-avg.util_avg内核原始值由外到内定位问题。perf 采样分析使用perf record -g sleep 10采样 CPU 热点结合 cpu_util 数据快速定位 CPU 密集型线程。区分指标使用场景排查调度公平性看load_avg排查 CPU 资源占用、功耗、调频问题优先使用cpu_util。6.2 应用层开发最佳实践谨慎使用 CPU 亲和性业务程序非必要不要绑定 CPU 核心会破坏内核基于 cpu_util 的负载均衡策略造成局部核心过载。若必须绑定建议分散任务到多个核心。控制 CPU 密集型任务数量单核心 cpu_util 达到 1024满载后新增 CPU 密集任务会加剧竞争调度延迟上升业务侧需要做任务限流。异构多核平台适配在 Big.LITTLE 架构下不要强行将高负载任务调度至小核内核依靠 cpu_util 做任务择优放置人为干预会导致性能下降。6.3 内核调优与性能优化负载均衡阈值调优高并发服务器场景可适当调低负载均衡触发阈值让 cpu_util 差值更小就触发任务迁移提升整体均衡性低延迟嵌入式场景调高阈值减少任务迁移带来的切换开销。PELT 参数调优对实时性要求极高的场景可微调 PELT 衰减系数缩短历史数据的权重周期让 cpu_util 响应更快。调频策略搭配业务为 CPU 密集型时推荐使用schedutil调频依托 cpu_util 动态调速静态高性能场景可使用performance策略固定主频。6.4 故障规避建议禁止在内核模块中直接修改util_avg字段会破坏 PELT 统计逻辑导致 cpu_util 失真引发负载均衡、调频逻辑异常。长时间运行 CPU 压测程序后及时终止进程避免单核长期满载导致 cpu_util 居高不下系统调度延迟累积。内核升级时重点核对pelt.c、fair.c的改动新版本若修改 PELT 算法cpu_util 的计算逻辑会同步变化原有调优规则需要重新适配。七、总结与应用延伸本文完整拆解了 Linux 负载均衡体系中cpu_util的底层原理、PELT 计算算法、数据聚合流程、内核接口、实操验证与工程调优。核心要点回顾cpu_util 由调度实体的util_avg逐级聚合而来依托指数加权滑动平均的 PELT 算法实现微秒级实时统计仅统计任务真实 CPU 运行时间取值范围 0~1024它是 Linux 多核负载均衡、schedutil 动态调频两大核心模块的统一数据来源也是区分于传统/proc/stat使用率的内核原生精细化指标。从技术价值来看理解 cpu_util 是打通 “调度算法 - 负载均衡 - 功耗管理” 三大模块的关键节点。在工程落地层面云服务器、容器集群依靠它实现算力均衡嵌入式工控、车载、移动设备依靠它实现性能与功耗的平衡内核性能调优、故障排查工作中cpu_util 是定位负载倾斜、调度延迟、调频异常的核心依据。建议读者基于本文提供的内核源码、CPU 压测程序、ftrace 跟踪命令在测试环境中复现实验手动制造单核高负载观察 cpu_util 数值变化、负载均衡触发时机、CPU 主频调整行为。也可以尝试修改内核 PELT 衰减系数观测指标实时性的变化加深对算法的理解。在实际项目中无论是做 Linux 内核二次开发、嵌入式系统裁剪、服务器性能调优还是撰写相关技术论文、报告cpu_util 相关的知识都具备极高的实用价值。吃透这套机制能帮助开发者从底层理解 Linux 多核调度的运行逻辑解决一线工作中各类 CPU 负载与性能问题。