Linux Schedutil work_in_progress:调频任务并发互斥控制实战详解
2026/7/22 4:24:14 网站建设 项目流程

一、简介

1.1 技术背景

Schedutil 采用事件驱动式调频触发机制,进程唤醒、上下文切换、硬中断退出、软中断收尾都会调用cpufreq_update_util函数向上推送 CPU 负载数据。 单颗 CPU 在极短时间内可能被多个执行路径同时触发调频更新:

  1. 普通用户态进程切换触发一次 util 上报;
  2. 同一时刻网卡硬中断处理完毕,再次调用上报接口;
  3. 后台内核线程调度又发起一次频率计算请求。

如果没有并发保护,多个执行流会并行进入sugov_update_single_freq核心调频函数,出现严重的竞态条件:

  • 多线程同时读写utilcached_raw_freqnext_freqlast_freq_update_time等共享结构体成员,数据被交叉覆盖,负载统计错乱;
  • 多次重复下发硬件变频指令,DVFS 寄存器频繁改写,CPU 电压频率来回抖动,硬件损耗增加;
  • limits_changed标记被多个流程同时检测并清零,导致策略变更刷新逻辑丢失,频率限制长期不生效;
  • 冷却防抖时间戳last_freq_update_time被无序改写,防抖间隔完全失效,出现毫秒级疯狂变频。

为解决同一 CPU 核心多条执行路径并发进入调频逻辑的线程安全问题,Linux 内核在每 CPU 独立的 struct sugov_policy结构体中引入布尔型标记位work_in_progress。 该字段本质是一个简易自旋锁语义的互斥标识:

  1. 任意执行流准备进入调频主逻辑前,先判断work_in_progress
  2. 若标记为false,立即将其置为true,独占本次调频计算流程;
  3. 其余同时抵达的触发路径检测到标记为true,直接放弃本次调频更新,避免重入;
  4. 本次频率计算、边界裁剪、指令下发全部完成后,再将work_in_progress重置为false,释放锁,允许下一次调频流程进入。

一句话通俗概括核心作用:work_in_progress = 单 CPU 调频流程的独占门禁,防止多路径并发重入同一个核心的调频逻辑,保证每一次频率决策原子执行,杜绝数据错乱与硬件频繁乱变频。

该机制属于 Schedutil 内核内部无锁轻量化并发控制,没有重量级自旋锁的上下文切换开销,仅依靠单字段原子读写实现互斥,是保障 DVFS 调频稳定性最基础的底层设计,也是很多嵌入式设备频率抖动、负载统计异常问题的根因知识点。

1.2 典型落地应用场景

  1. 高并发网关、小包转发服务器百万级数据包收发场景下,单 CPU 中断上报极其密集,若无work_in_progress互斥会造成大量并发调频请求,内核软中断开销飙升;该标记自动丢弃冗余并发请求,稳定调频链路。
  2. ARM 嵌入式多外设采集设备串口、CAN、ADC 多路外设同时产生中断,多路中断路径同时触发 util 更新,依靠该字段避免多线程篡改 sugov_policy 内部状态,防止采集任务频率紊乱卡顿。
  3. PREEMPT_RT 硬实时 Linux 控制系统实时任务抢占频繁,调度触发点极多,并发调频会引入不确定的内核临界区时延;互斥标记压缩临界区执行次数,缩小调度抖动区间,提升实时确定性。
  4. 云原生宿主机多租户混部场景多个虚拟机 vhost 中断共用宿主机物理 CPU,海量上报请求汇聚,互斥机制过滤重复调频事件,降低宿主机内核 CPU 占用。
  5. 内核定制与二次开发场景开发者新增自定义负载上报入口时,必须理解该互斥机制,否则新增上报接口会和原生流程并发冲突,导致调频功能异常。

1.3 学习本章核心价值

  1. 彻底理解 Schedutil 单核心调频流程的线程安全设计,明白为什么密集中断不会造成内核逻辑崩溃;
  2. 理清work_in_progress完整的上锁、执行、解锁生命周期,区分 “直接执行” 与 “直接丢弃” 两条分支;
  3. 能够使用 perf 探针捕获并发重入被拦截的事件,量化系统冗余调频请求数量;
  4. 排查 “CPU 频率无规律跳变、负载数值忽高忽低” 这类疑难问题时,定位是否存在互斥失效场景;
  5. 补全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 为什么不使用自旋锁 / 互斥锁?

  1. 性能开销最小化:util 上报路径处于中断上下文、调度快路径,重量级锁会带来死锁风险与调度延迟;
  2. 设计取舍合理:短时间内多次上报负载差异极小,丢弃冗余请求不会影响调频最终结果,只是少一次重复计算;
  3. 无阻塞设计:失败直接返回不忙等,不会占用 CPU 空转,符合中断上下文不可睡眠、不可长时间阻塞的内核规范。

