第2章:Linux电源管理框架全景

各位同学,欢迎来到第二章。上一章我们聊了为什么要做电源管理,这一章咱们直接切入正题——看看Linux电源管理这个庞然大物到底长什么样。

我第一次接触Linux电源管理时,说实话有点懵。代码散落在内核各个角落,有叫PM的,有叫Runtime的,还有CPU Freq、CPU Idle……感觉像是一堆独立的东西。后来我才明白,它们其实是一个有机的整体。

2.1 电源管理子系统架构概览

Linux电源管理,说白了就是一套“省电方法论”的内核实现。它不是一个单一的模块,而是一组相互协作的子系统。

我习惯把整个架构分成三层:

  • 用户空间层:sysfs、procfs接口,以及各种策略决策者(比如调频governor)
  • 内核核心层:PM Core、Runtime PM框架、Suspend/Resume流程
  • 硬件抽象层:CPU Freq驱动、CPU Idle驱动、Devfreq驱动、设备驱动中的PM回调

你想想看,用户空间说“我要省电”,这个指令怎么传到硬件?就是通过这三层一层层传递下去的。

核心观点:电源管理不是某个驱动的事,而是整个系统的事。从应用到底层硬件,每个环节都要参与。

2.2 PM Core——电源管理的中枢神经

PM Core是整个电源管理框架的“大脑”。它定义了一套标准接口,让所有设备驱动都能以统一的方式参与电源管理。

PM Core主要干这几件事:

  • 管理设备的电源状态(active、suspended、runtime suspended等)
  • 维护suspend/resume的调用顺序(先挂起谁、后唤醒谁)
  • 提供通用的PM回调函数模板

我记得有一次调试一个USB设备的挂起问题,折腾了两天没找到原因。后来发现是PM Core里设备依赖关系没配好,导致父设备先睡了,子设备还在干活——这不乱套了吗?

个人经验:调试PM相关问题时,先检查设备树里power-domains和supply的配置。很多诡异问题都是这里埋的坑。

2.3 Runtime PM——运行时动态电源管理

Runtime PM,顾名思义,是系统运行时动态管理设备电源的机制。它不像suspend那样把整个系统都挂起,而是针对单个设备——设备空闲了就关掉它的时钟或电源,需要时再重新打开。

它的核心思想很简单:用的时候开,不用的时候关。但实现起来可没那么简单。

Runtime PM有三个关键状态:

状态 含义 典型场景
ACTIVE 设备正在工作 设备正在处理I/O
SUSPENDED 设备已挂起 设备空闲超过阈值
SUSPENDING/RESUMING 状态切换中 正在执行回调函数

驱动开发者需要实现三个回调:runtime_suspendruntime_resumeruntime_idle。嗯,这里要注意——runtime_idle不是必须的,但它可以帮你做“预判”,在设备真正空闲前就决定要不要挂起。

// 一个典型的Runtime PM驱动片段
static int my_device_runtime_suspend(struct device *dev)
{
    struct my_device *mydev = dev_get_drvdata(dev);
    
    // 关闭设备时钟
    clk_disable(mydev->clk);
    // 关闭设备电源
    regulator_disable(mydev->regulator);
    
    dev_dbg(dev, "runtime suspended\n");
    return 0;
}

static int my_device_runtime_resume(struct device *dev)
{
    struct my_device *mydev = dev_get_drvdata(dev);
    
    // 打开设备电源
    regulator_enable(mydev->regulator);
    // 打开设备时钟
    clk_enable(mydev->clk);
    
    dev_dbg(dev, "runtime resumed\n");
    return 0;
}

避坑指南:我曾经在某个项目中,runtime_resume里忘了恢复寄存器状态,结果设备唤醒后工作异常。记住:挂起时保存上下文,恢复时还原上下文——这是基本功。

2.4 Suspend/Resume——系统级休眠与唤醒

Runtime PM管的是单个设备,而Suspend/Resume管的是整个系统。常见的suspend状态有:

  • Suspend-to-Idle (S0):轻度睡眠,CPU进入idle状态,但内存保持供电
  • Suspend-to-RAM (S3):深度睡眠,CPU断电,内存保持自刷新
  • Suspend-to-Disk (S4):休眠,系统状态保存到磁盘,完全断电

