3、Linux电源管理子系统概述:PM核心框架(/sys/power),CPU idle、CPU freq、Device PM、Clock框架、Regulator框架

好,我们直接切入正题。嵌入式Linux的电源管理,说白了就是一套「省电组合拳」。你想想看,一个跑Linux的嵌入式设备,电池就那么点容量,CPU、外设、内存、总线全都在跑,如果不做管理,电量很快就见底了。

我个人习惯把Linux电源管理子系统拆成几个核心模块来理解。它们各自负责不同的硬件层面,但又互相配合。今天我们就逐一过一遍。

3.1 PM核心框架:/sys/power 接口

这是整个电源管理的「总开关」。Linux内核通过一个虚拟文件系统 /sys/power 暴露给用户空间。你可以在终端里直接操作它。

举个例子,查看当前支持的休眠模式:

cat /sys/power/state

输出可能是:

freeze mem standby

这三个状态分别代表:

  • freeze:冻结所有用户空间进程,CPU进入idle,但内存保持供电。恢复最快。
  • standby:比freeze更深一点,CPU可能关闭更多缓存。
  • mem:Suspend-to-RAM,内存自刷新,CPU和大部分外设断电。这是嵌入式设备最常用的省电模式。

我曾在项目中遇到过一个问题:某款IoT设备在mem模式下唤醒后,Wi-Fi模块死活连不上。后来发现是网卡驱动没有正确实现suspend/resume回调。嗯,这里要注意——/sys/power/state只是入口,真正干活的是各个驱动。

核心要点:PM核心框架负责协调系统级的休眠与唤醒流程。它调用各子系统的回调函数,确保硬件状态被正确保存和恢复。

3.2 CPU idle:让CPU「偷懒」

CPU idle管理的是CPU在「无事可做」时的状态。你想想看,CPU执行完所有任务后,如果还在全速跑,那就是在浪费电。

Linux内核使用cpuidle框架来管理CPU的空闲状态。不同的ARM SoC会定义多个idle level,比如:

Level 名称 功耗节省 唤醒延迟
0 WFI (Wait For Interrupt) 极低
1 CPU Retention
2 CPU Power Down 较高

我曾经调试过一个项目,CPU idle进入Level 2后,定时器中断无法及时唤醒,导致系统响应变慢。解决方案是调整cpuidle.governor,从menu换成ladder,让CPU更保守地进入深度idle。

避坑指南:如果你发现系统响应偶尔「卡顿」,先检查CPU idle governor的配置。我曾经被这个问题坑了整整两天。

3.3 CPU freq:动态调频(DVFS)

CPU freq框架,也叫CPUFreq,负责动态调整CPU的工作频率和电压。说白了就是:任务重时跑高频,任务轻时跑低频。

Linux内核提供了几种调频策略(governor):

  • performance:始终最高频,性能优先。
  • powersave:始终最低频,省电优先。
  • ondemand:根据CPU负载动态调频,兼顾性能和功耗。
  • conservative:类似ondemand,但频率变化更平滑。
  • schedutil:基于调度器负载信息,更精准。这是新内核推荐的方式。

我个人习惯在嵌入式产品中先用ondemand,然后根据实际功耗测试结果再微调。你可以在用户空间通过/sys/devices/system/cpu/cpu0/cpufreq/来查看和设置。

cat /sys/devices/system/cpu/cpu0/cpufreq/scaling_available_frequencies

输出类似:

1200000 800000 500000 200000

嗯,这里要注意:不是所有SoC都支持所有频率点。有些芯片在低频下电压不够,会导致系统不稳定。我遇到过一款平板电脑,在200MHz下屏幕闪烁,后来发现是DDR控制器在低频下时序不满足。

警告:调频时务必确认SoC的电压-频率对应表(OPP Table)。盲目降频可能导致系统崩溃。

3.4 Device PM:设备级的电源管理

CPU idle和CPU freq管的是CPU本身,但嵌入式系统里还有一大堆外设:I2C、SPI、UART、USB、Wi-Fi、蓝牙……每个设备都需要单独管理。

