尧图网络 高端网站定制 · 原创设计
免费咨询热线
400-888-6620
免费获取方案
Linux PM QoS 功耗管理:约束聚合机制与驱动开发实践
1. 功耗管理的隐形裁判PM QoS 到底在管什么做过嵌入式 Linux 或者移动端内核调优的人大概率都遇到过这种场景系统待机功耗死活降不下来查了一圈 CPU idle、clock framework、regulator 都正常最后发现是某个驱动在初始化时提了一个“我不能进低功耗状态”的请求而且这个请求一直没释放。这个“提请求”的机制就是 PM QoSPower Management Quality of Service。PM QoS 的本质是一套约束聚合系统。它不直接控制硬件也不直接决定某个设备进不进低功耗状态它做的事情是把系统里各个角落对功耗和延迟的诉求收集起来按照“最严格者胜出”的原则算出一个最终约束值然后交给真正干活的子系统cpuidle、devfreq、runtime PM 等去执行。你可以把它理解成一个会议室里的“最保守意见汇总器”——十个人里九个人说可以关灯只要有一个人说不能关那灯就得留着。这套框架在 Linux 内核里已经存在很多年了代码主要集中在drivers/base/power/qos.c和include/linux/pm_qos.h另外在kernel/power/qos.c里还有一套面向系统级 CPU 延迟的接口。很多人第一次看这块代码会觉得“怎么有两套”这正是梳理 PM QoS 时第一个要搞清楚的分叉点。这篇文章适合三类人看一是正在做嵌入式 Linux 功耗优化的工程师二是需要给驱动加 QoS 约束但不确定该用哪个接口的内核开发者三是准备内核相关面试、想把功耗子系统串起来的同学。我会把框架结构、两套接口的区别、约束聚合的计算逻辑、实际驱动里的用法以及我自己踩过的坑尽量讲透。2. 框架整体设计与两套接口的分工2.1 为什么会有两套 QoS 接口刚接触这块的人最容易懵的地方就是发现内核里居然有两个地方都在实现 QoS一个是kernel/power/qos.c一个是drivers/base/power/qos.c。它们不是重复造轮子而是面向不同层级的需求。kernel/power/qos.c这套出现得更早主要管的是系统级 CPU 延迟约束和系统级功耗约束。它提供的是全局的、粗粒度的接口典型代表就是pm_qos_add_request()配合PM_QOS_CPU_DMA_LATENCY这个参数。它的特点是约束是全局生效的一旦有人提了 CPU DMA 延迟不能超过某个值整个系统的 cpuidle 都会受影响。drivers/base/power/qos.c这套是后来加的面向的是设备级的细粒度约束。它挂在 device 结构上每个设备可以有自己的 QoS 约束比如“我这个设备要求延迟不超过 100us”“我这个设备要求带宽至少 50MB/s”。这套接口更现代也是现在驱动开发里更推荐用的。我个人的判断标准很简单如果你要约束的是整个 SoC 的 CPU 进入深度 idle 的延迟用kernel/power/qos.c如果你要约束的是某个具体设备比如某个 DMA 控制器、某个显示控制器的性能下限用drivers/base/power/qos.c。搞混了会导致约束范围过大白白浪费功耗预算。2.2 约束聚合的核心逻辑PM QoS 最核心的一句话就是多个请求聚合时取最严格的值。但“最严格”在不同类型的约束里方向是不一样的这是很多人写代码时容易搞反的地方。对于延迟类约束latency数值越小越严格。比如 A 请求延迟不超过 100usB 请求延迟不超过 50us聚合结果就是 50us。因为要同时满足两者只能按更小的来。对于吞吐/带宽类约束throughput数值越大越严格。比如 A 要求带宽至少 10MB/sB 要求至少 30MB/s聚合结果就是 30MB/s。对于标志位类约束flags比如PM_QOS_FLAG_NO_POWER_OFF只要有一个请求置位聚合结果就是置位。这是布尔或运算。内核里用了一个叫plistpriority list的结构来维护这些请求按优先级排序取头部就是最严格的值。用 plist 而不是普通链表是因为聚合操作非常频繁每次请求增删都要重算plist 能保证 O(1) 拿到最严格值不用每次遍历。注意plist 的“优先级”和你调用接口时传的value不是一回事。plist 的优先级是内核内部用来排序的请求值本身才是约束值。别把这两个概念混了。2.3 请求的生命周期一个 QoS 请求从生到死大致经历这几个阶段添加请求调用pm_qos_add_request()或设备级的dev_pm_qos_add_request()此时请求被插入 plist聚合值可能立即变化。更新请求调用pm_qos_update_request()修改约束值内核会重新计算聚合值如果变了就通知监听者。移除请求调用pm_qos_remove_request()从 plist 摘除聚合值可能放松。这里有个关键点请求是引用计数式的资源必须成对添加和移除。我见过太多驱动在 probe 里加了请求remove 里忘了删结果模块卸载后约束还挂在那里系统功耗一直降不下来。这种 bug 特别隐蔽因为功能上完全正常只有测功耗才能发现。3. 核心数据结构与关键代码解析3.1 plist 与聚合值的维护先看kernel/power/qos.c里的核心结构。每个 QoS 类别比如 CPU_DMA_LATENCY对应一个pm_qos_object里面挂着一个 plist 头和一个target_value。每次请求变动都会调用update_target()重新算聚合值。/* 简化示意非完整源码 */ static int update_target(struct pm_qos_object *o, int value) { unsigned long flags; int prev, curr; spin_lock_irqsave(o-lock, flags); prev o-target_value; o-target_value value; curr o-target_value; spin_unlock_irqrestore(o-lock, flags); if (prev ! curr) blocking_notifier_call_chain(o-notifiers, curr, NULL); return 0; }这段逻辑看着简单但有个细节值得说通知是在锁外发的。因为 notifier 回调里可能会做耗时操作甚至可能反过来调用 QoS 接口如果在锁内发通知轻则死锁重则系统挂死。这个设计模式在内核里很常见写驱动时如果自己实现类似的回调机制也要注意这一点。3.2 设备级 QoS 的约束类型drivers/base/power/qos.c支持的约束类型更丰富主要有这么几类约束类型含义聚合方向典型用途RESUME_LATENCY恢复延迟上限取最小设备从 suspend 恢复的最大允许延迟LATENCY_TOLERANCE延迟容忍度取最小设备能接受的最大延迟NO_POWER_OFF禁止断电标志逻辑或标记设备不能被 runtime suspendFLAG_REMOTE_WAKEUP远程唤醒标志逻辑或标记设备可远程唤醒设备级 QoS 的请求结构是dev_pm_qos_request它比系统级的更复杂因为要处理设备生命周期。比如设备被移除时挂在它上面的 QoS 请求要自动清理否则就是悬空指针。3.3 约束值的单位与换算延迟类约束的单位是微秒us这个一定要记牢。我见过有人按毫秒传值结果约束松了 1000 倍功耗优化完全没效果查了两天才发现是单位搞错。带宽类约束的单位通常是KB/s 或 MB/s具体看接口定义。设备级的dev_pm_qos_add_request()里DEV_PM_QOS_BANDWIDTH用的是 KB/s。传值前一定要翻一下头文件确认别凭感觉。实操心得在驱动里定义 QoS 约束值时建议用宏或者常量并在旁边注释单位。比如#define MY_DEV_MAX_LATENCY_US 100比直接写100强太多后面维护的人包括三个月后的你自己会感谢你。4. 驱动里怎么用从添加请求到实际生效4.1 系统级 CPU 延迟约束的完整用法假设你有一个驱动它做 DMA 传输时要求 CPU 不能进太深的 idle否则 DMA 完成中断响应会超时。这时候可以用系统级的 CPU DMA 延迟约束。#include linux/pm_qos.h static struct pm_qos_request my_dma_qos; static int my_dma_start(void) { /* 要求 CPU DMA 延迟不超过 50us */ pm_qos_add_request(my_dma_qos, PM_QOS_CPU_DMA_LATENCY, 50); /* 启动 DMA 传输 */ start_dma_transfer(); return 0; } static void my_dma_done(void) { /* 传输完成释放约束 */ pm_qos_remove_request(my_dma_qos); }这段代码看着简单但有几个坑第一pm_qos_add_request()如果对同一个 request 结构调用两次会触发警告甚至崩溃。所以要么保证只加一次要么用pm_qos_update_request()更新。第二约束值传PM_QOS_DEFAULT_VALUE表示“无约束”传具体值才是约束。别把默认值当成“0 延迟”那是两码事。第三这个约束是全局的会影响整个系统的 cpuidle。如果你的驱动只是偶尔传一次 DMA用完立刻释放没问题但如果长期持有系统功耗会明显上升。我实测过一个场景一个驱动忘了释放 50us 的约束待机功耗从 8mA 涨到了 25mA非常夸张。4.2 设备级 QoS 的用法设备级 QoS 更适合“我这个设备本身有性能要求”的场景。比如一个显示控制器要求内存带宽不低于某个值。#include linux/pm_qos.h static struct dev_pm_qos_request disp_bw_qos; static int disp_probe(struct platform_device *pdev) { int ret; ret dev_pm_qos_add_request(pdev-dev, disp_bw_qos, DEV_PM_QOS_BANDWIDTH, 50000); if (ret 0) return ret; /* 后续显示控制器初始化 */ return 0; } static int disp_remove(struct platform_device *pdev) { dev_pm_qos_remove_request(disp_bw_qos); return 0; }设备级 QoS 的好处是约束跟着设备走设备销毁时框架会自动清理前提是你用了正确的 remove 流程。但要注意dev_pm_qos_add_request()在设备还没完全初始化时调用可能失败最好放在 probe 的后半段。4.3 约束生效的时机与验证添加了 QoS 请求不代表立刻生效中间还隔着“聚合值变化 → 通知监听者 → 监听者调整策略”这条链路。比如 CPU DMA 延迟约束变了cpuidle governor 要收到通知才会重新评估 idle 状态。验证约束是否生效最直接的方法是看 sysfs# 查看系统级 CPU DMA 延迟约束 cat /sys/devices/system/cpu/cpu0/power/pm_qos_resume_latency_us # 查看设备级约束以某个平台设备为例 cat /sys/devices/platform/my_device/power/pm_qos_resume_latency_us如果读出来的值和你设置的不一致先检查是不是有别的请求在“压着”——因为聚合取最严格值你设 100us但别人设了 50us你读出来就是 50us。常见误区有人以为pm_qos_add_request()返回成功就万事大吉其实返回值只代表请求插入成功不代表约束已经作用到硬件。真正的生效要等 notifier 链走完。调试功耗问题时一定要在 notifier 回调里打点确认回调被触发了。5. 常见问题与排查技巧实录5.1 功耗降不下来怎么定位是不是 QoS 的锅这是最高频的问题。我的排查顺序是这样的第一步先看系统级约束。读/sys/devices/system/cpu/cpu0/power/pm_qos_resume_latency_us如果值很小比如 0 或个位数说明有请求在压着CPU 进不了深度 idle。第二步用pm_qos相关的 debugfs 节点看请求列表。如果内核开了CONFIG_PM_DEBUG/sys/kernel/debug/pm_qos/下面会有各类约束的请求详情能看到是谁提的请求、值是多少。第三步如果 debugfs 没有就在update_target()里加 printk把调用栈打出来。这个方法粗暴但有效能直接定位到是哪个驱动在提请求。第四步确认请求有没有配对释放。我遇到过一个案例驱动在 suspend 回调里加了请求resume 回调里忘了删结果每次 suspend/resume 循环都会多一个请求跑几十次后约束值被压到极低功耗爆炸。5.2 约束值不生效的几种典型原因现象可能原因排查方法设了约束但聚合值没变有更严格的请求存在查 debugfs 请求列表约束值读出来是默认值请求没插入成功检查 add 返回值约束生效但功耗没降约束没传到 cpuidle查 notifier 回调是否触发模块卸载后约束还在remove 没调用检查 remove 路径约束值方向搞反延迟类传了大值确认约束类型和单位5.3 几个我踩过的坑坑一在原子上下文里调用 QoS 接口。pm_qos_add_request()内部会拿 spinlock如果你在中断上下文里调用且中断上下文本身已经持有相关锁就可能死锁。我见过在 timer 回调里加 QoS 请求导致系统卡死的案例。正确做法是把 QoS 操作放到工作队列或者线程上下文里。坑二把设备级和系统级接口混用。有人用dev_pm_qos_add_request()去约束 CPU 延迟结果发现 cpuidle 根本不理会。因为设备级约束是给设备自己的电源管理用的不会直接影响 CPU idle。要影响 CPU idle必须用系统级的PM_QOS_CPU_DMA_LATENCY。坑三忽略约束的传播延迟。QoS 约束变化到实际硬件策略调整中间有 notifier 链的传播时间。在快速 suspend/resume 的场景里如果约束刚加上就立刻 suspend可能 notifier 还没走完约束没生效。这种时序问题很难查建议在关键路径上加时间戳打点。坑四约束值设得太激进。有人为了省电把延迟约束设成 0结果 CPU 完全不能进 idle功耗反而更高。约束要基于实际需求设不是越严格越好。我一般会先测出设备能接受的最大延迟然后留 20% 余量。5.4 调试工具与手段除了 debugfs还有几个手段值得用ftrace打开pm_qos相关的 trace event能看到约束变化的完整时间线。perf配合 cpuidle 的 tracepoint看约束变化和 idle 状态切换的关联。自定义 notifier在自己的驱动里注册一个 QoS notifier把每次约束变化都打出来适合深度调试。实操心得调试功耗问题时我习惯先关掉所有非必要的驱动确认基线功耗然后逐个打开驱动观察功耗变化。如果某个驱动一打开功耗就涨再去查它的 QoS 请求。这个“二分法”比一上来就啃 QoS 代码高效得多。6. 约束聚合的边界情况与内核实现细节6.1 请求值为默认值时的处理PM_QOS_DEFAULT_VALUE是一个特殊值表示“无约束”。当所有请求都是默认值时聚合值就是默认值监听者收到默认值后会把策略恢复到最宽松状态。这里有个细节默认值不是 0。对于延迟类约束默认值通常是一个很大的数比如LATENCY_MAX表示“延迟随便多大都行”。如果你把默认值当成 0 来理解就会得出完全相反的结论。6.2 并发添加请求的竞态多个 CPU 同时调用pm_qos_add_request()时plist 的插入操作需要加锁保护。内核用的是spin_lock_irqsave保证在中断上下文里也安全。但锁只保护 plist 本身不保护“聚合值计算 通知”这个整体过程。所以理论上存在这样的时序A 请求插入聚合值算出 X还没通知B 请求插入聚合值算出 Y然后两个通知都发出去监听者收到 X 和 Y 两个值。内核的处理方式是通知里带的是“当前聚合值”监听者拿到后自己判断是否需要处理。即使收到重复或过期的值监听者做幂等处理也不会有问题。这个设计思路值得借鉴——不要把状态一致性寄托在通知顺序上让接收方做幂等。6.3 设备销毁时的约束清理设备级 QoS 请求挂在 device 结构上设备销毁时会调用dev_pm_qos_remove_request()清理。但如果驱动在 remove 之前就崩了或者设备是被强制热插拔移除的清理逻辑可能走不到。内核的兜底机制是在device_del()里遍历设备的 QoS 请求列表强制清理。但这个兜底只对通过dev_pm_qos_add_request()添加的请求有效如果你自己维护了一个全局的 QoS 请求结构就得自己保证清理。注意我强烈建议设备级约束一律用dev_pm_qos_*接口不要自己造轮子。设备生命周期管理比你想的复杂框架帮你处理的边界情况远比你手动处理的多。7. 从框架设计看功耗优化的方法论梳理完 PM QoS 的框架我最大的体会是功耗优化不是“把能关的都关掉”而是“在满足约束的前提下尽量关”。QoS 框架的存在就是为了让这个“满足约束”变得可量化、可聚合、可传播。很多新手做功耗优化上来就改 cpuidle governor 的参数把 idle 门槛调低结果系统是省电了但音频卡顿、显示掉帧。正确的做法是先梳理系统里有哪些 QoS 约束理解每个约束的来源和必要性然后针对性地放松不必要的约束而不是一刀切地改策略。从框架设计角度PM QoS 有几个值得学习的地方一是用 plist 做聚合把 O(n) 的遍历变成 O(1) 的取头二是通知在锁外发避免死锁三是让接收方做幂等不依赖通知顺序。这些模式在内核其他子系统里也能看到理解了 QoS 再看别的框架会轻松很多。最后分享一个我常用的技巧在系统稳定运行后把所有 QoS 请求的当前值 dump 出来做成一张表标注每个请求的来源和必要性。这张表就是功耗优化的“地图”哪里能放松、哪里必须保留一目了然。我做过的一个项目靠这张表发现了三个冗余约束放松后待机功耗降了 30% 多比调 governor 参数有效得多。
RELATED