Suspend/Resume的流程是分阶段的。我简单画一下:

suspend流程:
  1. 用户空间冻结(freeze processes)
  2. 设备挂起(device suspend)
     - 按依赖顺序:先挂起子设备,再挂起父设备
  3. 系统核心挂起(syscore suspend)
  4. CPU挂起(CPU suspend)

resume流程:
  1. CPU恢复
  2. 系统核心恢复
  3. 设备恢复(device resume)
     - 按依赖顺序:先恢复父设备,再恢复子设备
  4. 用户空间解冻

为什么suspend和resume的顺序是反的?你想想看——父设备是子设备的依赖,父设备挂了,子设备肯定没法正常工作。反过来,恢复时得先把父设备弄好,子设备才能干活。

2.5 CPU Freq——动态调频

CPU Freq(也叫cpufreq)是动态调整CPU频率和电压的机制。它的核心逻辑是:负载高时提频,负载低时降频

cpufreq有两个关键角色:

  • Governor:策略制定者,决定当前该用什么频率
  • Driver:策略执行者,负责实际切换频率

常见的governor有:

Governor 策略 适用场景
performance 始终最高频 追求性能,不在乎功耗
powersave 始终最低频 极致省电,性能无所谓
ondemand 按需调频 大多数场景的平衡方案
conservative 缓慢调频 对频率变化敏感的场景
schedutil 基于调度器负载 最新、最精准的方案

我个人比较推荐schedutil。它直接利用调度器的负载信息来做调频决策,响应更快、更精准。我在一个视频编解码项目里用过,功耗比ondemand低了将近15%。

2.6 CPU Idle——空闲时省电

CPU Idle和CPU Freq是两码事。CPU Freq是“干活时用多大力”,CPU Idle是“不干活时怎么休息”。

CPU Idle的核心是idle state(也叫C-state)。每个C-state代表一种睡眠深度:

  • C0:正常工作状态
  • C1:浅度睡眠,随时可以唤醒
  • C2:中度睡眠,唤醒延迟稍大
  • C3:深度睡眠,省电最多但唤醒最慢

idle governor负责选择进入哪个C-state。常见的governor有:

  • menu:根据预测的空闲时间选择最合适的C-state
  • ladder:逐级加深睡眠,比较保守

menu governor是目前的主流选择。它会根据历史数据预测CPU会空闲多久,然后选择一个“性价比最高”的C-state。

一个小技巧:如果你发现系统响应变慢,可以检查一下是不是进入了太深的C-state。有时候省电和省性能需要权衡。

2.7 Devfreq——设备动态调频

Devfreq是CPU Freq的“兄弟”,但它管的是非CPU设备——比如GPU、DDR控制器、总线等。

Devfreq的架构和cpufreq非常像:

  • Governor:决定设备的工作频率
  • Driver:执行频率切换

常见的devfreq governor:

  • simple_ondemand:类似cpufreq的ondemand
  • userspace:用户空间手动控制
  • passive:跟随父设备的频率变化

我记得在一个GPU项目中,默认的simple_ondemand调频太激进,导致画面卡顿。后来换成了自定义的governor,根据帧率来做调频决策,效果好了很多。

2.8 各组件如何协同工作

好了,上面讲了五个核心组件。它们不是孤立的,而是相互配合的。举个例子:

  1. 系统空闲时,CPU Idle让CPU进入C-state省电
  2. 如果某个设备空闲了,Runtime PM把它挂起
  3. CPU负载降低,CPU Freq自动降频
  4. GPU负载降低,Devfreq自动降频
  5. 如果整个系统都空闲了,可以触发Suspend-to-RAM

你看,从单个设备到整个系统,从CPU到外设,每个组件都在自己的层面做省电。这就是Linux电源管理的精髓——分层协作,各司其职

总结一下:PM Core是框架,Runtime PM管设备级,Suspend/Resume管系统级,CPU Freq管CPU频率,CPU Idle管CPU空闲,Devfreq管外设频率。把这几个搞明白了,Linux电源管理你就入门了。

下一章,我们会深入PM Core的代码实现,看看这些回调函数到底是怎么注册和调用的。到时候我会带大家手撕源码,敬请期待。