Linux内核通过Device PM框架,为每个设备驱动提供suspendresume回调。当系统进入休眠时,内核会按照设备树的依赖关系,依次调用每个驱动的suspend函数。

典型的设备驱动PM回调:

static int my_device_suspend(struct device *dev) {
    // 保存寄存器状态
    // 关闭时钟
    // 关闭电源
    return 0;
}

static int my_device_resume(struct device *dev) {
    // 恢复电源
    // 恢复时钟
    // 恢复寄存器状态
    return 0;
}

我曾经在调试一个音频编解码器驱动时,发现resume后I2C通信失败。原因是I2C控制器先于音频设备恢复,但音频设备还没准备好。解决方案是在设备树中增加power-domains属性,确保依赖关系正确。

核心要点:Device PM的成败往往取决于设备树中电源域(power domain)和时钟的依赖关系。写驱动时一定要理清硬件拓扑。

3.5 Clock框架:管理时钟树

时钟是芯片的「心跳」。每个外设都需要时钟才能工作。Linux的Clock框架(CCF,Common Clock Framework)负责管理SoC内部的时钟树。

你可以通过/sys/kernel/debug/clk/clk_summary查看当前时钟状态:

cat /sys/kernel/debug/clk/clk_summary

输出类似:

   clock                         enable_cnt  prepare_cnt  rate
   osc24M                        5           5            24000000
   cpu_clk                       1           1            1200000000
   uart_clk                      0           0            48000000
   i2c_clk                       1           1            100000000

你看,uart_clkenable_cnt是0,说明UART模块当前没有使用,可以关闭时钟来省电。

我建议你在做低功耗设计时,先通过这个接口摸清整个时钟树。哪些时钟是常开的?哪些可以动态关闭?很多时候,功耗降不下来就是因为某个「看似不重要」的时钟一直开着。

技巧:在驱动中,使用clk_enable()clk_disable()要成对出现。我曾经见过一个驱动只enabledisable,导致时钟一直开着,白白浪费了几十毫瓦。

3.6 Regulator框架:电压调节器

Regulator框架管理的是电压。每个外设都需要特定的工作电压,有些还支持动态调压(比如CPU的DVFS)。

Linux内核通过Regulator框架来抽象这些电压调节器。驱动可以通过API请求和释放电压:

struct regulator *vcc;
vcc = regulator_get(dev, "vcc-core");
regulator_set_voltage(vcc, 1000000, 1000000);  // 1.0V
regulator_enable(vcc);
// ... 使用设备 ...
regulator_disable(vcc);
regulator_put(vcc);

我在项目中遇到过一个问题:某款摄像头模组在休眠后无法唤醒,测量发现电压被完全切断了。后来在设备树中增加了regulator-always-on属性,让该路电源在休眠时保持供电。

警告:不要随意使用regulator-always-on。它会阻止该路电源在休眠时关闭,增加静态功耗。只有在设备确实需要常供电时才使用。

小结

好了,我们快速过了一遍Linux电源管理子系统的六大核心模块:

  • PM核心框架:系统级休眠/唤醒的总调度。
  • CPU idle:让CPU在空闲时进入低功耗状态。
  • CPU freq:动态调整CPU频率和电压。
  • Device PM:管理每个外设的电源状态。
  • Clock框架:管理SoC时钟树,关闭无用时钟。
  • Regulator框架:管理电压调节器,按需供电。

这几个模块不是孤立的。举个例子:当系统进入mem休眠时,PM核心框架会先调用CPU idle让CPU进入深度睡眠,然后通过Device PM逐个关闭外设,同时Clock框架关闭对应时钟,Regulator框架降低或切断电压。这一整套流程,才是真正的「低功耗设计」。

下一章,我们会深入CPU idle的底层实现,看看ARM的WFI和WFI是如何与Linux内核交互的。到时候我会分享一个我在某款车机项目中的实际调优案例,很有意思。