相关推荐

模型服务规模化:调度、KV Cache 与资源池化的系统之道

模型服务规模化:调度、KV Cache 与资源池化的系统之道

SOSP 的 Session 1A 开场就是 Model Serving at Scale,这个安排本身就很能说明问题。这几年我和团队一直在做 LLM 推理服务化,眼看着这个方向从"AI 实验室里的小工具"变成了"真正意义上的系统软件"——调度、缓存、资源池化、故障恢…

📅 2026/10/9 0:57:08
AI日报制作全攻略:从信息筛选到判断力训练的实操指南

AI日报制作全攻略:从信息筛选到判断力训练的实操指南

1. 一份“AI 日报”到底在记录什么每天早上打开电脑,我做的第一件事不是看邮件,而是花二十分钟把过去二十四小时里跟人工智能相关的动态过一遍。这个习惯坚持了快三年,从最开始只是随手记在备忘录里,到后来形成固定格式的日报&…

📅 2026/10/9 0:57:08
CodeX 开启 Image Gen 生图功能:config.toml 里 model_providers.custom 怎么配到 TaoToken

CodeX 开启 Image Gen 生图功能:config.toml 里 model_providers.custom 怎么配到 TaoToken

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

📅 2026/10/9 0:57:08
MORE NEWS

更多资讯