2.5 与其他 sugov_policy 字段联动关系

  1. cached_raw_freq:并发时若不拦截,多个流程会同时覆盖缓存值,导致缓存错乱;
  2. limits_changed:多线程同时检测该标记会出现重复清零,刷新逻辑失效;
  3. last_freq_update_time:无序改写时间戳会让防抖冷却机制完全失效;
  4. util总负载:多路径同时累加 util 数值,会造成负载虚高,频率异常拉满。

2.6 per-CPU 隔离特性

work_in_progress每一颗逻辑 CPU 独立拥有,CPU0 的互斥标记不会影响 CPU1 的调频流程,多核之间完全互不干扰,严格贴合 Schedutil per-CPU 架构设计。

三、环境准备

3.1 软硬件环境硬性要求

  1. 操作系统:Ubuntu 20.04 / 22.04、Debian 11+、CentOS Stream 8/9、Buildroot 嵌入式 Linux;
  2. 内核版本:Linux 5.0 及以上正式集成 work_in_progress 互斥逻辑,推荐 5.15 / 6.1 LTS 长期支持内核;
  3. 硬件:具备 DVFS 动态调频的物理 x86 服务器、ARM 开发板;虚拟机可完成探针验证,硬件变频效果无法直观观测;
  4. 操作权限:sysfs 配置修改、perf 内核探针挂载、压力测试均需要 root 管理员权限。

3.2 依赖工具一键批量安装

Ubuntu / Debian 发行版

bash

运行

apt update -y apt install cpufrequtils stress-ng watch perf ifstat -y
CentOS / RHEL / Stream 发行版

bash

运行

yum makecache fast yum install cpufrequtils stress-ng watch perf ifstat -y

3.3 工具用途说明

  1. cpufrequtils:全局切换 schedutil 调频器,确认调速器正常加载;
  2. stress-ng:生成 CPU 循环任务,持续触发调度上报;
  3. ifstat + ping -f:制造海量网卡中断,高频并发触发 util 上报;
  4. perf:挂载 kprobe 探针,分别捕获「抢占成功正常执行」「抢占失败直接丢弃」两条代码分支;
  5. 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 == 1'=work_do # 探针2:抢占失败,直接return丢弃本次请求 perf probe 'sugov_update_single_freq+%if policy->work_in_progress == 0'=work_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/online

CPU 上线时新建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

五、常见问题与精准问题解答

Q1:work_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.15+LTS 内核即可彻底规避该边界 bug。

Q5:同一个 CPU 多个内核线程同时触发上报,一定会有请求被跳过吗?

答复:原子比较交换是单指令执行,同一时刻只有一条执行流能抢占成功,其余全部直接返回跳过,这是该互斥机制的固有逻辑,属于正常现象。

Q6:EAS 能效调度是否会受该互斥标记影响?

答复:EAS 负责跨 CPU 任务迁移,work_in_progress仅管控单 CPU 内部调频计算流程;二者层级分离,EAS 的核心选择逻辑不会被该字段拦截,互不干扰。

六、实践建议与生产环境最佳实践

6.1 分业务场景运维调优规范

1)网络网关、防火墙、小包转发节点

该类设备天然海量中断并发上报,work_in_progress会自动过滤 90% 以上冗余调频请求,不建议做任何内核层面修改;只需保证内核版本不低于 5.4,原生机制即可保障内核开销最低。

2)ARM 嵌入式手持、车载终端

前台 UI 线程 + 外设中断多路上报,互斥机制避免界面刷新时频率来回跳变;不要频繁启停应用造成大量调度事件,减少内核调频路径触发次数。

3)PREEMPT_RT 硬实时工控系统

实时上下文禁止长时间占用work_in_progress临界区,内核原生代码临界区极短,不会引入额外调度时延;禁止自行修改内核源码加长临界区代码,否则会破坏硬实时确定性。

4)大数据离线计算集群

进程长期 100% 满载,util 数值稳定,极少并发上报冲突,该机制几乎不会触发跳过分支,对业务无任何影响,保持内核默认配置即可。

