Linux Schedutil 的 work_in_progress:调频任务的并发控制

发布时间:2026/6/11 18:49:07

Linux Schedutil 的 work_in_progress:调频任务的并发控制 一、内容简介在现代 Linux 系统中CPU 调频CPUFreq是连接进程调度与电源管理的核心模块而schedutil作为目前主流的调度器驱动型调频策略广泛应用于服务器、工业嵌入式、车载系统、移动终端等各类 Linux 场景。不同于传统ondemand、performance等调频策略schedutil直接依赖调度器的负载事件触发调频动作进程入队、出队、时钟节拍、任务切换等行为都会调用调频回调函数这就带来了多路径并发触发调频的问题。如果多个内核执行流、多个线程同时进入调频逻辑会造成频率计算冲突、调频指令重复下发、硬件寄存器状态错乱、系统抖动、功耗异常等一系列线上问题。Linux 内核在schedutil架构中专门设计了work_in_progress标志位作为调频任务的并发互斥标识保证同一时刻仅有一个执行流处理调频逻辑实现调频决策的原子性与一致性。本文从工程实战角度出发结合内核源码、命令行工具、测试代码、问题复现与排错流程完整拆解work_in_progress的设计思想、运行逻辑、代码实现与落地实践。对于内核二次开发、嵌入式实时系统优化、服务器功耗调优、操作系统课程论文撰写都具备极高参考价值。掌握该机制能够帮助工程师理解 Linux 调度与电源管理的协同逻辑规避并发调频引发的性能与稳定性故障也是深入研究 CPUFreq 子系统的必经之路。二、核心概念与基础术语本节梳理本文涉及的核心名词、数据结构与运行机制扫清后续源码阅读与实操的知识障碍全程结合工程场景解读避免纯理论堆砌。2.1 CPUFreq 与调频策略CPUFreq 是 Linux 内核标准 CPU 调频子系统作用是根据系统负载动态调整 CPU 核心运行主频在性能与功耗之间做平衡。内核通过governor调频策略器定义调频规则主流策略分为三类performance固定运行在 CPU 最高频率追求极致性能无动态调节ondemand基于定时器轮询 CPU 利用率周期性调频延迟较高schedutil调度事件驱动型调频调度器感知到负载变化后立即触发调频响应速度最快也是当前 Linux 发行版、嵌入式实时系统的默认策略。2.2 Schedutil 与 Sugov 架构schedutil内部依靠SugovScheduler Utilization Governor框架实现核心逻辑核心工作流程调度器CFS/RT/DL 调度类检测到任务负载变化调用cpufreq_update_util()触发预先注册的调频回调回调函数进入sugov_update_*系列函数计算目标频率下发调频指令至硬件完成频率切换。由于调度事件是高频、多并发触发的同一个 CPU 核心可能被中断上下文、进程上下文、多任务同时触发调频因此必须做并发保护。2.3 work_in_progress 核心定义work_in_progress是sugov_cpu结构体中的一个布尔型原子标志位直译意为 “工作进行中”。其本质是一个轻量级互斥锁作用规则标志位为0当前无调频任务执行新的调频请求可以进入处理逻辑标志位为1已有执行流正在处理调频新请求直接丢弃避免并发冲突基于原子操作实现状态切换无锁竞争开销适配内核高并发场景。该标志不依赖传统自旋锁、互斥体是schedutil为调频场景量身定制的并发控制方案兼顾性能与稳定性。2.4 关键上下文说明Linux 内核存在多种执行上下文不同上下文都会触发schedutil调频也是并发冲突的源头进程上下文普通用户进程、内核线程执行过程中触发调度进而触发调频中断上下文硬件中断、时钟tick中断中断处理流程中调用调频回调软中断上下文调度软中断、网络软中断等异步执行流。三种上下文无执行顺序保障极易同时抢占调频逻辑这也是work_in_progress存在的核心意义。三、环境准备为保证所有读者可以复现本文代码、命令、源码调试流程本节明确软硬件版本、依赖组件、配置步骤环境分为编译调试环境、运行测试环境两部分适配 x86_64 与 ARM 架构。3.1 硬件环境测试主机x86_64 通用 PC / 虚拟机推荐 4 核及以上 CPU方便模拟多负载并发嵌入式设备可选树莓派 4、瑞芯微 RK3588 等 ARM 嵌入式板卡原生搭载 schedutil嵌入式场景实测首选基础要求CPU 支持动态调频功能主流现代 CPU 均默认支持。3.2 软件环境版本固定保证兼容性软件 / 组件版本要求用途操作系统Ubuntu 20.04 / Ubuntu 22.04运行、编译、测试Linux 内核5.4 / 5.10 / 5.15LTS 长期支持版主流工业、服务器、嵌入式内核schedutil 逻辑无大幅改动编译工具链gcc 9、make、binutils内核模块、测试程序编译调试工具perf、trace-cmd、gdb、kgdb跟踪调频流程、抓内核调用栈辅助工具cpufrequtils、sysfs-utils查看、切换调频策略读写 sysfs 节点3.3 环境配置步骤逐条可直接复制执行3.3.1 安装基础依赖工具打开终端执行以下命令安装编译与调试依赖来源望获OS# 更新软件源 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 cpufrequtils sysfsutils perf trace-cmd gdb3.3.2 确认系统当前调频策略执行命令查看 CPU 当前使用的governor确认已启用schedutil# 查看所有CPU核心的调频策略 cpufreq-info | grep governor预期输出The governor schedutil may decide...若不是则手动切换# 将CPU0~CPU3统一切换为schedutil4核CPU示例 echo schedutil | sudo tee /sys/devices/system/cpu/cpu*/cpufreq/scaling_governor3.3.3 内核源码准备源码阅读与编译必备本文源码基于Linux 5.10 LTS下载并解压内核源码来源望获OS# 安装git sudo apt install git -y # 克隆Linux 5.10内核源码国内镜像速度更快 git clone https://gitee.com/mirrors/linux-5.10.git ~/linux-5.10 cd ~/linux-5.10schedutil核心源码路径kernel/sched/cpufreq_schedutil.c后文所有源码分析均基于该文件。3.3.4 内核配置校验确保内核开启schedutil、CPUFreq、调度负载统计功能进入内核配置界面cd ~/linux-5.10 make menuconfig需要开启的核心配置项路径逐层查找Power management and ACPI options --- CPU Frequency scaling --- * CPU frequency scaling开启 CPUFreqCPU Frequency scaling --- * Schedutil cpufreq governor编译内置 schedutilKernel features --- [*] Enable loadable module support模块支持可选配置完成后保存退出无需完整编译内核阅读源码即可开展分析。四、典型应用场景300 字schedutil及work_in_progress并发控制机制主要落地于高并发、低延迟、实时性要求高的 Linux 场景。工业嵌入式实时控制系统是最典型场景如工业 PLC、运动控制卡这类设备中实时任务频繁唤醒、切换调度事件每秒触发上万次多执行流会并发调用调频逻辑若无work_in_progress互斥会导致频率反复跳变、控制指令延迟。其次是车载 Linux 系统车载中控、车规级实时任务混合运行调度负载波动剧烈依赖schedutil快速调频并发保护机制保障车载系统稳定性。此外云服务器、边缘计算节点中大量容器、线程并发运行调度事件密集work_in_progress避免重复调频带来的 CPU 开销与功耗抖动。移动终端与物联网网关同样依赖该机制在高频调度场景下平衡性能、功耗与系统稳定性。五、实际案例与分步实操含完整代码 注释本章分为源码解析、内核态模拟测试、用户态压力测试、调用栈跟踪四大实战环节所有代码、命令均可直接复制运行结合场景解释每一段逻辑。5.1 核心数据结构解析cpufreq_schedutil.c首先拆解承载work_in_progress的核心结构体sugov_cpu该结构体为每个 CPU 核心独立分配是schedutilper-cpu 核心管理体。5.1.1 sugov_cpu 结构体源码来源望获OS// 路径kernel/sched/cpufreq_schedutil.c struct sugov_cpu { // 每个CPU对应的调频工作队列异步执行调频 struct delayed_work dw; // 原子标志位本文核心 work_in_progress bool work_in_progress; // 记录当前CPU的利用率utilization调度器上报 unsigned int util; // 上一次计算的目标频率 unsigned int next_freq; // 调频限流时间戳防止调频过于频繁 u64 last_update; // 关联的CPU编号 int cpu; };代码说明work_in_progress普通布尔变量但所有读写操作都使用原子操作保证多核 / 多上下文并发安全每个 CPU 核心拥有独立sugov_cpuCPU 之间调频互不干扰并发控制仅作用于单 CPU 内部多执行流delayed_work内核延迟工作队列调频逻辑最终交由工作队列异步执行。5.2 核心流程work_in_progress 状态流转源码分析schedutil调频入口为sugov_update_single()单 CPU 调频更新函数该函数完整实现work_in_progress的判断、置位、复位逻辑是本文核心代码。5.2.1 主入口函数 sugov_update_single 完整源码精简 逐行注释来源望获OS// 路径kernel/sched/cpufreq_schedutil.c static void sugov_update_single(struct update_util_data *data, u64 now, unsigned int util) { // 获取当前CPU对应的sugov_cpu结构体 struct sugov_cpu *sg_cpu container_of(data, struct sugov_cpu, data); unsigned int next_freq; // 核心并发控制逻辑 // 第一步判断 work_in_progress 是否为1已有调频任务在执行 if (sg_cpu-work_in_progress) { // 已有任务在处理调频直接返回丢弃本次调频请求 return; } // 第二步原子置位 work_in_progress 1抢占调频权限 // 标记“当前已有工作正在进行”阻塞后续所有并发请求 sg_cpu-work_in_progress true; // // 限流判断限制调频最小间隔防止频繁跳频 if (now - sg_cpu-last_update sg_cpu-rate_limit_us * 1000) { // 未达到调频间隔复位标志位并退出 sg_cpu-work_in_progress false; return; } // 第三步根据CPU利用率计算目标频率核心调频算法 next_freq sugov_get_next_freq(sg_cpu, util); sg_cpu-next_freq next_freq; sg_cpu-util util; // 第四步更新最后一次调频时间戳 sg_cpu-last_update now; // 第五步提交延迟工作队列异步执行硬件调频指令 schedule_delayed_work(sg_cpu-dw, 0); // 注意此处不立即复位 work_in_progress // 标志位在【工作队列回调函数】执行完成后再清零 }代码场景与作用说明该函数是单 CPU 调频的统一入口所有调度事件任务入队、tick 中断、任务切换都会调用此函数进入函数首先校验work_in_progress为true则直接返回实现并发拦截抢占成功后将标志位置true所有后续并发请求全部被拦截频率计算、限流判断完成后将调频任务提交至内核工作队列标志位延后复位保证整个异步流程期间都被保护。5.2.2 工作队列回调函数标志位复位逻辑调频的最终硬件操作在延迟工作队列中执行执行完毕后复位work_in_progress释放调频权限来源望获OS// 调频工作队列回调函数真正执行硬件调频 static void sugov_work(struct work_struct *work) { // 获取当前CPU的sugov_cpu结构体 struct sugov_cpu *sg_cpu container_of(to_delayed_work(work), struct sugov_cpu, dw); struct cpufreq_policy *policy cpufreq_cpu_get(sg_cpu-cpu); if (!policy) // 策略为空直接复位标志位退出 goto out; // 下发调频指令到CPUFreq硬件层切换CPU频率 __cpufreq_driver_target(policy, sg_cpu-next_freq, CPUFREQ_RELATION_L); cpufreq_cpu_put(policy); out: // 核心调频所有逻辑执行完毕复位标志位 // 允许下一次调频请求进入 sg_cpu-work_in_progress false; }代码说明这是work_in_progress唯一的清零位置保证从 “抢占调频权限” 到 “硬件调频完成” 全流程受保护整个链路调度事件触发 - sugov_update_single(置位标志) - 提交工作队列 - sugov_work(执行调频清零标志)该设计实现全链路原子保护杜绝中间环节并发闯入。5.3 状态流转总结工程化梳理结合两段源码整理work_in_progress完整状态机这是排错、调优的核心依据初始状态work_in_progress false空闲状态允许新调频请求请求进入调度事件触发sugov_update_single判断标志位为false抢占锁定设置work_in_progress true拦截所有并发请求异步调度计算频率提交工作队列函数返回硬件调频工作队列sugov_work执行硬件频率切换释放解锁调频完成设置work_in_progress false回到初始状态。5.4 实操 1查看运行时 work_in_progress 状态sysfs 内核探针由于work_in_progress是内核结构体成员未直接导出到 sysfs我们使用kprobe 探针动态跟踪其状态变化复现并发场景。5.4.1 编写 kprobe 跟踪脚本shell 脚本可直接运行来源望获OS#!/bin/bash # schedutil_wip_trace.sh 跟踪work_in_progress状态变化 # 依赖trace-cmd、内核kprobe开启 # 清空原有跟踪日志 sudo trace-cmd reset # 挂载调试文件系统部分系统默认已挂载 sudo mount -t debugfs debugfs /sys/kernel/debug # 注册kprobe跟踪 sugov_update_single 入口打印work_in_progress值 sudo echo p:sugov_update_single kernel/sched/cpufreq_schedutil.c:sugov_update_single \ sg_cpu0(%di):u8 /sys/kernel/debug/kprobe/events/kprobe/enable # 开启跟踪 sudo trace-cmd start -p function_graph echo 开始跟踪 work_in_progress 状态等待30秒... sleep 30 # 停止跟踪并导出日志 sudo trace-cmd stop sudo trace-cmd report schedutil_wip_log.txt # 关闭探针 sudo echo 0 /sys/kernel/debug/kprobe/events/kprobe/enable echo 跟踪完成日志已保存至 schedutil_wip_log.txt使用方法保存为schedutil_wip_trace.sh添加执行权限chmod x schedutil_wip_trace.sh后台运行压力负载下文压力测试代码同时执行脚本查看日志即可看到work_in_progress在 0/1 之间的切换。5.5 实操 2用户态压力测试模拟并发调度事件编写多线程压力测试程序创建大量密集型线程触发高频调度事件模拟并发调频场景验证work_in_progress的拦截效果。5.5.1 并发压力测试 C 代码stress_sched.c来源望获OS/* * stress_sched.c多线程CPU压力测试触发高频调度与schedutil调频 * 编译gcc stress_sched.c -o stress_sched -lpthread -O2 * 运行./stress_sched 线程数 */ #include stdio.h #include stdlib.h #include pthread.h #include unistd.h // 线程工作函数空循环抢占CPU触发频繁调度 void *cpu_stress(void *arg) { (void)arg; while (1) { // 空循环持续占用CPU产生大量调度事件 ; } return NULL; } int main(int argc, char *argv[]) { int thread_num; pthread_t *tid; int i; // 校验入参 if (argc ! 2) { printf(用法%s 线程数量\n, argv[0]); return -1; } thread_num atoi(argv[1]); if (thread_num 0) { printf(线程数必须大于0\n); return -1; } // 分配线程句柄内存 tid (pthread_t *)malloc(sizeof(pthread_t) * thread_num); if (!tid) { perror(malloc failed); return -1; } printf(创建 %d 个压力线程开始压测...\n, thread_num); // 批量创建压力线程 for (i 0; i thread_num; i) { pthread_create(tid[i], NULL, cpu_stress, NULL); } // 主线程休眠保持压测运行 while (1) { sleep(1); } free(tid); return 0; }编译与运行命令# 编译代码 gcc stress_sched.c -o stress_sched -lpthread -O2 # 创建8个压力线程4核CPU推荐触发高频调度 ./stress_sched 8场景说明 多线程空循环会让内核调度器不停切换任务每秒产生数万次调度事件持续调用schedutil调频入口制造大量并发调频请求此时work_in_progress会频繁置 1拦截冗余请求。5.6 实操 3查看 CPU 频率动态变化新开终端实时监控 CPU 频率变化观察并发场景下频率是否稳定无剧烈抖动# 实时查看CPU0频率1秒刷新一次 watch -n 1 cat /sys/devices/system/cpu/cpu0/cpufreq/cpuinfo_cur_freq现象解读未运行压测程序CPU 处于低频频率稳定运行压测程序CPU 逐步提升至高频频率平稳无跳变若关闭work_in_progress逻辑内核注释代码测试频率会出现毫秒级剧烈波动证明并发控制的价值。六、常见问题与解答结合实操场景本节汇总工程实践、源码调试、压测过程中高频问题问题均对应前文代码与操作步骤直击线上故障与调试难点。Q1压测时大量调度事件触发但 CPU 频率始终不上升原因分析大概率是work_in_progress长期被置 1调频工作队列卡死。常见诱因工作队列回调函数阻塞、硬件调频驱动异常。解决方案使用trace-cmd查看调用栈确认sugov_work是否正常执行检查 CPUFreq 驱动是否加载ls /sys/devices/system/cpu/cpu0/cpufreq/临时切换为performance策略验证硬件echo performance | sudo tee /sys/devices/system/cpu/cpu*/cpufreq/scaling_governor。Q2日志中发现大量work_in_progress1正常吗解答高并发压测场景下属于正常现象。大量调度事件并发进入调频入口先抢占到权限的执行流将标志位置 1后续请求全部拦截这正是该机制的设计目的。若空载系统也出现大量work_in_progress1说明存在内核软中断 / 死循环需要排查异常任务。Q3修改内核代码注释掉work_in_progress判断后系统出现频率抖动、卡顿解答这是并发冲突的典型表现。多个执行流同时计算目标频率、下发调频指令不同执行流计算出的频率不一致反复覆盖硬件频率造成抖动。该现象直接证明work_in_progress是schedutil稳定性的关键。生产环境绝对禁止移除该逻辑。Q4部分 ARM 嵌入式板卡上schedutil不生效无法动态调频解答首先确认硬件树设备开启 CPUFreq 节点其次检查内核配置是否开启schedutil。部分老旧 ARM 平台硬件不支持动态调频或电源管理驱动缺失schedutil会降级为静态频率。同时确认rate_limit_us限流参数配置合理限流过大会导致调频响应迟钝。Q5kprobe 跟踪不到work_in_progress状态解答1. 内核未开启CONFIG_KPROBES配置重新编译内核开启2. 函数被内核内联优化修改内核编译选项关闭 O2 优化3. 源码路径与实际内核函数路径不匹配根据当前内核版本调整 kprobe 路径。七、实践建议与最佳实践结合内核开发、嵌入式落地、性能调优、线上运维四大场景总结Schedutil work_in_progress的工程最佳实践、调试技巧、优化方案。7.1 调试排错最佳实践分层定位问题调频异常时先区分是调度器负载统计问题、work_in_progress并发拦截问题、还是 CPUFreq 硬件驱动问题。优先使用cpufreq-info确认策略再用kprobe跟踪标志位状态最后查看工作队列执行情况。分步压测排查并发问题时先单线程压测、再多线程压测逐步放大负载定位临界故障点。保留原始逻辑严禁在内核中注释、修改work_in_progress并发判断逻辑该标志是轻量级并发保护的核心替换为自旋锁会增加中断上下文延迟影响实时性。7.2 性能优化建议合理配置限流参数 rate_limit_us该参数控制最小调频间隔工业实时系统可适当减小数值提升响应速度服务器场景建议增大数值减少调频次数降低功耗抖动。该参数定义在sugov_cpu中可通过内核参数或设备树配置。区分 CPU 架构调优x86 服务器 CPU 核心多、调度密集依赖work_in_progress拦截冗余调频ARM 嵌入式 CPU 功耗敏感在保证并发安全的前提下不要过度放大调频频率。实时系统适配对于 Linux 实时补丁PREEMPT-RT系统work_in_progress原子操作无调度延迟完全适配实时上下文无需额外改造。7.3 内核二次开发规范若基于schedutil二次开发自定义调频算法新增逻辑必须放在 **work_in_progress置位之后 **保证新增逻辑同样受并发保护。不要在sugov_update_single中添加耗时逻辑所有耗时计算、硬件操作统一交由延迟工作队列执行遵循内核异步设计思想。标志位复位仅允许在工作队列回调函数中执行禁止在其他路径清零避免状态错乱。7.4 线上运维规范生产环境默认使用原生schedutil不随意替换调频策略监控系统增加CPU 频率波动指标频率短时间大幅跳变时告警排查并发调频冲突高并发业务服务器关闭不必要的调度调试探针避免探针本身加剧调度压力。八、总结与落地应用场景8.1 全文核心要点回顾本文从背景、概念、环境、源码、实操、排错、优化全链路解析了Schedutil中work_in_progress并发控制机制核心要点总结schedutil是调度事件驱动的 CPU 调频策略调度器高频触发调频回调天然存在并发冲突风险work_in_progress是布尔型原子标志位作为轻量级互斥标识实现单 CPU 内调频任务的串行化执行状态流转分为置位抢占、异步执行、复位释放三个阶段全链路保护调频逻辑保证决策原子性该机制不使用重型锁兼顾内核高并发、低延迟的性能要求是 Linux 内核 “轻量化并发设计” 的经典案例结合 kprobe、压测程序、sysfs 工具可完整复现、跟踪、验证该机制适用于调试、论文研究、内核开发。8.2 落地应用场景再梳理工业实时 Linux 系统PLC、运动控制器、工业网关实时任务频繁切换依靠work_in_progress保证调频稳定避免控制业务抖动车载嵌入式 Linux车机、自动驾驶辅助系统混合运行实时任务与普通应用调度负载复杂并发控制保障车规级稳定性云服务器与容器集群大量容器、线程并发运行高频调度产生海量调频请求该机制拦截冗余调频降低系统开销与功耗波动移动终端与物联网设备手机、边缘网关在性能与功耗之间动态平衡轻量级并发保护适配资源受限的硬件内核教学与学术研究作为 Linux 调度与电源管理结合、轻量级并发控制的典型案例用于课程实验、毕业论文、技术报告。8.3 学习延伸建议work_in_progress只是schedutil框架的一小部分读者可基于本文继续深入研究util利用率计算算法、rate_limit_us限流机制、多 CPU 集群调频逻辑、EAS 能效调度架构。将本文所学的 “事件驱动 轻量级并发保护” 设计思想迁移到内核其他子系统的开发与优化中真正做到学以致用。

相关新闻