Linux Schedutil work_in_progress:调频任务并发互斥控制实战详解
一、简介1.1 技术背景Schedutil 采用事件驱动式调频触发机制进程唤醒、上下文切换、硬中断退出、软中断收尾都会调用cpufreq_update_util函数向上推送 CPU 负载数据。 单颗 CPU 在极短时间内可能被多个执行路径同时触发调频更新普通用户态进程切换触发一次 util 上报同一时刻网卡硬中断处理完毕再次调用上报接口后台内核线程调度又发起一次频率计算请求。如果没有并发保护多个执行流会并行进入sugov_update_single_freq核心调频函数出现严重的竞态条件多线程同时读写util、cached_raw_freq、next_freq、last_freq_update_time等共享结构体成员数据被交叉覆盖负载统计错乱多次重复下发硬件变频指令DVFS 寄存器频繁改写CPU 电压频率来回抖动硬件损耗增加limits_changed标记被多个流程同时检测并清零导致策略变更刷新逻辑丢失频率限制长期不生效冷却防抖时间戳last_freq_update_time被无序改写防抖间隔完全失效出现毫秒级疯狂变频。为解决同一 CPU 核心多条执行路径并发进入调频逻辑的线程安全问题Linux 内核在每 CPU 独立的 struct sugov_policy结构体中引入布尔型标记位work_in_progress。 该字段本质是一个简易自旋锁语义的互斥标识任意执行流准备进入调频主逻辑前先判断work_in_progress若标记为false立即将其置为true独占本次调频计算流程其余同时抵达的触发路径检测到标记为true直接放弃本次调频更新避免重入本次频率计算、边界裁剪、指令下发全部完成后再将work_in_progress重置为false释放锁允许下一次调频流程进入。一句话通俗概括核心作用work_in_progress 单 CPU 调频流程的独占门禁防止多路径并发重入同一个核心的调频逻辑保证每一次频率决策原子执行杜绝数据错乱与硬件频繁乱变频。该机制属于 Schedutil 内核内部无锁轻量化并发控制没有重量级自旋锁的上下文切换开销仅依靠单字段原子读写实现互斥是保障 DVFS 调频稳定性最基础的底层设计也是很多嵌入式设备频率抖动、负载统计异常问题的根因知识点。1.2 典型落地应用场景高并发网关、小包转发服务器百万级数据包收发场景下单 CPU 中断上报极其密集若无work_in_progress互斥会造成大量并发调频请求内核软中断开销飙升该标记自动丢弃冗余并发请求稳定调频链路。ARM 嵌入式多外设采集设备串口、CAN、ADC 多路外设同时产生中断多路中断路径同时触发 util 更新依靠该字段避免多线程篡改 sugov_policy 内部状态防止采集任务频率紊乱卡顿。PREEMPT_RT 硬实时 Linux 控制系统实时任务抢占频繁调度触发点极多并发调频会引入不确定的内核临界区时延互斥标记压缩临界区执行次数缩小调度抖动区间提升实时确定性。云原生宿主机多租户混部场景多个虚拟机 vhost 中断共用宿主机物理 CPU海量上报请求汇聚互斥机制过滤重复调频事件降低宿主机内核 CPU 占用。内核定制与二次开发场景开发者新增自定义负载上报入口时必须理解该互斥机制否则新增上报接口会和原生流程并发冲突导致调频功能异常。1.3 学习本章核心价值彻底理解 Schedutil 单核心调频流程的线程安全设计明白为什么密集中断不会造成内核逻辑崩溃理清work_in_progress完整的上锁、执行、解锁生命周期区分 “直接执行” 与 “直接丢弃” 两条分支能够使用 perf 探针捕获并发重入被拦截的事件量化系统冗余调频请求数量排查 “CPU 频率无规律跳变、负载数值忽高忽低” 这类疑难问题时定位是否存在互斥失效场景补全sugov_policy结构体最后一个核心状态字段完成 per-CPU 所有内置变量的原理闭环。二、核心概念与底层执行原理2.1 关键名词与字段释义表格字段 / 术语归属结构体数据类型核心含义work_in_progressstruct sugov_policybool 布尔值调频工作进行中标记false 空闲可进入true 正在执行禁止重入sugov_update_single_freq全局函数调度核心入口单 CPU 频率计算、缓存刷新、防抖判定、硬件下发主逻辑cpufreq_update_util上报入口钩子函数任务切换、中断结束后推送负载触发调频入口重入 / 并发执行行为多路径同时调用同一函数多条内核执行流同一时刻进入同一个 CPU 的调频逻辑原子赋值内核操作内存屏障读写对 work_in_progress 的修改使用原子指令避免多核 CPU 自身读写撕裂2.2 标准互斥执行完整流程伪代码// 任意上报路径进入调频主函数 func sugov_update_single_freq(policy): // 第一步原子检查并抢占标记 if atomic_cmpxchg(policy-work_in_progress, false, true) false: // 抢占成功获得执行权限开始完整调频流程 1. 聚合当前CPU task负载 irq中断负载更新policy-util 2. 判断limits_changed标记决定是否清空cached_raw_freq强制重算 3. 根据util调用map_util_freq计算原始目标频率raw_freq 4. 对比cached_raw_freq命中则复用缓存未命中则更新缓存 5. 校验freq_update_delay_ns防抖冷却时间 6. 使用scaling_min/max_freq钳位得到最终next_freq 7. 下发指令修改硬件CPU频率 8. 更新last_freq_update_time时间戳 9. 重置limits_changed为false // 关键收尾释放互斥标记 policy-work_in_progress false; else: // 抢占失败已有流程正在执行调频直接返回丢弃本次请求 return;2.3 两种分支场景详细说明分支 1抢占成功无并发正常执行调频当前没有其他执行流占用该 CPU 的调频逻辑work_in_progress从 false 置为 true走完一整套负载统计、频率计算、硬件变频流程结束后归还标记。 本次 util 上报会真正修改 CPU 运行频率。分支 2抢占失败并发冲突直接丢弃上一个 util 上报还在执行调频计算work_in_progress已经是 true新到来的上报请求直接函数 return 退出。 内核不会缓存本次上报数据直接舍弃本次触发等待下一次空闲时机再处理负载更新。2.4 为什么不使用自旋锁 / 互斥锁性能开销最小化util 上报路径处于中断上下文、调度快路径重量级锁会带来死锁风险与调度延迟设计取舍合理短时间内多次上报负载差异极小丢弃冗余请求不会影响调频最终结果只是少一次重复计算无阻塞设计失败直接返回不忙等不会占用 CPU 空转符合中断上下文不可睡眠、不可长时间阻塞的内核规范。2.5 与其他 sugov_policy 字段联动关系与cached_raw_freq并发时若不拦截多个流程会同时覆盖缓存值导致缓存错乱与limits_changed多线程同时检测该标记会出现重复清零刷新逻辑失效与last_freq_update_time无序改写时间戳会让防抖冷却机制完全失效与util总负载多路径同时累加 util 数值会造成负载虚高频率异常拉满。2.6 per-CPU 隔离特性work_in_progress是每一颗逻辑 CPU 独立拥有CPU0 的互斥标记不会影响 CPU1 的调频流程多核之间完全互不干扰严格贴合 Schedutil per-CPU 架构设计。三、环境准备3.1 软硬件环境硬性要求操作系统Ubuntu 20.04 / 22.04、Debian 11、CentOS Stream 8/9、Buildroot 嵌入式 Linux内核版本Linux 5.0 及以上正式集成 work_in_progress 互斥逻辑推荐 5.15 / 6.1 LTS 长期支持内核硬件具备 DVFS 动态调频的物理 x86 服务器、ARM 开发板虚拟机可完成探针验证硬件变频效果无法直观观测操作权限sysfs 配置修改、perf 内核探针挂载、压力测试均需要 root 管理员权限。3.2 依赖工具一键批量安装Ubuntu / Debian 发行版bash运行apt update -y apt install cpufrequtils stress-ng watch perf ifstat -yCentOS / RHEL / Stream 发行版bash运行yum makecache fast yum install cpufrequtils stress-ng watch perf ifstat -y3.3 工具用途说明cpufrequtils全局切换 schedutil 调频器确认调速器正常加载stress-ng生成 CPU 循环任务持续触发调度上报ifstat ping -f制造海量网卡中断高频并发触发 util 上报perf挂载 kprobe 探针分别捕获「抢占成功正常执行」「抢占失败直接丢弃」两条代码分支watch实时监控 CPU 当前运行主频观察并发拦截后频率不会乱跳。3.4 前置环境校验必须逐条执行bash运行# 整机所有CPU强制切换为Schedutil调频器 cpufreq-set -r -g schedutil # 查看当前CPU0调频驱动是否正常挂载 cat /sys/devices/system/cpu/cpu0/cpufreq/scaling_driver # 检查内核符号是否存在用于后续perf探针 perf probe --check sugov_update_single_freq无报错即代表内核包含目标函数与 work_in_progress 互斥逻辑实验环境就绪。四、分步实战可复现案例实验一单进程缓慢压测无并发全部请求正常执行步骤 1锁定 CPU0 频率上下限固定观测区间bash运行# 读取硬件最大频率作为上限 MAX_HW$(cat /sys/devices/system/cpu/cpu0/cpufreq/cpuinfo_max_freq) echo $MAX_HW /sys/devices/system/cpu/cpu0/cpufreq/scaling_max_freq # 下限使用硬件默认最小值 cat /sys/devices/system/cpu/cpu0/cpuinfo_min_freq /sys/devices/system/cpu/cpu0/cpufreq/scaling_min_freq步骤 2后台单进程绑定 CPU0 慢速循环压测bash运行# 单进程占用cpu0调度上报频率平缓极少并发冲突 stress-ng --cpu 1 --cpu-affinity 0 --timeout 120 步骤 3挂载双分支探针区分两种执行路径bash运行# 探针1成功抢占work_in_progress完整走完调频流程 perf probe sugov_update_single_freq%if policy-work_in_progress 1work_do # 探针2抢占失败直接return丢弃本次请求 perf probe sugov_update_single_freq%if policy-work_in_progress 0work_skip步骤 4采集 10 秒事件统计bash运行perf record -g sleep 10 perf report -g none实验现象work_do事件数量远大于work_skip几乎没有请求被丢弃无并发冲突每一次上报都正常执行频率计算。实验二海量中断制造并发上报大量请求被互斥拦截丢弃步骤 1开启 ping 洪水对网关地址发送超大包极速 ping产生巨量网卡中断bash运行# -f 洪水模式全速发包-s 1472大包最大化中断数量 ping -f -s 1472 192.168.1.1步骤 2再次采集 perf 事件bash运行perf record -g sleep 10 perf report -g none核心实验现象work_skip直接跳过丢弃事件数量暴增远超正常执行分支。 大量同一时刻抵达的 util 上报请求被work_in_progress标记拦截不会并发进入调频函数内核避免了竞态数据错误。实验三手动动态修改 scaling_max_freq验证互斥不会丢失 limits_changed 刷新bash运行# 持续ping制造并发上报 ping -f 192.168.1.1 # 在线调低CPU最大频率上限 echo $((MAX_HW * 7 / 10)) /sys/devices/system/cpu/cpu0/cpufreq/scaling_max_freq # 开启频率监控 watch -n1 cat /sys/devices/system/cpu/cpu0/cpufreq/scaling_cur_freq现象说明 即便存在大量并发上报只要有一次成功抢占到work_in_progress进入主逻辑就会检测limits_changed标记并完成频率重算新的频率限制最终一定会生效不会因为部分请求被丢弃而永久丢失策略更新。实验四CPU 热插拔后标记自动初始化bash运行# 下线CPU1 echo 0 /sys/devices/system/cpu/cpu1/online # 重新上线CPU1 echo 1 /sys/devices/system/cpu/cpu1/onlineCPU 上线时新建sugov_policy结构体work_in_progress默认初始化为 false处于空闲可进入状态无需人工重置标记。实验收尾清理环境还原系统默认状态bash运行# 终止所有压力、ping进程 pkill stress-ng pkill ping # 删除所有perf内核探针 perf probe -d sugov_update_single_freq # 还原CPU0频率上限为硬件原生最大值 cat /sys/devices/system/cpu/cpu0/cpuinfo_max_freq /sys/devices/system/cpu/cpu0/cpufreq/scaling_max_freq五、常见问题与精准问题解答Q1work_in_progress 可以在 sysfs 里直接读写修改吗答复不可以。该成员属于sugov_policy内核结构体内部私有变量没有导出任何用户态 sysfs 文件节点仅在内核调度上下文内部做原子读写应用层无法直接干预、强制解锁或上锁完全由 Schedutil 自动管理生命周期。Q2大量请求被 work_skip 丢弃会不会导致负载严重滞后、频率更新不及时答复不会出现滞后问题。 被丢弃的请求只是本次函数调用退出但 CPU 负载会持续累积在下一次成功进入调频流程时统一计算短时间密集上报的负载值差异极小合并一次计算完全可以等效多次重复计算结果内核设计本身就做了合并冗余请求的优化。Q3切换调频 governor 之后该标记状态会残留吗答复不会残留。切换调速器会销毁当前 CPU 绑定的sugov_policy对象并重新创建实例新结构体work_in_progress固定初始值 false旧标记随内存释放彻底清除不存在死锁卡住的情况。Q4极端情况下会不会出现 work_in_progress 永久置为 true 死锁主线 Linux 官方内核不会出现死锁 无论正常执行还是异常分支退出代码路径一定会将标记重置为 false 仅 4.19 及以下老旧内核存在极小概率异常路径漏写复位升级 5.15LTS 内核即可彻底规避该边界 bug。Q5同一个 CPU 多个内核线程同时触发上报一定会有请求被跳过吗答复原子比较交换是单指令执行同一时刻只有一条执行流能抢占成功其余全部直接返回跳过这是该互斥机制的固有逻辑属于正常现象。Q6EAS 能效调度是否会受该互斥标记影响答复EAS 负责跨 CPU 任务迁移work_in_progress仅管控单 CPU 内部调频计算流程二者层级分离EAS 的核心选择逻辑不会被该字段拦截互不干扰。六、实践建议与生产环境最佳实践6.1 分业务场景运维调优规范1网络网关、防火墙、小包转发节点该类设备天然海量中断并发上报work_in_progress会自动过滤 90% 以上冗余调频请求不建议做任何内核层面修改只需保证内核版本不低于 5.4原生机制即可保障内核开销最低。2ARM 嵌入式手持、车载终端前台 UI 线程 外设中断多路上报互斥机制避免界面刷新时频率来回跳变不要频繁启停应用造成大量调度事件减少内核调频路径触发次数。3PREEMPT_RT 硬实时工控系统实时上下文禁止长时间占用work_in_progress临界区内核原生代码临界区极短不会引入额外调度时延禁止自行修改内核源码加长临界区代码否则会破坏硬实时确定性。4大数据离线计算集群进程长期 100% 满载util 数值稳定极少并发上报冲突该机制几乎不会触发跳过分支对业务无任何影响保持内核默认配置即可。6.2 内核开发与二次开发避坑准则自行新增cpufreq_update_util自定义上报入口时绝对不能绕过 work_in_progress 判断直接调用调频逻辑否则必然引发结构体数据竞态错乱临界区内禁止添加睡眠、阻塞、长耗时操作否则标记长期为 true后续所有调频请求全部被丢弃CPU 频率锁死无法更新热插拔、policy 销毁分支务必确保结构体完整释放防止标记野指针访问 Oops 内核崩溃。6.3 线上故障标准排查流程CPU 频率无规律频繁跳变 → 核查是否内核版本过低缺少该互斥机制升级 LTS 内核负载很高但频率长期不更新 → 排查是否临界区死锁标记 stuck 为 true重启调速器重置 policy内核软中断 CPU 占用偏高 → 使用 perf 查看 work_skip 占比占比过高说明上报过于密集可适当增大freq_update_delay_ns防抖间隔合并请求。6.4 集群基线配置建议不需要针对work_in_progress添加任何用户态配置项该机制属于内核透明底层保护逻辑服务器基线只需要固定调速器为 schedutil、开启 irq_time_accounting 中断统计、配置合理防抖延迟即可。七、总结与工程落地应用延伸7.1 全文核心知识点复盘work_in_progress 本质定位sugov_policy 每 CPU 私有布尔互斥标记采用无锁原子 CAS 实现轻量级并发控制用于防止多条内核执行路径同时进入单核心调频主函数规避共享成员变量读写竞态完整生命周期抢占置位 true → 执行负载聚合、缓存校验、频率计算、硬件下发全流程 → 执行完毕复位 false抢占失败直接丢弃本次上报请求架构隔离特性严格 per-CPU 独立标记多核之间互斥状态完全隔离互不影响CPU 上下线、调速器切换会销毁重建结构体标记自动初始化无残留设计取舍优势放弃阻塞锁、采用非阻塞直接丢弃冗余请求适配中断上下文不可睡眠约束最小化内核性能开销联动上层机制与 limits_changed、cached_raw_freq、irq_time_accounting、防抖冷却字段共同构成 Schedutil 稳定运行的完整防护体系。7.2 多场景实战落地核心价值高并发网络基础设施稳定性加固网关、负载均衡设备海量中断上报场景下依靠原生互斥机制避免内核逻辑紊乱降低软中断 CPU 开销提升网络转发稳定性嵌入式终端电源管理可靠性提升多路外设中断不会造成 DVFS 寄存器无序改写减少 CPU 电压频繁切换带来的硬件损耗与发热实时 Linux 系统时延收敛压缩调频临界区并发冲突带来的不确定调度抖动让工业控制、自动驾驶主控的周期任务时序更加稳定云虚拟化宿主机资源管控大量虚拟机虚拟中断汇聚时过滤重复调频事件防止宿主机内核被海量上报请求挤占算力提升整体租户隔离稳定性。7.3 Schedutil CPUFreq 全系列知识体系最终闭环本文作为 Schedutil per-CPU sugov_policy 结构体最后一个核心状态字段讲解至此整套 Linux DVFS 动态电压频率调节从用户配置、调度上报、负载统计、中断核算、缓存优化、参数变更刷新、防抖节流、并发互斥、热插拔生命周期、能效协同调度、硬件指令下发全链路 12 大核心模块全部讲解完毕完整链路汇总scaling_min_freq /scaling_max_freq用户自定义频率硬边界limits_changed策略修改强制刷新缓存标记scaling_governor调频调速器绑定与切换sugov_policy单 CPU 调频状态总容器utilCFS 任务基础负载统计irq_time_accounting硬 / 软中断耗时并入负载map_util_freq负载数值映射目标原始频率cached_raw_freq重复计算缓存优化freq_update_delay_ns last_freq_update_time变频防抖节流work_in_progress并发重入互斥与原子性保护CPU Hotplugpolicy 创建、销毁、资源回收生命周期EAS 能效调度跨 CPU 任务迁移与核心择优决策cpufreq 驱动层向硬件下发频率电压寄存器指令整套知识体系可直接用于企业服务器性能基线规范编写、嵌入式 Linux 固件电源管理模块开发、Linux 内核模块二次定制开发、线上服务器 / 嵌入式设备疑难性能故障根因定位、PREEMPT_RT 实时系统调优等工程化工作。
