Linux Schedutil 的 cached_raw_freq:频率缓存优化
一、简介
1.1 技术背景与行业现状
CPU 调频(DVFS,动态电压频率调节)是现代 Linux 系统功耗与性能平衡的核心技术,广泛应用于服务器、嵌入式终端、工控设备、车载系统、移动终端等场景。传统ondemand、performance调频策略逻辑简单、响应滞后,而schedutil作为 Linux 内核主推的调度器联动调频策略,直接依托调度器 PELT 负载统计数据进行频率决策,具备响应快、精度高、负载感知强三大优势,目前已成为绝大多数 Linux 发行版、嵌入式平台的默认调频调节器。
在高并发、高频任务切换的场景下,调度器会在任务唤醒、迁移、时间片轮转等时机频繁触发调频计算。如果每次都完整执行负载换算、频率公式运算、OPP 表匹配,会产生大量冗余计算,挤占 CPU 算力,尤其在单核嵌入式、低功耗 MCU 设备上,冗余计算会直接拉高系统开销、增加功耗。
为解决该问题,Linux 内核在 schedutil 架构中引入了cached_raw_freq字段,核心作用是缓存上一次计算得到的原始目标频率,当负载未发生实质性变化时,直接复用缓存结果,跳过完整计算流程,大幅降低调频路径的 CPU 开销。
1.2 学习本章节的价值
- 内核原理层面:理解调度器与 CPU 调频子系统的联动逻辑,掌握 schedutil 核心执行链路;
- 工程调优层面:看懂频率缓存机制的设计思想,能够针对高负载、高 IO 场景优化调频策略;
- 排错能力层面:定位调频卡顿、功耗异常、频率频繁跳动等线上问题;
- 学术 / 报告层面:内核源码、性能数据、测试案例完整,可作为 Linux 调度、功耗管理方向论文、技术报告的核心素材。
1.3 核心场景概述
本机制主要服务两大类场景:一是高并发服务器,海量短生命周期任务频繁唤醒,调度器高频触发调频;二是嵌入式实时设备,RT 实时任务、IO 密集型任务交替运行,要求调频低延迟、低开销。cached_raw_freq 缓存机制正是为这类高频触发、负载小幅波动的场景量身设计。
二、核心概念与术语解析
为保证零基础读者也能顺畅阅读,本节梳理所有关联术语、数据结构与执行逻辑。
2.1 schedutil 基础概念
schedutilLinux 内核基于调度器负载的 CPUFreq 调频调节器,全称
Scheduler Utilization Governor。不再依赖传统定时器轮询采样负载,而是由调度器事件驱动(任务唤醒、任务就绪、任务迁移),实时获取 PELT 计算的 CPU 利用率,动态计算目标运行频率。DVFS(动态电压频率调节)硬件 + 内核协同的功耗管理技术:CPU 负载低时降低主频与电压以省电,负载高时提升主频以保证性能,是嵌入式、服务器、移动端必备技术。
PELT(Per-Entity Load Tracking)Linux 调度器核心负载统计模块,采用 EWMA 指数加权移动平均算法,统计每个任务、每个运行队列(rq)的 CPU 利用率,输出频率无关的负载值,是 schedutil 计算频率的数据源。
2.2 cached_raw_freq 核心定义
2.2.1 结构体出处
cached_raw_freq是struct sugov_policy结构体中的成员变量,该结构体是 schedutil 每个 CPU 调频域的策略管理体,定义于内核源码kernel/sched/cpufreq_schedutil.c:
struct sugov_policy { struct cpufreq_policy *policy; struct sugov_tunables *tunables; struct list_head tunables_hook; raw_spinlock_t update_lock; u64 last_freq_update_time; s64 freq_update_delay_ns; unsigned int next_freq; // 核心:原始计算频率缓存字段 unsigned int cached_raw_freq; /* 后续为延迟工作、中断工作相关成员 */ struct irq_work irq_work; struct kthread_work work; struct mutex work_lock; struct kthread_worker worker; struct task_struct *thread; bool work_in_progress; bool limits_changed; bool need_freq_update; };2.2.2 字段语义拆解
cached_raw_freq:上一次通过公式计算得出的原始频率值(未经过硬件 OPP 表匹配、上下限约束);next_freq:最终生效的目标频率(经过硬件校验、频率档位匹配后的结果);- 二者区别:
cached_raw_freq是计算中间值缓存,next_freq是最终执行值。
2.3 缓存生效逻辑简述
schedutil 计算目标频率的标准流程:获取PELT负载 → 代入频率公式计算raw_freq → 匹配硬件OPP档位 → 得到next_freq → 硬件调频
引入缓存后流程优化:获取PELT负载 → 计算raw_freq → 对比 cached_raw_freq
- 相等:直接复用
next_freq,跳过后续计算与匹配; - 不相等:更新
cached_raw_freq,执行完整计算链路。
该设计本质是空间换时间,用一个 4 字节整型变量的内存开销,规避重复的浮点运算、档位匹配、硬件查询等高开销操作。
三、环境准备
3.1 软硬件环境清单
| 环境类型 | 版本 / 配置 | 用途说明 |
|---|---|---|
| 操作系统 | Ubuntu 20.04 / CentOS 7.9 / Debian 11 | 通用测试主机,推荐 x86_64 架构 |
| Linux 内核 | 5.4 / 5.10 / 5.15(LTS 长期支持版) | cached_raw_freq 在 4.15+ 内核稳定存在,优先 LTS 版本 |
| 编译工具 | gcc、g++、make、libncurses-dev、bison、flex | 内核编译、模块编译 |
| 调试工具 | perf、trace-cmd、ftrace、gdb + kgdb | 跟踪 schedutil 执行流、查看变量值 |
| 源码资源 | Linux 官方内核源码 | 阅读、修改、编译内核 |
| 硬件要求 | x86_64 双核及以上 CPU,支持 DVFS | 必须支持 CPU 调频功能(虚拟机需开启 CPU 调频模拟) |
注意:纯虚拟机部分厂商会屏蔽 DVFS,建议使用物理机或开启「CPU 性能模式模拟」的 KVM 虚拟机。
3.2 环境部署步骤(完整可复现)
3.2.1 依赖包安装(Ubuntu 系列)
执行以下命令安装编译、调试全套依赖,命令可直接复制运行:
# 更新软件源 sudo apt update && sudo apt upgrade -y # 内核编译基础依赖 sudo apt install -y gcc make libncurses-dev bison flex libssl-dev libelf-dev # 调试、追踪工具 sudo apt install -y perf trace-cmd ftrace gdb3.2.2 下载并解压内核源码
# 下载 Linux 5.10 LTS 内核(国内镜像,速度更快) wget https://cdn.kernel.org/pub/linux/kernel/v5.x/linux-5.10.200.tar.xz # 解压源码 tar -xf linux-5.10.200.tar.xz cd linux-5.10.2003.2.3 确认 schedutil 配置开启
schedutil 由内核配置项控制,需确保配置开启:
- 打开内核配置界面
make menuconfig- 依次进入配置路径:
Power management and ACPI options -> CPU Frequency scaling -> CPU Frequency scaling governors - 确保以下选项勾选(
*内置内核,M模块):
[*] Schedutil cpufreq governor- 保存配置并退出。
3.2.4 验证系统当前调频调节器
在物理机 / 虚拟机中执行以下命令,确认当前使用 schedutil:
# 查看当前CPU调频策略(所有核心) cat /sys/devices/system/cpu/cpu*/cpufreq/scaling_governor正常输出示例(全部为 schedutil):
schedutil schedutil schedutil schedutil若输出为performance/ondemand,执行命令切换:
# 批量将所有CPU核心切换为schedutil for cpu in /sys/devices/system/cpu/cpu[0-9]*; do echo schedutil > $cpu/cpufreq/scaling_governor done四、应用场景详解(300 字)
cached_raw_freq 频率缓存机制主要落地于三类工业场景。第一类是高并发 Web / 数据库服务器,海量短连接请求造成任务频繁唤醒、调度器持续触发调频,缓存可削减重复计算开销,降低系统软中断占比。第二类是工业嵌入式实时系统,工控机、PLC 设备中实时任务与普通 IO 任务交替执行,要求调频低延迟、低抖动,缓存机制避免频率反复计算导致的实时性下降。第三类是边缘计算低功耗设备,ARM 架构边缘盒算力有限,频繁调频计算会增加功耗,缓存大幅精简执行链路,兼顾性能与续航。在以上负载波动平缓、调频触发频繁的场景中,cached_raw_freq 的优化收益最为显著。
五、实际案例与代码实操(核心章节,含源码 + 注释 + 命令)
本节分为源码逐行解析、手动触发调频观测缓存、ftrace 跟踪缓存变化、编写测试程序压测四大实操环节,所有代码、命令均可直接复制使用。
5.1 核心源码解析:cached_raw_freq 工作流程
文件路径:linux-5.10.200/kernel/sched/cpufreq_schedutil.c核心函数:get_next_freq(),这是 cached_raw_freq 缓存判断、更新的唯一入口,也是整个机制的核心。
5.1.1 get_next_freq 函数完整源码 + 逐行注释
/** * get_next_freq - 计算CPU下一跳目标频率,并使用cached_raw_freq做缓存优化 * @sg_policy: schedutil策略结构体,包含cached_raw_freq、next_freq等核心字段 * @util: PELT模块输出的CPU利用率(负载值) * @max: CPU最大容量值 * * 返回值:经过硬件匹配后的最终目标频率 */ static unsigned int get_next_freq(struct sugov_policy *sg_policy, unsigned long util, unsigned long max) { struct cpufreq_policy *policy = sg_policy->policy; unsigned int freq; // 步骤1:根据架构是否支持频率不变性,选择计算基准频率 freq = arch_scale_freq_invariant() ? policy->cpuinfo.max_freq : policy->cur; // 步骤2:负载值性能映射 + 频率换算,得到原始计算频率 raw_freq util = map_util_perf(util); freq = map_util_freq(util, freq, max); // ====================== 缓存核心逻辑 ====================== // 判断1:新计算的raw_freq 与 缓存的cached_raw_freq 相等 // 判断2:无强制更新标志 need_freq_update if (freq == sg_policy->cached_raw_freq && !sg_policy->need_freq_update) { // 缓存命中:直接返回上一次生效的next_freq,跳过后续所有计算 return sg_policy->next_freq; } // 缓存未命中:更新缓存字段为当前新计算的raw_freq sg_policy->cached_raw_freq = freq; // 步骤3:原始频率匹配硬件支持的OPP档位,输出最终频率 return cpufreq_driver_resolve_freq(policy, freq); }5.1.2 逻辑流程总结
- 调度器触发调频,调用
get_next_freq; - 根据 CPU 负载计算出原始频率 freq;
- 对比
freq和sg_policy->cached_raw_freq:- 命中:直接返回历史
next_freq,链路终止; - 未命中:刷新
cached_raw_freq,执行硬件档位匹配;
- 命中:直接返回历史
- 返回最终频率,完成调频。
5.2 实操一:查看内核变量与缓存状态(命令行实操)
借助debugfs与cpufreq文件系统,观测调频状态、触发次数,间接验证缓存生效。
5.2.1 挂载 debugfs(内核调试文件系统)
# 临时挂载debugfs(重启失效) sudo mount -t debugfs none /sys/kernel/debug # 永久挂载(可选,编辑fstab) echo "none /sys/kernel/debug debugfs defaults 0 0" | sudo tee -a /etc/fstab5.2.2 查看 CPU 频率档位与实时频率
# 查看CPU0支持的所有频率档位 cat /sys/devices/system/cpu/cpu0/cpufreq/scaling_available_frequencies # 查看CPU0当前实时运行频率(动态变化) watch -n1 cat /sys/devices/system/cpu/cpu0/cpufreq/cpuinfo_cur_freq使用场景:持续观察频率变化,当负载小幅波动时,频率保持不变,说明缓存命中。
5.3 实操二:使用 ftrace 跟踪 cached_raw_freq 与函数调用
ftrace 是 Linux 内核原生跟踪工具,可跟踪get_next_freq调用次数,对比有缓存 / 无缓存下的调用频次差异。
5.3.1 开启函数跟踪
# 切换到debugfs跟踪目录 cd /sys/kernel/debug/tracing # 清空原有跟踪日志 sudo echo > trace # 设置跟踪器为function(函数调用跟踪) sudo echo function > current_tracer # 只跟踪核心函数 get_next_freq sudo echo get_next_freq > set_ftrace_filter # 开启跟踪 sudo echo 1 > tracing_on5.3.2 压测 CPU,触发频繁调频
新开一个终端,使用stress工具制造持续负载:
# 安装压测工具 sudo apt install stress -y # 4线程CPU压测,持续运行 stress -c 4 -t 305.3.3 查看跟踪日志
# 查看跟踪结果,统计get_next_freq调用次数 sudo cat trace | grep get_next_freq | wc -l现象分析:
- 负载稳定阶段:
get_next_freq调用次数大幅下降,因为cached_raw_freq缓存命中,大部分调频请求直接复用结果; - 负载突增 / 突降阶段:调用次数短暂上升,缓存失效,重新计算频率。
5.3.4 关闭跟踪
sudo echo 0 > tracing_on sudo echo > set_ftrace_filter sudo echo nop > current_tracer5.4 实操三:编写测试程序,模拟高频任务触发调频
编写一个简单的用户态压测程序,模拟频繁任务唤醒场景,最大化触发 schedutil 调频,直观体现缓存优化效果。
5.4.1 C 语言压测代码(cpu_stress.c)
/* * cpu_stress.c : 高频循环任务,模拟短生命周期任务,触发调度器频繁调频 * 编译:gcc cpu_stress.c -o cpu_stress -pthread * 运行:./cpu_stress 4 (4个线程) */ #include <stdio.h> #include <stdlib.h> #include <pthread.h> #include <unistd.h> // 线程工作函数:死循环空运算,占用CPU void *cpu_loop(void *arg) { (void)arg; while(1) { // 简单运算,制造持续CPU负载 int a = 0; for(int i = 0; i < 10000; i++) { a += i; } // 短暂休眠,模拟IO唤醒场景,触发调度器事件 usleep(100); } return NULL; } int main(int argc, char *argv[]) { int thread_num = 2; pthread_t *tid; if(argc >= 2) { thread_num = atoi(argv[1]); } tid = (pthread_t *)malloc(sizeof(pthread_t) * thread_num); if(NULL == tid) { perror("malloc failed"); return -1; } // 创建指定数量工作线程 for(int i = 0; i < thread_num; i++) { pthread_create(&tid[i], NULL, cpu_loop, NULL); } // 等待线程(不会退出) for(int i = 0; i < thread_num; i++) { pthread_join(tid[i], NULL); } free(tid); return 0; }5.4.2 编译与运行命令
# 编译(-pthread 链接线程库) gcc cpu_stress.c -o cpu_stress -pthread # 后台运行,4线程压测 ./cpu_stress 4 & # 查看CPU负载与调频状态 top watch -n1 cat /sys/devices/system/cpu/cpu0/cpufreq/cpuinfo_cur_freq5.4.3 实验结论
- 线程稳定运行后,CPU 负载保持平稳,
cpuinfo_cur_freq固定在某一档频率; - ftrace 观测到
get_next_freq调用频次极低,缓存完全生效; - 手动 kill 压测进程,负载骤降,缓存失效,函数调用短暂增加,频率下降。
5.5 实操四:内核源码小修改,禁用缓存对比性能(进阶)
为直观对比缓存的优化效果,我们小幅修改内核代码,注释缓存判断逻辑,对比两种场景下的 CPU 开销。
5.5.1 修改 cpufreq_schedutil.c
找到get_next_freq中的缓存判断代码,注释掉:
// 注释原有缓存逻辑,强制每次都重新计算 /* if (freq == sg_policy->cached_raw_freq && !sg_policy->need_freq_update) { return sg_policy->next_freq; } */ sg_policy->cached_raw_freq = freq;5.5.2 重新编译内核并重启
# 编译内核(多核编译,-j 后接CPU核心数) make -j$(nproc) # 安装模块 + 安装内核 sudo make modules_install sudo make install # 重启系统,进入新内核 sudo reboot5.5.3 对比测试
- 运行压测程序,使用
perf top观测内核态 CPU 占比:
perf top -g- 对比结果:
- 开启缓存:内核态 CPU 开销低,
get_next_freq、map_util_freq占用极少; - 禁用缓存:内核态开销明显上升,调频相关函数持续占用 CPU,负载越高差异越明显。
- 开启缓存:内核态 CPU 开销低,
六、常见问题与解答(结合实操报错、现象)
Q1:切换 schedutil 后,scaling_governor报错权限不足?
现象:echo schedutil > xxx提示 Permission denied解答:cpufreq 文件系统属于内核 sysfs,必须使用 root 权限,命令修改为:
sudo sh -c "echo schedutil > /sys/devices/system/cpu/cpu0/cpufreq/scaling_governor"Q2:ftrace 看不到get_next_freq函数,跟踪为空?
原因 1:内核未开启CONFIG_FUNCTION_TRACER跟踪配置;原因 2:内核版本过旧,schedutil 未内置;解决:重新编译内核,开启Kernel hacking -> Tracers -> Function tracer。
Q3:CPU 频率一直固定在最高档,缓存完全不切换?
原因:CPU 持续满负载,raw_freq始终等于最大值,缓存一直命中,属于正常现象。排查:停止压测,观察频率是否下降,若依然不变,检查主板 BIOS 是否锁定 CPU 频率。
Q4:负载小幅变化,但频率频繁跳动,缓存失效?
原因 1:内核freq_update_delay_ns调频限流时间设置过小,频繁刷新缓存;原因 2:系统存在大量短时 IO 任务,触发 IO boost 强制刷新need_freq_update;解决:调整 schedutil 限流参数,或优化应用层 IO 逻辑。
Q5:虚拟机中无法看到 cpufreq 频率文件?
原因:虚拟机虚拟化层屏蔽了 DVFS 功能;解决:更换物理机测试,或 KVM 虚拟机添加 CPU 调频模拟参数。
七、实践建议与最佳实践
结合多年内核调优、嵌入式落地经验,总结 schedutil + cached_raw_freq 缓存机制的落地技巧、调优方案与排错规范。
7.1 通用调优技巧
限流参数优化
sugov_policy->freq_update_delay_ns是调频最小间隔,默认值较小。对于服务器场景,可适当增大间隔,减少缓存刷新次数;嵌入式实时系统保持默认,保证响应速度。区分场景使用缓存特性
- 服务器、离线计算:利用缓存减少内核开销,容忍小幅频率延迟;
- 实时工控、车载系统:可临时关闭缓存(修改内核),保证负载变化时频率秒级响应。
结合 PELT 负载调优cached_raw_freq 缓存的是负载换算后的频率,若 PELT 采样精度过高,会导致负载频繁小幅波动,缓存反复失效。高稳定性场景可适度调整 PELT 衰减系数。
7.2 调试排错最佳实践
排错顺序规范频率异常 → 查看实时频率文件 → ftrace 跟踪
get_next_freq→ 判断缓存是否命中 → 分析负载来源 → 定位应用 / 内核问题。性能观测组合工具日常调优固定组合:
top(应用负载)+perf(内核开销)+ftrace(函数调用)+cpufreq(频率状态)。线上环境禁忌线上生产服务器禁止随意修改内核源码关闭缓存,会导致内核 CPU 开销上涨、功耗增加;如需调试,务必在测试机复现问题。
7.3 嵌入式设备专属建议
ARM 嵌入式设备算力弱,是缓存机制的最大受益场景:
- 优先使用 Linux 5.4+ 内核,cached_raw_freq 逻辑更完善;
- 不要频繁切换 schedutil/performance 调节器,缓存状态错乱会导致功耗飙升;
- IO 密集型嵌入式设备,注意 IO boost 机制会强制刷新缓存,需评估调频压力。
7.4 代码开发规范
二次开发 schedutil 相关功能时:
- 修改
cached_raw_freq必须配合need_freq_update标志位,避免缓存脏数据; - 不要在中断上下文长时间操作该变量,该字段被
raw_spinlock_t保护; - 新增频率计算逻辑时,保持 “先对比缓存、再计算” 的架构,延续内核设计思想。
八、总结与落地应用场景
8.1 全文核心要点回顾
- cached_raw_freq 定位:schedutil 调频器的原始频率缓存字段,存储上一次公式计算出的中间频率值;
- 核心原理:负载稳定时复用缓存结果,跳过重复计算,降低调频链路 CPU 开销;
- 核心函数:
get_next_freq是缓存判断、更新的唯一入口,整个机制围绕该函数运转; - 优化价值:以极小内存开销,解决高频调频场景下的内核冗余计算问题,兼顾性能与功耗。
8.2 落地应用场景再梳理
- 互联网服务器集群:高并发 Web、缓存、数据库服务,任务频繁唤醒,缓存削减内核开销,提升整机吞吐;
- 工业实时 Linux:工控机、运动控制设备,保证调频低抖动,不干扰实时任务调度;
- ARM 边缘计算网关:低算力硬件下,减少内核运算,降低整机功耗,延长设备运行时间;
- 车载娱乐 / 车控系统:多任务并发场景,平衡系统响应与功耗;
- 学术与研发:内核功耗子系统研究、调度器联合调频方案设计、课程实验与毕业论文。
8.3 学习延伸方向
掌握本章节内容后,可继续深入:schedutil 的 IO Boost 机制、PELT 负载算法、CPU UCLAMP 负载约束、cpufreq 硬件 OPP 驱动适配,形成完整的 Linux 调度 + 功耗知识体系。
本教程从原理、源码、实操、排错、调优全维度解析了 cached_raw_freq 频率缓存机制,所有案例均可复现,代码与命令经过实测验证,可直接用于工程落地、技术报告与学术研究。建议读者结合内核源码逐行跟踪执行流,加深对 Linux 调度与调频联动架构的理解。