6.2 内核开发与二次开发避坑准则

  1. 自行新增cpufreq_update_util自定义上报入口时,绝对不能绕过 work_in_progress 判断直接调用调频逻辑,否则必然引发结构体数据竞态错乱;
  2. 临界区内禁止添加睡眠、阻塞、长耗时操作,否则标记长期为 true,后续所有调频请求全部被丢弃,CPU 频率锁死无法更新;
  3. 热插拔、policy 销毁分支务必确保结构体完整释放,防止标记野指针访问 Oops 内核崩溃。

6.3 线上故障标准排查流程

  1. CPU 频率无规律频繁跳变 → 核查是否内核版本过低缺少该互斥机制,升级 LTS 内核;
  2. 负载很高但频率长期不更新 → 排查是否临界区死锁标记 stuck 为 true,重启调速器重置 policy;
  3. 内核软中断 CPU 占用偏高 → 使用 perf 查看 work_skip 占比,占比过高说明上报过于密集,可适当增大freq_update_delay_ns防抖间隔合并请求。

6.4 集群基线配置建议

不需要针对work_in_progress添加任何用户态配置项,该机制属于内核透明底层保护逻辑;服务器基线只需要固定调速器为 schedutil、开启 irq_time_accounting 中断统计、配置合理防抖延迟即可。

七、总结与工程落地应用延伸

7.1 全文核心知识点复盘

  1. work_in_progress 本质定位:sugov_policy 每 CPU 私有布尔互斥标记,采用无锁原子 CAS 实现轻量级并发控制,用于防止多条内核执行路径同时进入单核心调频主函数,规避共享成员变量读写竞态;
  2. 完整生命周期:抢占置位 true → 执行负载聚合、缓存校验、频率计算、硬件下发全流程 → 执行完毕复位 false;抢占失败直接丢弃本次上报请求;
  3. 架构隔离特性:严格 per-CPU 独立标记,多核之间互斥状态完全隔离,互不影响;CPU 上下线、调速器切换会销毁重建结构体,标记自动初始化无残留;
  4. 设计取舍优势:放弃阻塞锁、采用非阻塞直接丢弃冗余请求,适配中断上下文不可睡眠约束,最小化内核性能开销;
  5. 联动上层机制:与 limits_changed、cached_raw_freq、irq_time_accounting、防抖冷却字段共同构成 Schedutil 稳定运行的完整防护体系。

7.2 多场景实战落地核心价值

  1. 高并发网络基础设施稳定性加固:网关、负载均衡设备海量中断上报场景下,依靠原生互斥机制避免内核逻辑紊乱,降低软中断 CPU 开销,提升网络转发稳定性;
  2. 嵌入式终端电源管理可靠性提升:多路外设中断不会造成 DVFS 寄存器无序改写,减少 CPU 电压频繁切换带来的硬件损耗与发热;
  3. 实时 Linux 系统时延收敛:压缩调频临界区并发冲突带来的不确定调度抖动,让工业控制、自动驾驶主控的周期任务时序更加稳定;
  4. 云虚拟化宿主机资源管控:大量虚拟机虚拟中断汇聚时,过滤重复调频事件,防止宿主机内核被海量上报请求挤占算力,提升整体租户隔离稳定性。

7.3 Schedutil + CPUFreq 全系列知识体系最终闭环

本文作为 Schedutil per-CPU sugov_policy 结构体最后一个核心状态字段讲解,至此整套 Linux DVFS 动态电压频率调节从用户配置、调度上报、负载统计、中断核算、缓存优化、参数变更刷新、防抖节流、并发互斥、热插拔生命周期、能效协同调度、硬件指令下发全链路 12 大核心模块全部讲解完毕,完整链路汇总:

  1. scaling_min_freq /scaling_max_freq:用户自定义频率硬边界
  2. limits_changed:策略修改强制刷新缓存标记
  3. scaling_governor:调频调速器绑定与切换
  4. sugov_policy:单 CPU 调频状态总容器
  5. util:CFS 任务基础负载统计
  6. irq_time_accounting:硬 / 软中断耗时并入负载
  7. map_util_freq:负载数值映射目标原始频率
  8. cached_raw_freq:重复计算缓存优化
  9. freq_update_delay_ns + last_freq_update_time:变频防抖节流
  10. work_in_progress:并发重入互斥与原子性保护
  11. CPU Hotplug:policy 创建、销毁、资源回收生命周期
  12. EAS 能效调度:跨 CPU 任务迁移与核心择优决策
  13. cpufreq 驱动层:向硬件下发频率电压寄存器指令

整套知识体系可直接用于企业服务器性能基线规范编写、嵌入式 Linux 固件电源管理模块开发、Linux 内核模块二次定制开发、线上服务器 / 嵌入式设备疑难性能故障根因定位、PREEMPT_RT 实时系统调优等工程化工作。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询