15 · 电源管理
本篇定位:嵌入式和移动设备,省电是硬指标。FreeRTOS 用 tickless idle + WFI;Linux 有完整电源管理框架:cpuidle(空闲状态)、cpufreq(频率调节)、Runtime PM(运行时按需开关设备)、系统级 suspend/hibernate、wakeup source(唤醒源)。本篇讲清这套,读完能给设备配省电策略、能调"为什么设备不休眠"。
Linux 电源管理是tickless/WFI 的系统化扩展
FreeRTOS 省电:tickless idle(进 WFI,停 tick)。Linux 省电复杂得多:①cpuidle(多级空闲状态,浅睡/深睡);②cpufreq(动态调频,按负载);③Runtime PM(设备不用就关);④suspend(整机睡眠);⑤wakeup source(什么能唤醒)。增量是"多级 + 设备级 + 系统级"。WFI 是 cpuidle 最浅一级。
目录
- 15 · 电源管理
- 一、电源管理的层次
- 二、cpuidle:CPU 空闲状态
- 2.1 为什么多级空闲
- 2.2 idle state
- 2.3 RISC-V idle
- 2.4 看 cpuidle
- 2.5 对照 FreeRTOS tickless
- 三、cpufreq:动态调频(DVFS)
- 3.1 DVFS 是什么
- 3.2 cpufreq 框架
- 3.3 governor(策略)
- 3.4 看与配
- 3.5 OPP(Operating Performance Points)
- 四、Runtime PM:设备运行时电源管理⭐
- 4.1 为什么 Runtime PM
- 4.2 Runtime PM API
- 4.3 runtime PM callback
- 4.4 引用计数
- 4.5 autosuspend(延迟挂起)
- 4.6 看 Runtime PM
- 五、系统级 Suspend(睡眠)
- 5.1 suspend 状态
- 5.2 suspend 流程
- 5.3 沿设备树级联(10 篇)
- 5.4 PM QoS(电源管理服务质量)
- 5.5 触发 suspend
- 六、wakeup source(唤醒源)
- 6.1 什么是唤醒源
- 6.2 注册唤醒源
- 6.3 看唤醒源
- 6.4 唤醒原因排查
- 七、clock 和 regulator 框架
- 7.1 clock framework
- 7.2 regulator framework
- 7.3 与 PM 关系
- 八、PM ops 完整结构
- 九、嵌入式省电策略
- 9.1 常见策略
- 9.2 嵌入式典型场景
- 9.3 调试省电
- 十、对照总表
- 十一、本篇小结
- 速查表
一、电源管理的层次
系统级: suspend(sleep) / hibernate(磁盘) / shutdown │ 子系统级: Runtime PM(设备按需开关) │ CPU 级: cpufreq(调频) + cpuidle(空闲状态) │ 硬件: DVFS(电压频率) / WFI / clock gating / power gating| 层次 | 作用 | 触发 |
|---|---|---|
| 系统级 | 整机睡眠/休眠 | 用户(电源键)/超时 |
| 子系统级 | 设备按需开关 | 设备不忙时自动 |
| CPU 级 | 调频/空闲 | 负载/空闲 |
二、cpuidle:CPU 空闲状态
2.1 为什么多级空闲
- CPU 空闲可进不同睡眠深度
- 浅睡(WFI):唤醒快,省电少
- 深睡(power gating):唤醒慢,省电多
- cpuidle 框架:按预计空闲时间选最优状态
2.2 idle state
structcpuidle_state{charname[CPUIDLE_NAME_LEN];unsignedintflags;int(*enter)(structcpuidle_device*dev,structcpuidle_driver*drv,intindex);intexit_latency;// 退出延迟(μs,唤醒要多久)inttarget_residency;// 目标驻留(μs,至少睡这么久才划算)intpower_usage;// 功耗(mW)...};- 每个 state 有退出延迟 + 目标驻留
- idle 时间短选浅睡(快醒),长选深睡(省电)
2.3 RISC-V idle
- 最浅:WFI(Wait For Interrupt),指令进低功耗
- 更深:SoC 特定(关 PLL/关域)
- cpuidle driver 注册多级
2.4 看 cpuidle
cat/sys/devices/system/cpu/cpu0/cpuidle/state*/name# WFI# RETENTION# POWER_OFFcat/sys/devices/system/cpu/cpu0/cpuidle/state*/disable# 0 (0=启用,1=禁用某状态)2.5 对照 FreeRTOS tickless
| FreeRTOS tickless | Linux cpuidle | |
|---|---|---|
| 空闲进 | WFI(常一级) | 多级 idle state |
| 选哪级 | 单级 | 按预计空闲时间 |
| 唤醒 | 中断 | 中断 |
| tick | 停 | 停(NOHZ,08 篇) |
tickless WFI 是 cpuidle 最浅级
FreeRTOS tickless 进 WFI——Linux cpuidle 的最浅 state(WFI)。Linux 多了深睡状态(关域/关 PLL),按空闲时长选。增量是多级 + 智能选择。WFI 经验直接迁移,新增"深睡的延迟/驻留权衡"。
三、cpufreq:动态调频(DVFS)
3.1 DVFS 是什么
- Dynamic Voltage and Frequency Scaling
- 负载低降频降压(省电),负载高升频升压(性能)
- CPU + 调频驱动 + governor(策略)
3.2 cpufreq 框架
structcpufreq_driver{int(*init)(structcpufreq_policy*policy);int(*setpolicy)(...);// 设策略int(*target_index)(...);// 设频率...};structcpufreq_governor{int(*throttle)(...);unsignedint(*get)(...);...};- driver:硬件操作(SoC 调频接口)
- governor:策略(什么时候调多少)
3.3 governor(策略)
| governor | 策略 | 用途 |
|---|---|---|
| performance | 一直最高频 | 性能优先 |
| powersave | 一直最低频 | 省电优先 |
| userspace | 用户态指定 | 手动 |
| ondemand | 按负载升频 | 通用(默认) |
| schedutil | 据调度器信息调 | 现代(配合 EAS) |
| conservative | 渐进调频 | 平衡 |
3.4 看与配
cat/sys/devices/system/cpu/cpu0/cpufreq/scaling_available_governors# ondemand performance powersave ...cat/sys/devices/system/cpu/cpu0/cpufreq/scaling_cur_freq# 1200000 (1.2 GHz)cat/sys/devices/system/cpu/cpu0/cpufreq/scaling_available_frequencies# 400000 800000 1200000 1600000# 改 governorechoperformance>/sys/devices/system/cpu/cpu0/cpufreq/scaling_governor# 设固定频率echo800000>/sys/devices/system/cpu/cpu0/cpufreq/scaling_setspeed3.5 OPP(Operating Performance Points)
- SoC 定义的"频率+电压"组合
- DTS 描述:
operating-points-v2 - 调频驱动据 OPP 切换
cpu0_opp_table: opp-table { compatible = "oper-points-v2"; opp-400M { opp-hz = <400000000>; opp-microvolt = <900000>; }; opp-1G { opp-hz = <1000000000>; opp-microvolt = <1100000>; }; };四、Runtime PM:设备运行时电源管理⭐
4.1 为什么 Runtime PM
- 设备不忙时关它(省电),要用时开
- 不等整机 suspend,运行时动态开关
- 例:USB 设备空闲自动挂起
4.2 Runtime PM API
// 设备结构(继承 device,10 篇)structdevice{...structdev_pm_infopower;// PM 状态...};// 驱动用:pm_runtime_get_sync(dev);// 标记"要用",+1 计数,若 0→1 调 resumepm_runtime_put_sync(dev);// 标记"用完",-1,若 1→0 调 suspend// 或自动:pm_runtime_enable(dev);pm_runtime_set_autosuspend_delay(dev,2000);// 空闲 2s 后自动挂起pm_runtime_use_autosuspend(dev);pm_runtime_mark_last_busy(dev);4.3 runtime PM callback
structdev_pm_ops{int(*runtime_suspend)(structdevice*dev);// 挂起int(*runtime_resume)(structdevice*dev);// 恢复int(*runtime_idle)(structdevice*dev);// 空闲(可决定挂起)...};// 驱动注册staticconststructdev_pm_opsmy_pm={SET_RUNTIME_PM_OPS(my_runtime_suspend,my_runtime_resume,NULL)};4.4 引用计数
pm_runtime_get:计数 +1(要用)pm_runtime_put:计数 -1(用完)- 计数 > 0:设备活跃
- 计数 = 0:可挂起
- 计数 0→1:调 resume;1→0:调 suspend
4.5 autosuspend(延迟挂起)
- 不是用完立刻挂起(频繁开关损耗)
- 空闲 N 秒后才挂起
pm_runtime_mark_last_busy+ autosuspend_delay
Runtime PM 是设备级省电,你裸机手动开关外设
裸机省电:不用 UART 时手动关时钟。Linux Runtime PM框架化:驱动实现 suspend/resume 回调,框架管引用计数,自动开关。写驱动加 dev_pm_ops,框架管时机。
4.6 看 Runtime PM
cat/sys/devices/.../power/control# on (on=禁止自动挂起,auto=允许)cat/sys/devices/.../power/runtime_status# active / suspended / suspending / resumingechoauto>/sys/devices/.../power/control# 允许自动echoon>/sys/devices/.../power/control# 禁止(常开)五、系统级 Suspend(睡眠)
5.1 suspend 状态
| 状态 | 名字 | 特点 |
|---|---|---|
| freeze | 冻结用户态 | 最浅,只停进程 |
| standby | 待机 | 系统进低功耗,内存保持 |
| mem | 挂起到内存(S3) | 内存自刷新,CPU/多数外设关 |
| disk | 挂起到磁盘(休眠,S4) | 内存存盘后整机关 |
5.2 suspend 流程
echo mem > /sys/power/state → 内核 suspend 流程: 1. 冻结用户进程(信号停止) 2. 通知所有设备 suspend(dev_pm_ops.suspend) 3. 关中断 4. 关 CPU(进睡眠) ... 等唤醒 ... 唤醒: 5. 唤醒源触发 6. 恢复 CPU 7. 开中断 8. resume 设备(逆序) 9. 解冻进程5.3 沿设备树级联(10 篇)
- suspend 按设备树深度优先逆序:先子后父
- resume 按设备树顺序:先父后子
- 父设备(总线)先 resume,子设备才能用
5.4 PM QoS(电源管理服务质量)
- 约束"不能睡太深"或"必须快醒"
- 例:音频要低延迟,不能进深睡
pm_qos_add_request加约束
5.5 触发 suspend
echomem>/sys/power/state# 挂起到内存echodisk>/sys/power/state# 休眠echofreeze>/sys/power/state# 仅冻结# 看支持的状态cat/sys/power/state# freeze mem disk六、wakeup source(唤醒源)
6.1 什么是唤醒源
- 系统睡眠时,只有特定源能唤醒(中断)
- wakeup source 标记"我能唤醒系统"
- 例:电源键、网络(Wake-on-LAN)、RTC 闹钟、GPIO
6.2 注册唤醒源
// 设备启用唤醒能力device_set_wakeup_capable(dev,true);device_wakeup_enable(dev);// 中断唤醒(IRQF 之外)enable_irq_wake(irq);// 让该中断能唤醒disable_irq_wake(irq);6.3 看唤醒源
cat/proc/acpi/wakeup# x86cat/sys/kernel/debug/wakeup_sources# 通用# 看哪些设备能唤醒fordin/sys/devices/*/*/power/wakeup;doecho"$d:$(cat$d)"done6.4 唤醒原因排查
# 看上次唤醒原因cat/sys/kernel/debug/wakeup_sources# active_count / event_count / wakeup_count / expire_count# last_time / max_time / total_time嵌入式唤醒调试是难点
嵌入式产品:“睡眠了但被意外唤醒"或"睡眠后唤醒不来”。排查:①/sys/kernel/debug/wakeup_sources看哪个源触发;② 检查设备 wakeup_enabled;③ enable_irq_wake 是否对。这是嵌入式 BSP 省电调试的核心。
七、clock 和 regulator 框架
7.1 clock framework
- SoC 时钟树复杂(PLL/分频/gating)
- clock framework 统一管理
- 驱动
clk_get/clk_prepare_enable/clk_disable_unprepare
structclk*clk=clk_get(dev,NULL);clk_prepare_enable(clk);// 开时钟// 用设备clk_disable_unprepare(clk);7.2 regulator framework
- 电源调节器(LDO/DCDC)
- 驱动
regulator_get/regulator_enable/regulator_set_voltage
structregulator*r=devm_regulator_get(dev,"vdd");regulator_enable(r);regulator_set_voltage(r,1100000,1100000);7.3 与 PM 关系
- cpufreq 调频要同步调压(调 regulator)
- suspend 关时钟(clock gating)
- DVFS = clock(频率)+ regulator(电压)联动
八、PM ops 完整结构
structdev_pm_ops{/* 系统 suspend/resume */int(*suspend)(...);int(*resume)(...);int(*freeze)(...);int(*thaw)(...);int(*poweroff)(...);int(*restore)(...);/* Runtime PM */int(*runtime_suspend)(...);int(*runtime_resume)(...);int(*runtime_idle)(...);/* ... 完整版更多 */};// 驱动用宏简化staticconststructdev_pm_opsmy_pm={SET_SYSTEM_SLEEP_PM_OPS(my_suspend,my_resume)// 系统 suspendSET_RUNTIME_PM_OPS(my_rt_suspend,my_rt_resume,NULL)// Runtime PM};- 系统 suspend 和 Runtime PM 是两套
- 驱动可只实现一套或都实现
九、嵌入式省电策略
9.1 常见策略
| 策略 | 工具 |
|---|---|
| CPU 调频 | cpufreq ondemand/schedutil |
| CPU 空闲 | cpuidle 多级 |
| 设备按需开关 | Runtime PM |
| 整机睡眠 | suspend mem(无任务时) |
| 关外设时钟 | clock gating |
| 关未用域 | power gating(SoC 级) |
| 唤醒源 | 只留必要(电源键/RTC/网络) |
9.2 嵌入式典型场景
- 电池产品(手持):cpufreq ondemand + cpuidle 深睡 + Runtime PM + 空闲 suspend
- 常电产品( plugged):cpufreq performance + 禁深睡(低延迟优先)
- 边缘 AI:高性能时全频,空闲降频
9.3 调试省电
# 看功耗(若有功率计)# 看 CPU 频率/状态cat/sys/devices/system/cpu/cpu*/cpufreq/scaling_cur_freqcat/sys/devices/system/cpu/cpu*/cpuidle/state*/usage# 看设备 PM 状态cat/sys/devices/.../power/runtime_status# 看唤醒源cat/sys/kernel/debug/wakeup_sources# powertop(省电分析工具)powertop嵌入式视角:powertop 是省电调优利器
Ubuntu 有 powertop,扫系统给出"哪些设备可省电"“哪些 wakeup 频繁”。嵌入式虽不一定有 powertop,但思路通用:① 找异常活跃设备;② 禁不必要唤醒;③ 配 Runtime PM。你 AEGIX 省电产品可参考。
十、对照总表
| 概念 | FreeRTOS/裸机 | Linux PM |
|---|---|---|
| CPU 空闲 | WFI | cpuidle 多级 |
| 调频 | 手动 | cpufreq + governor |
| 设备开关 | 手动 | Runtime PM |
| 整机睡眠 | 无(或自己写) | suspend(mem/disk) |
| 唤醒源 | 中断 | wakeup source |
| 时钟管理 | 手动配 | clock framework |
| 电压管理 | 手动 | regulator framework |
| 省电策略 | tickless | 多层组合 |
| 调试 | 电流计 | powertop/wakeup_sources |
十一、本篇小结
- cpuidle:CPU 空闲多级状态(WFI 浅/深睡),按预计空闲时间选,exit_latency/target_residency 权衡
- cpufreq:DVFS 动态调频,governor(performance/ondemand/schedutil),DTS OPP 描述频率电压
- Runtime PM:设备运行时按需开关,引用计数(pm_runtime_get/put),autosuspend 延迟挂起,dev_pm_ops runtime_suspend/resume
- 系统 suspend:freeze/standby/mem/disk,沿设备树级联(子先 suspend,父先 resume)
- wakeup source:标记能唤醒系统的源,enable_irq_wake,排查
/sys/kernel/debug/wakeup_sources - clock framework:统一时钟管理(clk_prepare_enable);regulator framework:电源调压
- PM ops:系统 suspend 和 Runtime PM 两套,SET_SYSTEM_SLEEP_PM_OPS / SET_RUNTIME_PM_OPS
- 嵌入式省电:cpufreq ondemand + cpuidle 深睡 + Runtime PM + 空闲 suspend + 只留必要唤醒源
- FreeRTOS tickless WFI = cpuidle 最浅级,增量是多级 + 设备级 + 系统级
速查表
| 想干啥 | 用什么 |
|---|---|
| 看 CPU 频率 | cat /sys/…/cpu/cpu0/cpufreq/scaling_cur_freq |
| 改 governor | echo ondemand > /sys/…/scaling_governor |
| 设固定频率 | echo Hz > /sys/…/scaling_setspeed |
| 看 idle 状态 | cat /sys/…/cpu0/cpuidle/state*/name |
| 禁某 idle 状态 | echo 1 > /sys/…/cpu0/cpuidle/state3/disable |
| 看设备 PM 状态 | cat /sys/devices/…/power/runtime_status |
| 禁设备自动挂起 | echo on > /sys/devices/…/power/control |
| 系统挂起 | echo mem > /sys/power/state |
| 看支持睡眠状态 | cat /sys/power/state |
| 看唤醒源 | cat /sys/kernel/debug/wakeup_sources |
| 让中断唤醒 | enable_irq_wake(irq) |
| 开时钟 | clk_prepare_enable(clk) |
| 设电压 | regulator_set_voltage |
| 省电分析 | powertop |
| Runtime PM 引用 | pm_runtime_get_sync / pm_runtime_put_sync |
💡技术之路漫漫,分享是为了更好地交流。如果本文的内容对你有启发,希望能得到你的点赞 👍和收藏 ⭐。
如果你在调试过程中遇到了其他问题,欢迎在评论区 💬留言,我们一起探讨。也欢迎关注 👀我,一起交流底层开发的那些事儿。