Linux电源管理(1)

  1. Linux电源管理架构
  2. CPUFreq驱动
    1. CPUFreq驱动实现
    2. CPUFreq的策略
    3. CPUFreq的性能测试和调优
    4. CPUFreq的通知
  3. CPUIdle驱动
    1. CPUIdle驱动的实现

Linux电源管理架构

Linux电源管理十分复杂,涉及到系统级的待机、频率电压变换、系统空闲时的处理以及每个设备驱动对系统待机的支持和每个设备的运行时电源管理。

Linux的电源管理大致可以分为以下几类:

  1. CPU在运行时根据系统负载进行动态电压和频率变换的CPUFreq
  2. CPU在系统空闲时根据空闲情况进行低功耗模式的CPUIdle
  3. 多核系统下CPU热插拔的支持
  4. 系统和设备针对延迟的特别需求而提出申请的PM QoS。
  5. 设备驱动针对系统挂起到RAM/硬盘的一系列入口函数
  6. SoC进入挂起状态、SDRAM自刷新的入口
  7. 设备的运行时动态电源管理
  8. 底层的时钟、稳压器、频率/电压表支撑

内核电源管理架构

CPUFreq驱动

CPUFreq子系统位于drivers/cpufreq目录下,负责运行时CPU频率和电压的动态调整,即DVFS(Dynamic Voltage Frequency Scaling)。其目的在于降低功耗(CMOS电路中的功耗与电压的平方成正比、与频率成正比)。

CPUFreq核心为CPUFreq驱动提供了一套统一的接口和notifier机制,方便CPUFreq驱动的编写和事件的通知。CPU运行频率发生变化时,内核的loops_per_jiffy常数也会发生变化。

CPUFreq驱动实现

每个SoC的具体CPUFreq驱动只需要实现电压、频率表,以及从硬件层面完成这些变化。其在内核中的抽象为cpufreq_driver。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
struct cpufreq_driver {
char name[CPUFREQ_NAME_LEN];
u8 flags;
void *driver_data;

/* needed by all drivers */
int (*init)(struct cpufreq_policy *policy);
int (*verify)(struct cpufreq_policy *policy);

/* define one out of two */
int (*setpolicy)(struct cpufreq_policy *policy);

/*
* On failure, should always restore frequency to policy->restore_freq
* (i.e. old freq).
*/
int (*target)(struct cpufreq_policy *policy,
unsigned int target_freq,
unsigned int relation); /* Deprecated */
int (*target_index)(struct cpufreq_policy *policy,
unsigned int index);
unsigned int (*fast_switch)(struct cpufreq_policy *policy,
unsigned int target_freq);

unsigned int (*resolve_freq)(struct cpufreq_policy *policy,
unsigned int target_freq);

unsigned int (*get_intermediate)(struct cpufreq_policy *policy,
unsigned int index);
int (*target_intermediate)(struct cpufreq_policy *policy,
unsigned int index);

/* should be defined, if possible */
unsigned int (*get)(unsigned int cpu);

/* Called to update policy limits on firmware notifications. */
void (*update_limits)(unsigned int cpu);

/* optional */
int (*bios_limit)(int cpu, unsigned int *limit);

int (*online)(struct cpufreq_policy *policy);
int (*offline)(struct cpufreq_policy *policy);
int (*exit)(struct cpufreq_policy *policy);
void (*stop_cpu)(struct cpufreq_policy *policy);
int (*suspend)(struct cpufreq_policy *policy);
int (*resume)(struct cpufreq_policy *policy);

/* Will be called after the driver is fully initialized */
void (*ready)(struct cpufreq_policy *policy);

struct freq_attr **attr;

/* platform specific boost support code */
bool boost_enabled;
int (*set_boost)(int state);
};

CPUFreq驱动就是对cpufreq_driver数据结构的填充,可以说linux说有的驱动实现都是对相应驱动数据结构的填充。具体可参考内核中相应代码。

CPUFreq的策略

CPUFreq驱动设定了CPU的频率参数,以及提供了设置频率的途径。而CPUFreq的策略则负责频率变化的调整。

在内核中,分别实现了如下CPUFreq策略,其通过cpufreq governor进行管理。

CPUFreq策略 策略的实现方法
ondemand 平时以低速方式运行,当系统负载提高时按需自动提高频率
performance CPU以最高频率运行
conservative 与cpufreq_ondemand 类似,区别在动态频率变更时采用渐进的方式
powersave CPU以最低频率运行
userspace 通过sys节点scaling_setspeed设置频率,同时设置scaling_governor节点为userspace

