Linux 电源管理及实例分析

2023-01-03 19:17:06 浏览数 (2)

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 )进行详细描述之前,先在这里了解一下基本概念。

  1. Power Supply,是一个供用户空间程序监控系统的供电状态(电池供电、USB 供电、AC供电等等)的 class。通俗的讲,它是一个 Battery&Charger 驱动的 Framework
  2. Clock Framework,Clock 驱动的 Framework,用于统一管理系统的时钟资源
  3. Regulator Framework,Voltage/Current Regulator 驱动的 Framework。该驱动用于调节 CPU 等模块的电压和电流值
  4. Dynamic Tick/Clock Event,在传统的 Linux Kernel 中,系统 Tick 是固定周期(如 10ms)的,因此每隔一个 Tick,就会产生一个 Timer 中断。这会唤醒处于 Idle 或者 Sleep 状态的 CPU,而很多时候这种唤醒是没有意义的。因此新的 Kernel 就提出了 Dynamic Tick 的概念,Tick 不再是周期性的,而是根据系统中定时器的情况,不规律的产生,这样可以减少很多无用的 Timer 中断
  5. CPU Idle,用于控制 CPU Idle 状态的 Framework
  6. Generic PM,传统意义上的 Power Management,如Power Off、Suspend to RAM、Suspend to Disk、Hibernate 等
  7. Runtime PM and Wakelock,运行时的 Power Management,不再需要用户程序的干涉,由 Kernel 统一调度,实时的关闭或打开设备,以便在使用性能和省电性能之间找到最佳的平衡
  8. CPU Freq/Device Freq,用于实现 CPU 以及 Device 频率调整的 Framework
  9. OPP(Operating Performance Point),是指可以使 SOCs 或者 Devices 正常工作的电压和频率组合。内核提供这一个 Layer,是为了在众多的电压和频率组合中,筛选出一些相对固定的组合,从而使事情变得更为简单一些
  10. 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 的接口,就可以解决该问题。

0 人点赞