1、Linux 电源管理的组成
电源管理(Power Management)在 Linux Kernel 中,是一个比较庞大的子系统,涉及到供电(Power Supply)、充电(Charger)、时钟(Clock)、频率(Frequency)、电压(Voltage)、睡眠/唤醒(Suspend/Resume)等方方面面。
注1:该图片只是一个示意图,并没有划分软件层次,因此模块之间的关系不一定是真正的关系。
注2:Framework 是一个中间层的软件,提供软件开发的框架。其目有三:一是屏蔽具体的实现细节,固定对上的接口,这样可以方便上层软件的开发和维护;二是尽可能抽象公共逻辑,并在 Framework 内实现,以提高重用性、减少开发量;三是向下层提供一系列的回调函数(callback function),下层软件可能面对差别较大的现实,但只要填充这些回调函数,即可完成所有逻辑,减小了开发的难度。
注3:Runtime PM 是 Linux Kernel 亲生的运行时电源管理机制,Wakelock 是由 Android 提出的机制。这两种机制的目的是一样的,因此只需要支持一种即可。另外,由于 Wakelock 机制路子太野了,饱受 Linux 社区的鄙视。
在对图片中的这些组件(也可以称作 Framework )进行详细描述之前,先在这里了解一下基本概念。
- Power Supply,是一个供用户空间程序监控系统的供电状态(电池供电、USB 供电、AC供电等等)的 class。通俗的讲,它是一个 Battery&Charger 驱动的 Framework
- Clock Framework,Clock 驱动的 Framework,用于统一管理系统的时钟资源
- Regulator Framework,Voltage/Current Regulator 驱动的 Framework。该驱动用于调节 CPU 等模块的电压和电流值
- Dynamic Tick/Clock Event,在传统的 Linux Kernel 中,系统 Tick 是固定周期(如 10ms)的,因此每隔一个 Tick,就会产生一个 Timer 中断。这会唤醒处于 Idle 或者 Sleep 状态的 CPU,而很多时候这种唤醒是没有意义的。因此新的 Kernel 就提出了 Dynamic Tick 的概念,Tick 不再是周期性的,而是根据系统中定时器的情况,不规律的产生,这样可以减少很多无用的 Timer 中断
- CPU Idle,用于控制 CPU Idle 状态的 Framework
- Generic PM,传统意义上的 Power Management,如Power Off、Suspend to RAM、Suspend to Disk、Hibernate 等
- Runtime PM and Wakelock,运行时的 Power Management,不再需要用户程序的干涉,由 Kernel 统一调度,实时的关闭或打开设备,以便在使用性能和省电性能之间找到最佳的平衡
- CPU Freq/Device Freq,用于实现 CPU 以及 Device 频率调整的 Framework
- OPP(Operating Performance Point),是指可以使 SOCs 或者 Devices 正常工作的电压和频率组合。内核提供这一个 Layer,是为了在众多的电压和频率组合中,筛选出一些相对固定的组合,从而使事情变得更为简单一些
- PM QOS,所谓的 PM QOS,是指系统在指定的运行状态下(不同电压、频率,不同模式之间切换,等等)的工作质量,包括 latency、timeout、throughput 三个参数,单位分别为 us、us 和 kb/s。通过 QOS 参数,可以分析、改善系统的性能
2、电源管理源码目录
代码语言:javascript复制kernel/power/
drivers/power/
drivers/base/power/
drivers/cpuidle/
drivers/cpufreq/
drivers/devfreq/
include/linux/power_supply.h
include/linux/cpuidle.h
include/linux/cpufreq.h
include/linux/cpu_pm.h
include/linux/device.h
include/linux/pm.h
include/linux/pm_domain.h
include/linux/pm_runtime.h
include/linux/pm_wakeup.h
include/linux/qos.h
include/linux/suspend.h
Documentation/power/xxx.txt
电源管理的东西很多,大家没必要每个都学一遍,用到的时候去研究即可。
3、实例分析
最近博主遇到 i2c 传输慢和中断触发慢的问题,一般这种【慢】的情况大都和【性能与功耗冲突】相关,研究了 Qos 系统,打了笔 patch 解决了。
中断触发慢:注册的下降沿中断,从下降沿打到芯片中,到跑到中断处理函数,快则 270us,慢则 2.7ms。由于所做功能对中断处理时间有要求,因此要解决中断处理慢的问题。
抓 trace 分析
使用上次博主发的脚本,可以抓到 ftrace,这个脚本中博主使能了 sched_switch、sched_wakeup、irq、irq_handler_entry、irq_handler_exit、cpu_idle、pm_qos_update_request 等 event。这些 event 可以记录下 CPU 调度和中断处理情况。
从抓到的 trace 分析,中断处理慢并不是由于 CPU loading 重导致的处理不及时,而是中断来的时候,CPU0 处于 idle 状态,而 kernel-5.10 以后除了特定的 feature,所有的中断都默认发到 CPU0,这样即便设置了中断可以唤醒系统,把 CPU0 从 idle 转为 active 也要 1ms。
问题确定后,就是如何处理的问题了。找了低功耗的同事,确认 CPU 在没事情做的时候就是会进入 idle,即便在游戏场景,也不会禁止 CPU 进入 idle。
研究了一下 Linux 电源管理子系统,发现 Qos 有接口可以使用:在某一段时间内拉 Qos,可以让 CPU 在这段时间不进入 idle,使用完毕再去掉 Qos,让 CPU 可以进入 idle,这样满足了性能需求,带来的功耗也不是特别高。
PM QoS classes framework 位于 kernel/power/qos.c 中,负责系统级别的 PM QoS 管理。per-device PM QoS framework 位于 drivers/base/power/qos.c 中,负责 per-device 的 PM QoS 管理。Common header 位于 include/linux/pm_qos.h 中,负责通用数据结构的抽象、函数声明等工作。
在 kernel/power/qos.c 中,有 cpu_latency_qos_update_request 接口可以使用,通过该接口将 Qos 拉到 150,使用完毕再将 Qos 拉到 -1(关闭)。
使用方法:
代码语言:javascript复制1、文件开头注册自己的结构体:
struct pm_qos_request my_qos_request;
2、自己驱动的 probe 函数加上:
cpu_latency_qos_add_request(my_qos_request, PM_QOS_DEFAULT_VALUE);
3、在做事情前加上:
cpu_latency_qos_update_request(my_qos_request, 150);
4、在做事情后加上:
cpu_latency_qos_update_request(my_qos_request, PM_QOS_DEFAULT_VALUE);
PM_QOS_DEFAULT_VALUE 其实就是 -1
这样在自己做事情期间,CPU 就不会进入 idle,自己模块的性能就会好很多。如果还要更好,可以在此期间调节 CPU 频率,但调频带来的功耗很高,需要自己评估。
该 patch 解决的问题:
1、中断处理慢,可以在第一次中断打进来后,拉 Qos,这样自己后面的几次中断处理一定会快,使用完毕后,去掉 Qos。
2、i2c 传输慢,其中一种情况是 i2c 传输完毕返回时,CPU0 进入 idle,导致 i2c 中断打不进来,详情参考我的文章(背景:设置 i2c 中断无法唤醒系统):
手把手教你使用 ftrace
手把手教你分析 trace【附脚本】
这种情况,我们在调用 i2c_transfer 前后加上 cpu_latency_qos_update_request 的接口,就可以解决该问题。