总之,系统的状态和CPUFreq的策略共同决定了CPU频率跳变的目标,CPUFreq的核心层将目标频率传递给底层具体SoC的CPUFreq驱动,由该驱动完成CPU频率的变换。

CPUFreq驱动架构

CPUFreq的性能测试和调优

内核中存在cpuower-utils工具集(tools/power/cpupower),其中的cpufreq-bench工具帮助分析CPUFreq对系统性能的影响。

cpufreq-bench原理是模拟系统运行时的“空闲->忙->空闲->忙”等场景,从而触发系统的动态频率变化,然后分析performance策略与其它策略下完成同样运算任务时,performance策略相对的时间占比。

CPUFreq的通知

CPUFreq子系统会发出通知的情况有两种:CPUFreq的策略变化CPU运行频率变化。当事件发生时,CPUFreq驱动会触发CPUFreq核心发出相应通知。

内核提供了如下接口方便地使用CPUFreq的通知:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#define CPUFREQ_TRANSITION_NOTIFIER (0)
#define CPUFREQ_POLICY_NOTIFIER (1)

/* Transition notifiers */
#define CPUFREQ_PRECHANGE (0)
#define CPUFREQ_POSTCHANGE (1)

/* Policy Notifiers */
#define CPUFREQ_CREATE_POLICY (0)
#define CPUFREQ_REMOVE_POLICY (1)

int cpufreq_register_notifier(struct notifier_block *nb, unsigned int list);
int cpufreq_unregister_notifier(struct notifier_block *nb, unsigned int list);

void cpufreq_freq_transition_begin(struct cpufreq_policy *policy,
struct cpufreq_freqs *freqs);
void cpufreq_freq_transition_end(struct cpufreq_policy *policy,
struct cpufreq_freqs *freqs, int transition_failed);

CPUIdle驱动

目前ARM SoC大多支持多个不同的Idle级别,CPUIdle驱动子系统存在的目的就是对这些Idle状态进行管理,并根据系统运行状态进入不同的Idle级别。

CPUIdle驱动主要实现是提供一个类似CPUFreq驱动频率表的Idle级别表,同时实现各种不同Idle状态的进入和退出的硬件相关代码。

对于支持ACPI(Advanced Configuration and Power Interface)的Intel笔记本而言,一般有4个不同的C状态。

状态 功耗(mW) 延迟(us)
C0(操作) -1 0
C1(Halt) 1000 1
C2(Stop-Clock) 500 1
C3(Sleep) 100 57

对ARM SoC而言,差异较大,但有一个最简单的WFI(等待中断发生)状态,默认会进入此状态下。

CPUIdle驱动的实现

要实现CPUIdle驱动,就是要填充cpuidle_driver结构体,具体完成可参考内核相应驱动,下面是cpuidle_driver结构体定义。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
struct cpuidle_driver {
const char *name;
struct module *owner;
int refcnt;

/* used by the cpuidle framework to setup the broadcast timer */
unsigned int bctimer:1;
/* states array must be ordered in decreasing power consumption */
struct cpuidle_state states[CPUIDLE_STATE_MAX];
int state_count;
int safe_state_index;

/* the driver handles the cpus in cpumask */
struct cpumask *cpumask;

/* preferred governor to switch at register time */
const char *governor;
};

与CPUFreq类似,在CPUIdle子系统中也有对应的governor来抉择何时进入何种Idle级别的策略。

CPUIdle策略 策略实现方法
haltpoll 根据过去空闲时间和睡眠时间动态调整最匹配的Idle级别
ladder 以步进方式进入和退出Idle级别,以过去的时间作为参考(不依赖NO_HZ配置)
menu 根据预期的空闲时间直接进入目标Idle级别(依赖NO_HZ配置)
teo 根据过去空闲时间和睡眠时间动态调整最匹配的Idle级别

转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 yxhlfx@163.com

文章标题:Linux电源管理(1)

本文作者:红尘追风

发布时间:2017-01-09, 18:15:17

原始链接:http://www.micernel.com/2017/01/09/Linux%E7%94%B5%E6%BA%90%E7%AE%A1%E7%90%86(1)/

版权声明: "署名-非商用-相同方式共享 4.0" 转载请保留原文链接及作者。

目录