📰

Gemini对话、AI历史搜索、DevTools AI一网打尽:enable-chrome-ai解锁的3大Chrome隐藏功能实测

Gemini对话、AI历史搜索、DevTools AI一网打尽:enable-chrome-ai解锁的3大Chrome隐藏功能实测 【免费下载链接】enable-chrome-ai Enable Gemini in Chrome, AI Powered History search, DevTools Al Innovations in Google Chrome without cleaning data and reins…

📰

微信 openclaw 插件接入 TaoToken 统一 Key:Python CLI 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

📰

串口为何在IIoT底层长盛不衰:从RS485偏置电阻到Linux丢数据实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

📰

导师说选题太宽泛、创新点不足怎么办?academic-ai-prompt的6个应急修改Prompt清单

导师说选题太宽泛、创新点不足怎么办?academic-ai-prompt的6个应急修改Prompt清单 【免费下载链接】academic-ai-prompt 一套为研究生和学术研究者设计的完整AI Prompt库 📖 包含内容: ✨ 40 精心设计的AI Prompt ✨ 论文选题系统方法&#x…

📰

Suricata IP Reputation(IP 信誉)机制完全指南:配置、数据格式与 iprep 规则实战

网络安全 【免费下载链接】suricata Suricata is a network Intrusion Detection System, Intrusion Prevention System and Network Security Monitoring engine developed by the OISF and the Suricata community. 项目地址: https://gitcode.com/gh_mirrors/su/…

📰

universal-modder 中 Unreal 4/5 游戏的 Mod 实战:引擎识别、UE4SS 钩子、Pak 替换与调校路线

【免费下载链接】universal-modder Point Claude at any game. Skills, tools and the fal MCP that let Claude Code mod almost any PC game you own: recon, reverse engineering, fal-generated art/3D/audio, in-game testing, showcase videos. 项目地址: htt…

TODAY

今日更新

THIS WEEK

本周精选

THIS MONTH

本月热门

读完文章,想聊聊您的网站?

告诉我们您的行业与需求,资深顾问一对一梳理方案与报价,全程免费。

📞 💬