一、简介在现代多核处理器系统中动态电压频率调节DVFS和能量感知调度EAS已成为提升能效比的关键技术。Linux 内核的 CFSCompletely Fair Scheduler调度器通过PELTPer Entity Load Tracking机制实现了对任务负载的精确追踪但 PELT 存在一个固有缺陷当周期性任务从睡眠状态唤醒时其util_avg会因为历史衰减而显著低于实际运行时的利用率导致 CPU 频率选择过低造成性能抖动。为了解决这一问题Linux 内核引入了util_estUtilization Estimation机制通过在任务入队enqueue和出队dequeue时维护利用率的估计值实现快速上探、缓慢衰减的预测模型。本文将深入剖析util_est_enqueue()和util_est_dequeue()的实现原理通过源码分析、实战案例和性能调优技巧帮助开发者掌握这一核心调度机制。本文价值理解内核调度器与 CPU 频率调节的协同工作原理掌握 PELT 与 util_est 的设计差异与适用场景学会通过 ftrace、bpftrace 等工具观测调度器内部状态为实时系统调优、云原生资源调度提供理论支撑二、核心概念2.1 PELT 与 util_est 的关系PELTPer Entity Load Tracking是 Linux 内核的负载追踪基础框架使用指数加权移动平均EWMA算法以 1024μs 为周期对任务运行时间进行衰减计算衰减系数满足 y320.5 。然而PELT 存在睡眠衰减问题当周期性任务睡眠时其util_avg会持续衰减当任务重新唤醒时util_avg远低于实际运行需求导致schedutil等频率调节器选择过低频率。util_est 的设计目标快速上探Fast Ramp-up当任务利用率增加时立即更新估计值缓慢衰减Slow Decay当任务利用率降低时使用 EWMA 平滑处理避免瞬时波动入队出队同步在任务状态变化时同步更新 cfs_rq 的聚合估计值2.2 关键数据结构// include/linux/sched.h struct util_est { unsigned int enqueued; // 瞬时估计值任务入队时的 util_avg unsigned int ewma; // 指数加权移动平均历史平滑值 #define UTIL_EST_WEIGHT_SHIFT 2 // 权重移位weight 1/4 };字段说明enqueued对于任务表示上次出队时的util_avg对于 cfs_rq表示所有可运行任务的util_est之和ewma任务的利用率历史平滑值计算公式为 ewmat43×ewmat−1utilavg2.3 UTIL_AVG_UNCHANGED 标志位内核使用UTIL_AVG_UNCHANGED值为 0x80000000标志位来优化更新流程入队时将标志位 OR 到enqueued字段表示该值尚未被 PELT 更新PELT 更新时通过cfs_se_util_change()清除标志位出队时检查标志位若仍存在则跳过 EWMA 更新表示任务未完成一个完整的激活周期三、环境准备3.1 硬件与软件要求组件最低要求推荐配置CPUx86_64 或 ARM64支持 DVFS 的多核处理器内存4GB8GB操作系统Linux 5.4Linux 6.6最新 util_est 优化内核配置CONFIG_SCHED_DEBUGyCONFIG_SCHED_DEBUGy, CONFIG_FTRACEy3.2 内核编译与配置# 1. 下载内核源码以 6.8 为例 wget https://cdn.kernel.org/pub/linux/kernel/v6.x/linux-6.8.tar.xz tar -xf linux-6.8.tar.xz cd linux-6.8 # 2. 配置内核选项 make menuconfig # 必须启用的选项 # → General setup # → [*] Configure standard kernel features (expert users) # → Kernel hacking # → [*] Tracers # → [*] Scheduler Tracing # → CPU Power Management # → CPU Frequency scaling # → Default CPUFreq governor (schedutil) # 3. 编译并安装 make -j$(nproc) sudo make modules_install install3.3 调试工具安装# 安装性能分析工具 sudo apt-get update sudo apt-get install -y \ linux-tools-common \ linux-tools-generic \ bpftrace \ trace-cmd \ kernelshark \ vim # 验证 ftrace 可用性 sudo mount -t tracefs nodev /sys/kernel/tracing ls /sys/kernel/tracing/available_tracers四、应用场景场景一移动端游戏性能优化在智能手机或平板设备上运行高帧率游戏时游戏线程通常呈现周期性突发负载特征每 16ms60FPS或 8ms120FPS渲染一帧期间 CPU 高负载其余时间低负载等待 VSync。若仅依赖 PELT 的util_avg在帧开始渲染时频率尚未提升导致掉帧。通过util_est游戏线程每次唤醒入队时其ewma值保留了上次渲染的负载特征schedutil能立即选择合适频率实现零延迟频率响应。场景二云服务器容器密度优化在 Kubernetes 集群中大量微服务容器呈现短突发请求模式。通过观测cfs_rq-avg.util_est调度器可以更准确地预测容器实际所需算力避免过度配置导致的资源浪费。结合 CPU 容量感知Capacity Awareness可将高util_est的容器迁移至大核Big Core低util_est的容器保留在小核LITTLE Core实现能效最优的任务分布。场景三实时工业控制系统在工业自动化场景中PLC 控制任务需要严格的确定性响应。通过分析util_est_enqueue/dequeue的更新时机工程师可以验证调度器是否在任务就绪时正确预测了负载确保在硬实时任务唤醒时CPU 频率已提前调整至满足截止时间要求的水平避免由于频率爬升延迟导致的控制回路超时。五、实际案例与步骤5.1 源码剖析util_est_enqueue// kernel/sched/fair.c (Linux 6.8) static inline void util_est_enqueue(struct cfs_rq *cfs_rq, struct task_struct *p) { unsigned int enqueued; // 检查 UTIL_EST 特性是否启用 if (!sched_feat(UTIL_EST)) return; /* * 更新 root cfs_rq 的估计利用率 * 将当前任务的 util_est 累加到 cfs_rq 的总 util_est 中 */ enqueued cfs_rq-avg.util_est.enqueued; enqueued _task_util_est(p); // 获取任务的 util_est 估计值 WRITE_ONCE(cfs_rq-avg.util_est.enqueued, enqueued); // 追踪点用于 ftrace 分析 trace_sched_util_est_cfs_tp(cfs_rq); }关键逻辑解析累加机制cfs_rq-avg.util_est.enqueued是运行队列中所有可运行runnable任务的util_est之和任务估计值获取_task_util_est(p)返回max(ue.ewma, ue.enqueued)兼顾历史平滑值和最新瞬时值调用时机在enqueue_task_fair()最开始调用确保在 PELT 更新和频率调节前完成统计调用栈示例enqueue_task_fair() ├── util_est_enqueue(rq-cfs, p) // 更新 util_est ├── enqueue_entity() // 加入红黑树 ├── update_load_avg() // PELT 更新 └── cpufreq_update_util() // 触发频率调节5.2 源码剖析util_est_dequeue// kernel/sched/fair.c (Linux 6.8) static inline void util_est_dequeue(struct cfs_rq *cfs_rq, struct task_struct *p, bool task_sleep) { unsigned int enqueued; if (!sched_feat(UTIL_EST)) return; /* * 从 root cfs_rq 的估计利用率中扣除当前任务的 util_est * 使用 min_t 防止减法溢出数值下界保护 */ enqueued cfs_rq-avg.util_est.enqueued; enqueued - min_t(unsigned int, enqueued, _task_util_est(p)); WRITE_ONCE(cfs_rq-avg.util_est.enqueued, enqueued); trace_sched_util_est_cfs_tp(cfs_rq); /* * 只有在任务因睡眠而 dequeue 时才更新任务的 util_est * 如果是被抢占preempted则保持原有估计值 */ if (!task_sleep) return; // 更新任务自身的 util_estEWMA 计算 util_est_update(cfs_rq, p); }关键逻辑解析扣除机制使用min_t确保enqueued不会下溢为负值睡眠检测task_sleep为 true 表示任务主动睡眠调用schedule()此时更新 EWMA为 false 表示被抢占保留估计值以维持连续性EWMA 更新在util_est_update()中实现快速上探、缓慢衰减逻辑5.3 源码剖析util_est_updateEWMA 核心// kernel/sched/fair.c static inline void util_est_update(struct cfs_rq *cfs_rq, struct task_struct *p) { struct util_est ue; unsigned int dequeued, ewma; unsigned int last_ewma_diff; if (!sched_feat(UTIL_EST)) return; ue READ_ONCE(p-se.avg.util_est); dequeued task_util(p); // 获取当前 util_avg /* * 检查 UTIL_AVG_UNCHANGED 标志 * 如果 PELT 未更新任务被迁移或短时间运行跳过更新 */ if (ue.enqueued UTIL_AVG_UNCHANGED) return; /* * 快速上探Fast Ramp-up逻辑 * 如果当前利用率高于 EWMA立即将 EWMA 设置为当前值 */ if (dequeued ue.ewma) { ewma dequeued; goto done; } /* * 检查变化幅度如果变化小于 1%SCHED_CAPACITY_SCALE/100 * 认为是噪声跳过更新以减少计算开销 */ last_ewma_diff dequeued - ue.ewma; if (within_margin(last_ewma_diff, (SCHED_CAPACITY_SCALE / 100))) return; /* * 缓慢衰减Slow DecayEWMA 计算 * 公式ewma (ewma * 3 dequeued) / 4 * 即新 EWMA 0.75 * 旧 EWMA 0.25 * 当前值 */ ewma ue.ewma UTIL_EST_WEIGHT_SHIFT; // ewma * 4 ewma last_ewma_diff; // (dequeued - ewma) ewma UTIL_EST_WEIGHT_SHIFT; // / 4 done: ue.enqueued dequeued; ue.ewma ewma; WRITE_ONCE(p-se.avg.util_est, ue); trace_sched_util_est_task(p, p-se.avg); }5.4 实战使用 ftrace 观测 util_est 更新步骤 1启用调度器事件追踪# 以 root 身份执行 cd /sys/kernel/tracing # 启用 sched_util_est_cfs 和 sched_util_est_task 追踪点 echo 1 events/sched/sched_util_est_cfs/enable echo 1 events/sched/sched_util_est_task/enable # 设置环形缓冲区大小避免数据丢失 echo 8192 buffer_size_kb # 清除历史数据 echo trace步骤 2运行测试负载# 创建周期性 CPU 负载模拟游戏渲染线程 # 每 100ms 运行 50ms持续 10 秒 cat /tmp/periodic_load.c EOF #include stdio.h #include unistd.h #include time.h static inline unsigned long long get_ns() { struct timespec ts; clock_gettime(CLOCK_MONOTONIC, ts); return ts.tv_sec * 1000000000ULL ts.tv_nsec; } int main() { volatile int dummy 0; unsigned long long start, now; for (int i 0; i 100; i) { start get_ns(); // 忙碌 50ms while ((now get_ns()) - start 50000000) { dummy now * i; } // 睡眠 50ms usleep(50000); } return 0; } EOF gcc -O2 -o /tmp/periodic_load /tmp/periodic_load.c -lrt /tmp/periodic_load PID$! sleep 10 kill $PID 2/dev/null步骤 3分析追踪数据# 读取追踪结果 cat /sys/kernel/tracing/trace | grep -E (util_est|periodic_load) | head -50预期输出解析periodic_load-1234 [001] .... 12345.678901: sched_util_est_task: pid1234 cpu1 util_est_ewma512 util_est_enqueued512 periodic_load-1234 [001] .... 12345.728902: sched_util_est_cfs: cpu1 util_est_enqueued512分析要点sched_util_est_task事件显示任务出队时的ewma和enqueued值sched_util_est_cfs事件显示 cfs_rq 的聚合util_est观察ewma值是否随时间平滑收敛到任务的实际利用率5.5 实战使用 bpftrace 实时监控# 安装 bpftrace sudo apt-get install -y bpftrace # 创建监控脚本 cat /tmp/monitor_util_est.bt EOF #!/usr/bin/bpftrace #include linux/sched.h // 监控 util_est_enqueue kprobe:util_est_enqueue { $cfs_rq (struct cfs_rq *)arg0; $p (struct task_struct *)arg1; printf([%s] ENQUEUE: task%s pid%d util_est%u cfs_rq_util%u\n, nsecs, $p-comm, $p-pid, $p-se.avg.util_est.ewma, $cfs_rq-avg.util_est.enqueued); } // 监控 util_est_dequeue kprobe:util_est_dequeue { $cfs_rq (struct cfs_rq *)arg0; $p (struct task_struct *)arg1; $task_sleep arg2; printf([%s] DEQUEUE: task%s pid%d util_est%u cfs_rq_util%u sleep%d\n, nsecs, $p-comm, $p-pid, $p-se.avg.util_est.ewma, $cfs_rq-avg.util_est.enqueued, $task_sleep); } EOF # 运行监控需要 root sudo bpftrace /tmp/monitor_util_est.bt输出示例[1713001234567890] ENQUEUE: taskperiodic_load pid5678 util_est400 cfs_rq_util1200 [1713001234612345] DEQUEUE: taskperiodic_load pid5678 util_est400 cfs_rq_util800 sleep15.6 实战通过 procfs 查看当前 util_est# 查看特定进程的 util_est需要内核启用 CONFIG_SCHED_DEBUG cat /proc/[pid]/sched | grep -E (util_est|util_avg) # 示例输出 # util_avg: 512 # util_est.ewma: 480 # util_est.enqueued: 512字段解读util_avgPELT 计算的当前瞬时利用率0-1024util_est.ewma历史平滑估计值EWMA 结果util_est.enqueued上次出队时的util_avg快照六、常见问题与解答Q1: 为什么 util_est_enqueue 在 enqueue_task_fair 最开始调用而不是最后A: 这是为了确保在调用cpufreq_update_util()进行频率调节之前cfs_rq 的util_est已经包含了新任务的估计值。如果放在后面频率调节器看到的将是过低的利用率估计导致频率选择不当。根据 Linux 6.4 的优化补丁调度器在enqueue_task_fair中直接调用util_est_enqueue然后才进行后续的 PELT 更新和频率调节。Q2: 如何处理任务迁移时的 util_estA: 当任务从一个 CPU 迁移到另一个 CPU 时util_est_dequeue在原 CPU 执行扣除该任务的util_est随后util_est_enqueue在新 CPU 执行累加相同的值。由于util_est是基于任务自身历史ewma而非 CPU 特定状态迁移过程保持了估计值的连续性。需要注意的是如果迁移发生在 PELT 窗口内UTIL_AVG_UNCHANGED标志未清除util_est_update会跳过 EWMA 更新避免不完整数据污染历史记录。Q3: 为什么 util_est_dequeue 只在 task_sleep 为 true 时更新 EWMAA: 这是为了区分主动睡眠和被抢占两种情况主动睡眠task_sleeptrue任务完成一个完整的执行周期此时util_avg反映了真实的周期负载适合更新 EWMA被抢占task_sleepfalse任务尚未完成执行此时util_avg不能代表完整周期若更新 EWMA 会导致低估Q4: 如何验证 util_est 是否有效提升了性能A: 可通过对比测试验证# 禁用 UTIL_EST通过 sched_feat echo NO_UTIL_EST /sys/kernel/debug/sched_features # 运行基准测试如 hackbench、schbench hackbench -l 1000 -g 10 # 启用 UTIL_EST echo UTIL_EST /sys/kernel/debug/sched_features # 再次运行相同测试对比延迟分布通常启用UTIL_EST后周期性任务的唤醒延迟wakeup latency标准差会降低 10-30%。Q5: 在容器/ cgroup 环境中util_est 如何工作A: 在启用了组调度CONFIG_CGROUP_SCHED的系统中每个 task_group 都有自己的 cfs_rq。util_est_enqueue/dequeue不仅更新 root cfs_rq对应物理 CPU还会通过update_tg_load_avg传播到父组。这意味着容器层面的cpu.util_est统计同样准确可用于容器的垂直扩缩容VPA决策。七、实践建议与最佳实践7.1 调试技巧技巧 1使用 trace-cmd 进行长时间记录# 开始记录 sudo trace-cmd start -e sched_util_est_cfs -e sched_util_est_task -e sched_switch # 运行工作负载 ./your_benchmark # 停止并查看报告 sudo trace-cmd stop trace-cmd report util_est_analysis.txt # 可视化需安装 kernelshark trace-cmd report | ./scripts/trace2html.py util_est.html技巧 2动态调整 sched_features 进行 A/B 测试# 查看当前特性 cat /sys/kernel/debug/sched_features # 临时禁用 UTIL_EST无需重启 echo NO_UTIL_EST /sys/kernel/debug/sched_features # 恢复 echo UTIL_EST /sys/kernel/debug/sched_features7.2 性能优化建议建议 1结合 UCLAMP 使用util_est与UCLAMPUtilization Clamping协同工作时可以进一步消除频率选择的不确定性// 设置任务的最低利用率确保频率不会过低 struct sched_attr attr { .size sizeof(attr), .sched_policy SCHED_OTHER, .sched_util_min 400, // 对应 400/1024 ≈ 40% CPU }; sched_setattr(pid, attr, 0);建议 2避免频繁的短睡眠如果任务的睡眠时间短于 PELT 衰减周期32msutil_avg不会显著衰减此时util_est的优势不明显。对于这类任务建议合并多个短睡眠为单次长睡眠使用sched_setattr设置SCHED_IDLE策略避免干扰其他任务建议 3监控 cfs_rq 的 util_est 过载# 实时监控各 CPU 的 util_est watch -n 1 grep -r util_est /proc/sched_debug | grep cfs_rq | head -20若某 CPU 的util_est持续接近capacity而util_avg较低说明存在大量周期性突发任务应考虑启用schedutil的iowaitboost 功能调整sched_migration_cost_ns减少不必要的迁移7.3 常见错误解决方案错误 1util_est 值异常高超过 1024原因旧版本内核5.10在任务被限制capped时未正确处理util_est衰减。解决升级到 Linux 6.6或应用补丁sched/fair: Allow decaying util_est when util_avg CPU capa。错误 2频率调节滞后现象任务已唤醒但 CPU 频率在 5-10ms 后才提升。排查# 检查 schedutil 的 rate_limit cat /sys/devices/system/cpu/cpufreq/policy0/schedutil/rate_limit_us若值过高1000调整为 500μs 或更低echo 500 | sudo tee /sys/devices/system/cpu/cpufreq/policy*/schedutil/rate_limit_us八、总结与应用场景本文深入剖析了 Linux CFS 调度器中util_est_enqueue和util_est_dequeue的实现机制揭示了其如何通过快速上探、缓慢衰减的 EWMA 策略解决 PELT 在周期性任务场景下的频率选择滞后问题。核心要点回顾入队累加util_est_enqueue将任务的max(ewma, enqueued)累加到 cfs_rq确保频率调节器立即感知新任务的负载需求出队扣除util_est_dequeue安全扣除任务贡献仅在任务主动睡眠时更新 EWMA维持估计值的准确性快速上探当util_avg ewma时立即更新避免性能抖动缓慢衰减使用 0.75/0.25 权重进行 EWMA 平滑过滤瞬时噪声sandbox:///mnt/kimi/output/util_est_flow.pngsandbox:///mnt/kimi/output/cfs_rq_util_est.png未来应用场景AI 推理优化在边缘 AI 设备上推理任务呈现明显的周期性突发特征利用util_est可实现毫秒级频率响应降低端到端延迟云原生调度结合 Kubernetes 的 CPU Manager 和拓扑管理器基于util_est进行更精确的 Pod 到节点调度提升集群资源利用率异构计算在 big.LITTLE 架构中util_est可作为任务大小分类的依据实现更智能的核间迁移策略掌握util_est机制不仅有助于理解现代操作系统调度器的内部工作原理更为开发高性能、低延迟的实时应用提供了坚实的理论基础。建议读者结合本文提供的 ftrace 和 bpftrace 工具在实际工作负载中观测util_est的动态变化深化对调度器行为的理解。
Linux CFS 的 util_est_enqueue/dequeue:入队出队时的利用率更新
发布时间:2026/6/6 1:26:19
一、简介在现代多核处理器系统中动态电压频率调节DVFS和能量感知调度EAS已成为提升能效比的关键技术。Linux 内核的 CFSCompletely Fair Scheduler调度器通过PELTPer Entity Load Tracking机制实现了对任务负载的精确追踪但 PELT 存在一个固有缺陷当周期性任务从睡眠状态唤醒时其util_avg会因为历史衰减而显著低于实际运行时的利用率导致 CPU 频率选择过低造成性能抖动。为了解决这一问题Linux 内核引入了util_estUtilization Estimation机制通过在任务入队enqueue和出队dequeue时维护利用率的估计值实现快速上探、缓慢衰减的预测模型。本文将深入剖析util_est_enqueue()和util_est_dequeue()的实现原理通过源码分析、实战案例和性能调优技巧帮助开发者掌握这一核心调度机制。本文价值理解内核调度器与 CPU 频率调节的协同工作原理掌握 PELT 与 util_est 的设计差异与适用场景学会通过 ftrace、bpftrace 等工具观测调度器内部状态为实时系统调优、云原生资源调度提供理论支撑二、核心概念2.1 PELT 与 util_est 的关系PELTPer Entity Load Tracking是 Linux 内核的负载追踪基础框架使用指数加权移动平均EWMA算法以 1024μs 为周期对任务运行时间进行衰减计算衰减系数满足 y320.5 。然而PELT 存在睡眠衰减问题当周期性任务睡眠时其util_avg会持续衰减当任务重新唤醒时util_avg远低于实际运行需求导致schedutil等频率调节器选择过低频率。util_est 的设计目标快速上探Fast Ramp-up当任务利用率增加时立即更新估计值缓慢衰减Slow Decay当任务利用率降低时使用 EWMA 平滑处理避免瞬时波动入队出队同步在任务状态变化时同步更新 cfs_rq 的聚合估计值2.2 关键数据结构// include/linux/sched.h struct util_est { unsigned int enqueued; // 瞬时估计值任务入队时的 util_avg unsigned int ewma; // 指数加权移动平均历史平滑值 #define UTIL_EST_WEIGHT_SHIFT 2 // 权重移位weight 1/4 };字段说明enqueued对于任务表示上次出队时的util_avg对于 cfs_rq表示所有可运行任务的util_est之和ewma任务的利用率历史平滑值计算公式为 ewmat43×ewmat−1utilavg2.3 UTIL_AVG_UNCHANGED 标志位内核使用UTIL_AVG_UNCHANGED值为 0x80000000标志位来优化更新流程入队时将标志位 OR 到enqueued字段表示该值尚未被 PELT 更新PELT 更新时通过cfs_se_util_change()清除标志位出队时检查标志位若仍存在则跳过 EWMA 更新表示任务未完成一个完整的激活周期三、环境准备3.1 硬件与软件要求组件最低要求推荐配置CPUx86_64 或 ARM64支持 DVFS 的多核处理器内存4GB8GB操作系统Linux 5.4Linux 6.6最新 util_est 优化内核配置CONFIG_SCHED_DEBUGyCONFIG_SCHED_DEBUGy, CONFIG_FTRACEy3.2 内核编译与配置# 1. 下载内核源码以 6.8 为例 wget https://cdn.kernel.org/pub/linux/kernel/v6.x/linux-6.8.tar.xz tar -xf linux-6.8.tar.xz cd linux-6.8 # 2. 配置内核选项 make menuconfig # 必须启用的选项 # → General setup # → [*] Configure standard kernel features (expert users) # → Kernel hacking # → [*] Tracers # → [*] Scheduler Tracing # → CPU Power Management # → CPU Frequency scaling # → Default CPUFreq governor (schedutil) # 3. 编译并安装 make -j$(nproc) sudo make modules_install install3.3 调试工具安装# 安装性能分析工具 sudo apt-get update sudo apt-get install -y \ linux-tools-common \ linux-tools-generic \ bpftrace \ trace-cmd \ kernelshark \ vim # 验证 ftrace 可用性 sudo mount -t tracefs nodev /sys/kernel/tracing ls /sys/kernel/tracing/available_tracers四、应用场景场景一移动端游戏性能优化在智能手机或平板设备上运行高帧率游戏时游戏线程通常呈现周期性突发负载特征每 16ms60FPS或 8ms120FPS渲染一帧期间 CPU 高负载其余时间低负载等待 VSync。若仅依赖 PELT 的util_avg在帧开始渲染时频率尚未提升导致掉帧。通过util_est游戏线程每次唤醒入队时其ewma值保留了上次渲染的负载特征schedutil能立即选择合适频率实现零延迟频率响应。场景二云服务器容器密度优化在 Kubernetes 集群中大量微服务容器呈现短突发请求模式。通过观测cfs_rq-avg.util_est调度器可以更准确地预测容器实际所需算力避免过度配置导致的资源浪费。结合 CPU 容量感知Capacity Awareness可将高util_est的容器迁移至大核Big Core低util_est的容器保留在小核LITTLE Core实现能效最优的任务分布。场景三实时工业控制系统在工业自动化场景中PLC 控制任务需要严格的确定性响应。通过分析util_est_enqueue/dequeue的更新时机工程师可以验证调度器是否在任务就绪时正确预测了负载确保在硬实时任务唤醒时CPU 频率已提前调整至满足截止时间要求的水平避免由于频率爬升延迟导致的控制回路超时。五、实际案例与步骤5.1 源码剖析util_est_enqueue// kernel/sched/fair.c (Linux 6.8) static inline void util_est_enqueue(struct cfs_rq *cfs_rq, struct task_struct *p) { unsigned int enqueued; // 检查 UTIL_EST 特性是否启用 if (!sched_feat(UTIL_EST)) return; /* * 更新 root cfs_rq 的估计利用率 * 将当前任务的 util_est 累加到 cfs_rq 的总 util_est 中 */ enqueued cfs_rq-avg.util_est.enqueued; enqueued _task_util_est(p); // 获取任务的 util_est 估计值 WRITE_ONCE(cfs_rq-avg.util_est.enqueued, enqueued); // 追踪点用于 ftrace 分析 trace_sched_util_est_cfs_tp(cfs_rq); }关键逻辑解析累加机制cfs_rq-avg.util_est.enqueued是运行队列中所有可运行runnable任务的util_est之和任务估计值获取_task_util_est(p)返回max(ue.ewma, ue.enqueued)兼顾历史平滑值和最新瞬时值调用时机在enqueue_task_fair()最开始调用确保在 PELT 更新和频率调节前完成统计调用栈示例enqueue_task_fair() ├── util_est_enqueue(rq-cfs, p) // 更新 util_est ├── enqueue_entity() // 加入红黑树 ├── update_load_avg() // PELT 更新 └── cpufreq_update_util() // 触发频率调节5.2 源码剖析util_est_dequeue// kernel/sched/fair.c (Linux 6.8) static inline void util_est_dequeue(struct cfs_rq *cfs_rq, struct task_struct *p, bool task_sleep) { unsigned int enqueued; if (!sched_feat(UTIL_EST)) return; /* * 从 root cfs_rq 的估计利用率中扣除当前任务的 util_est * 使用 min_t 防止减法溢出数值下界保护 */ enqueued cfs_rq-avg.util_est.enqueued; enqueued - min_t(unsigned int, enqueued, _task_util_est(p)); WRITE_ONCE(cfs_rq-avg.util_est.enqueued, enqueued); trace_sched_util_est_cfs_tp(cfs_rq); /* * 只有在任务因睡眠而 dequeue 时才更新任务的 util_est * 如果是被抢占preempted则保持原有估计值 */ if (!task_sleep) return; // 更新任务自身的 util_estEWMA 计算 util_est_update(cfs_rq, p); }关键逻辑解析扣除机制使用min_t确保enqueued不会下溢为负值睡眠检测task_sleep为 true 表示任务主动睡眠调用schedule()此时更新 EWMA为 false 表示被抢占保留估计值以维持连续性EWMA 更新在util_est_update()中实现快速上探、缓慢衰减逻辑5.3 源码剖析util_est_updateEWMA 核心// kernel/sched/fair.c static inline void util_est_update(struct cfs_rq *cfs_rq, struct task_struct *p) { struct util_est ue; unsigned int dequeued, ewma; unsigned int last_ewma_diff; if (!sched_feat(UTIL_EST)) return; ue READ_ONCE(p-se.avg.util_est); dequeued task_util(p); // 获取当前 util_avg /* * 检查 UTIL_AVG_UNCHANGED 标志 * 如果 PELT 未更新任务被迁移或短时间运行跳过更新 */ if (ue.enqueued UTIL_AVG_UNCHANGED) return; /* * 快速上探Fast Ramp-up逻辑 * 如果当前利用率高于 EWMA立即将 EWMA 设置为当前值 */ if (dequeued ue.ewma) { ewma dequeued; goto done; } /* * 检查变化幅度如果变化小于 1%SCHED_CAPACITY_SCALE/100 * 认为是噪声跳过更新以减少计算开销 */ last_ewma_diff dequeued - ue.ewma; if (within_margin(last_ewma_diff, (SCHED_CAPACITY_SCALE / 100))) return; /* * 缓慢衰减Slow DecayEWMA 计算 * 公式ewma (ewma * 3 dequeued) / 4 * 即新 EWMA 0.75 * 旧 EWMA 0.25 * 当前值 */ ewma ue.ewma UTIL_EST_WEIGHT_SHIFT; // ewma * 4 ewma last_ewma_diff; // (dequeued - ewma) ewma UTIL_EST_WEIGHT_SHIFT; // / 4 done: ue.enqueued dequeued; ue.ewma ewma; WRITE_ONCE(p-se.avg.util_est, ue); trace_sched_util_est_task(p, p-se.avg); }5.4 实战使用 ftrace 观测 util_est 更新步骤 1启用调度器事件追踪# 以 root 身份执行 cd /sys/kernel/tracing # 启用 sched_util_est_cfs 和 sched_util_est_task 追踪点 echo 1 events/sched/sched_util_est_cfs/enable echo 1 events/sched/sched_util_est_task/enable # 设置环形缓冲区大小避免数据丢失 echo 8192 buffer_size_kb # 清除历史数据 echo trace步骤 2运行测试负载# 创建周期性 CPU 负载模拟游戏渲染线程 # 每 100ms 运行 50ms持续 10 秒 cat /tmp/periodic_load.c EOF #include stdio.h #include unistd.h #include time.h static inline unsigned long long get_ns() { struct timespec ts; clock_gettime(CLOCK_MONOTONIC, ts); return ts.tv_sec * 1000000000ULL ts.tv_nsec; } int main() { volatile int dummy 0; unsigned long long start, now; for (int i 0; i 100; i) { start get_ns(); // 忙碌 50ms while ((now get_ns()) - start 50000000) { dummy now * i; } // 睡眠 50ms usleep(50000); } return 0; } EOF gcc -O2 -o /tmp/periodic_load /tmp/periodic_load.c -lrt /tmp/periodic_load PID$! sleep 10 kill $PID 2/dev/null步骤 3分析追踪数据# 读取追踪结果 cat /sys/kernel/tracing/trace | grep -E (util_est|periodic_load) | head -50预期输出解析periodic_load-1234 [001] .... 12345.678901: sched_util_est_task: pid1234 cpu1 util_est_ewma512 util_est_enqueued512 periodic_load-1234 [001] .... 12345.728902: sched_util_est_cfs: cpu1 util_est_enqueued512分析要点sched_util_est_task事件显示任务出队时的ewma和enqueued值sched_util_est_cfs事件显示 cfs_rq 的聚合util_est观察ewma值是否随时间平滑收敛到任务的实际利用率5.5 实战使用 bpftrace 实时监控# 安装 bpftrace sudo apt-get install -y bpftrace # 创建监控脚本 cat /tmp/monitor_util_est.bt EOF #!/usr/bin/bpftrace #include linux/sched.h // 监控 util_est_enqueue kprobe:util_est_enqueue { $cfs_rq (struct cfs_rq *)arg0; $p (struct task_struct *)arg1; printf([%s] ENQUEUE: task%s pid%d util_est%u cfs_rq_util%u\n, nsecs, $p-comm, $p-pid, $p-se.avg.util_est.ewma, $cfs_rq-avg.util_est.enqueued); } // 监控 util_est_dequeue kprobe:util_est_dequeue { $cfs_rq (struct cfs_rq *)arg0; $p (struct task_struct *)arg1; $task_sleep arg2; printf([%s] DEQUEUE: task%s pid%d util_est%u cfs_rq_util%u sleep%d\n, nsecs, $p-comm, $p-pid, $p-se.avg.util_est.ewma, $cfs_rq-avg.util_est.enqueued, $task_sleep); } EOF # 运行监控需要 root sudo bpftrace /tmp/monitor_util_est.bt输出示例[1713001234567890] ENQUEUE: taskperiodic_load pid5678 util_est400 cfs_rq_util1200 [1713001234612345] DEQUEUE: taskperiodic_load pid5678 util_est400 cfs_rq_util800 sleep15.6 实战通过 procfs 查看当前 util_est# 查看特定进程的 util_est需要内核启用 CONFIG_SCHED_DEBUG cat /proc/[pid]/sched | grep -E (util_est|util_avg) # 示例输出 # util_avg: 512 # util_est.ewma: 480 # util_est.enqueued: 512字段解读util_avgPELT 计算的当前瞬时利用率0-1024util_est.ewma历史平滑估计值EWMA 结果util_est.enqueued上次出队时的util_avg快照六、常见问题与解答Q1: 为什么 util_est_enqueue 在 enqueue_task_fair 最开始调用而不是最后A: 这是为了确保在调用cpufreq_update_util()进行频率调节之前cfs_rq 的util_est已经包含了新任务的估计值。如果放在后面频率调节器看到的将是过低的利用率估计导致频率选择不当。根据 Linux 6.4 的优化补丁调度器在enqueue_task_fair中直接调用util_est_enqueue然后才进行后续的 PELT 更新和频率调节。Q2: 如何处理任务迁移时的 util_estA: 当任务从一个 CPU 迁移到另一个 CPU 时util_est_dequeue在原 CPU 执行扣除该任务的util_est随后util_est_enqueue在新 CPU 执行累加相同的值。由于util_est是基于任务自身历史ewma而非 CPU 特定状态迁移过程保持了估计值的连续性。需要注意的是如果迁移发生在 PELT 窗口内UTIL_AVG_UNCHANGED标志未清除util_est_update会跳过 EWMA 更新避免不完整数据污染历史记录。Q3: 为什么 util_est_dequeue 只在 task_sleep 为 true 时更新 EWMAA: 这是为了区分主动睡眠和被抢占两种情况主动睡眠task_sleeptrue任务完成一个完整的执行周期此时util_avg反映了真实的周期负载适合更新 EWMA被抢占task_sleepfalse任务尚未完成执行此时util_avg不能代表完整周期若更新 EWMA 会导致低估Q4: 如何验证 util_est 是否有效提升了性能A: 可通过对比测试验证# 禁用 UTIL_EST通过 sched_feat echo NO_UTIL_EST /sys/kernel/debug/sched_features # 运行基准测试如 hackbench、schbench hackbench -l 1000 -g 10 # 启用 UTIL_EST echo UTIL_EST /sys/kernel/debug/sched_features # 再次运行相同测试对比延迟分布通常启用UTIL_EST后周期性任务的唤醒延迟wakeup latency标准差会降低 10-30%。Q5: 在容器/ cgroup 环境中util_est 如何工作A: 在启用了组调度CONFIG_CGROUP_SCHED的系统中每个 task_group 都有自己的 cfs_rq。util_est_enqueue/dequeue不仅更新 root cfs_rq对应物理 CPU还会通过update_tg_load_avg传播到父组。这意味着容器层面的cpu.util_est统计同样准确可用于容器的垂直扩缩容VPA决策。七、实践建议与最佳实践7.1 调试技巧技巧 1使用 trace-cmd 进行长时间记录# 开始记录 sudo trace-cmd start -e sched_util_est_cfs -e sched_util_est_task -e sched_switch # 运行工作负载 ./your_benchmark # 停止并查看报告 sudo trace-cmd stop trace-cmd report util_est_analysis.txt # 可视化需安装 kernelshark trace-cmd report | ./scripts/trace2html.py util_est.html技巧 2动态调整 sched_features 进行 A/B 测试# 查看当前特性 cat /sys/kernel/debug/sched_features # 临时禁用 UTIL_EST无需重启 echo NO_UTIL_EST /sys/kernel/debug/sched_features # 恢复 echo UTIL_EST /sys/kernel/debug/sched_features7.2 性能优化建议建议 1结合 UCLAMP 使用util_est与UCLAMPUtilization Clamping协同工作时可以进一步消除频率选择的不确定性// 设置任务的最低利用率确保频率不会过低 struct sched_attr attr { .size sizeof(attr), .sched_policy SCHED_OTHER, .sched_util_min 400, // 对应 400/1024 ≈ 40% CPU }; sched_setattr(pid, attr, 0);建议 2避免频繁的短睡眠如果任务的睡眠时间短于 PELT 衰减周期32msutil_avg不会显著衰减此时util_est的优势不明显。对于这类任务建议合并多个短睡眠为单次长睡眠使用sched_setattr设置SCHED_IDLE策略避免干扰其他任务建议 3监控 cfs_rq 的 util_est 过载# 实时监控各 CPU 的 util_est watch -n 1 grep -r util_est /proc/sched_debug | grep cfs_rq | head -20若某 CPU 的util_est持续接近capacity而util_avg较低说明存在大量周期性突发任务应考虑启用schedutil的iowaitboost 功能调整sched_migration_cost_ns减少不必要的迁移7.3 常见错误解决方案错误 1util_est 值异常高超过 1024原因旧版本内核5.10在任务被限制capped时未正确处理util_est衰减。解决升级到 Linux 6.6或应用补丁sched/fair: Allow decaying util_est when util_avg CPU capa。错误 2频率调节滞后现象任务已唤醒但 CPU 频率在 5-10ms 后才提升。排查# 检查 schedutil 的 rate_limit cat /sys/devices/system/cpu/cpufreq/policy0/schedutil/rate_limit_us若值过高1000调整为 500μs 或更低echo 500 | sudo tee /sys/devices/system/cpu/cpufreq/policy*/schedutil/rate_limit_us八、总结与应用场景本文深入剖析了 Linux CFS 调度器中util_est_enqueue和util_est_dequeue的实现机制揭示了其如何通过快速上探、缓慢衰减的 EWMA 策略解决 PELT 在周期性任务场景下的频率选择滞后问题。核心要点回顾入队累加util_est_enqueue将任务的max(ewma, enqueued)累加到 cfs_rq确保频率调节器立即感知新任务的负载需求出队扣除util_est_dequeue安全扣除任务贡献仅在任务主动睡眠时更新 EWMA维持估计值的准确性快速上探当util_avg ewma时立即更新避免性能抖动缓慢衰减使用 0.75/0.25 权重进行 EWMA 平滑过滤瞬时噪声sandbox:///mnt/kimi/output/util_est_flow.pngsandbox:///mnt/kimi/output/cfs_rq_util_est.png未来应用场景AI 推理优化在边缘 AI 设备上推理任务呈现明显的周期性突发特征利用util_est可实现毫秒级频率响应降低端到端延迟云原生调度结合 Kubernetes 的 CPU Manager 和拓扑管理器基于util_est进行更精确的 Pod 到节点调度提升集群资源利用率异构计算在 big.LITTLE 架构中util_est可作为任务大小分类的依据实现更智能的核间迁移策略掌握util_est机制不仅有助于理解现代操作系统调度器的内部工作原理更为开发高性能、低延迟的实时应用提供了坚实的理论基础。建议读者结合本文提供的 ftrace 和 bpftrace 工具在实际工作负载中观测util_est的动态变化深化对调度器行为的理解。