在手持设备设计中,电源管理历来为重要的研究课题之一。我们日常所说的省电就属于电源管理的范畴,这也是我们最关心的一个部分。通过挂起不必要的设备、降低CPU的频率或者其它方法,可以减少能量的消耗,达到省电的目的。电源管理实际上是一个系统工程,从应用程序到内核框架,再到设备驱动和硬件设备,都要参与进来,才能达到电源管理的最优化。本文介绍一下动态电源管理(DPM)。
所谓的动态电源管理(DPM)是一种电源管理机制,它允许在系统运行时动态的管理电源,这可能是相对于传统的电源管理方式而言的,传统的电源管理方式要求系统要么挂起(suspend)以节省能源,要么恢复(resume)运行让程序正常工作,这个过程通常要用户参与(如按键),而且这种状态切换非常缓慢。在动态电源管理(DPM)中,系统一方面可以关闭暂时不使用的设备,比如关闭硬盘和显示器。另外一方面也可以根据负载的重轻,动态调整CPU和总线的频率,以达到节省能源的目的。这都是动态完成的,不需要用户的干预,而且状态之间的切换非常快(每秒数百次)。
动态电源管理(DPM)是很一个广泛的概念,很多系统实际上都采用了动态电源管理(DPM)方式,本文要谈的是Linux下的动态电源管理(DPM)。Linux很早就采用了动态电源管理,在driver目录下有个cpufreq的驱动程序,它就是用来动态调整CPU频率以降低能源消耗的。
不过cpufreq似乎不能用于嵌入式环境,主要原因是:在嵌入式系统中,与LCD显示屏等外设相比,CPU已经不是能源消耗的大户了,光调整CPU的频率用处不大。而且cpufreq还依赖于像ACPI等PC环境,而嵌入式设备一般都没有BIOS,电源管理功能只能完全由操作系统实现。cpufreq的实现目前还不太清楚,我们会在后续的文章中继续研究。
就目前掌握的资料来看,用嵌入式Linux系统的动态电源管理只有IBM奥斯汀实验室和MontaVista联合开发的动态电源管理(DPM)(http://dynamicpower.sourceforge.net/)。我们将对它的架构做简要分析,下面提到动态电源管理(DPM)实际上是特指这个解决方案及其实现。
我们先介绍几个重要概念:
1. operating point: 它实际上就是电源管理的一组配置数据,这组配置数据一旦确定,能源消耗率和系统性能也就确定了。比如:
上图有三个operaTIng point,第一个operaTIng point的名称为”33/33”,它的配置为:Core Voltage=1.0v、PLL VCO = 800MHz等如表格第二列里的数据所示。
2. operaTIng state: 它实际上就是系统的运行状态,比如工作状态和空闲状态。不过在dynamicpower中,状态可以有很多种。同是工作状态,有高性能工作状态、中等性能工作状态和低性能工作状态等,甚至更多,根据具体的情况而定。
3. policy: 它是电源管理的一个高级抽象。它负责把operaTIng state映射到一个或者一组(class) operating point上。系统中可以有多个policy,但只一个policy处理激活状态。
4. class: 代表一组operating point,在状态切换时,policy选取其中第一个满足约束条件的operating point作为有效operating point。
5. constraint:它指设备的约束条件,即只有在满足约束条件下,设备才能正常工作,比如LCD要一定总线频率才能正常更新屏幕。在状态切换时,如果下一状态对应的operating point不满足设备的约束条件,有两种选择:要么强制关闭设备,要么状态切换失败,根据设置而定。
dynamicpower可以认为是一种典型的按照机制与策略分开的模式设计的,它只实现了动态电源管理这种机制,而所有策略完全由用户空间的应用程序去做实现。总的来说它分为三个层次:
1. API函数库。这一部分主要是对内核提供的sysfs和proc文件进行封装,提供更好用的接口函数。它提供的函数如下:
int dpm_init(void); int dpm_terminate(void); int dpm_set_state(char *statename); int dpm_create_op(char *name, char *params); int dpm_set_op_param(char *op, char *param, int value); int dpm_get_op_param(char *opname, char *param, char *buf, size_t bufsiz); int dpm_create_class(char *name, char *params); int dpm_create_policy(char *name, char *params); int dpm_set_policy_state_map(char *policy, char *state, char *opclass); int dpm_get_policy_state_map(char *policy, char *state, char *buf, size_t bufsiz); int dpm_get_active_policy(char *name, size_t namemax); int dpm_set_active_policy(char *policy); |
如果明白了policy、operating state和operating point等基本概念,上述函数不难理解:首先要创建一些operating point,即各种电源管理配置; 然后把这些operating point组合成class,接下来在operating state和class/operating point之间建立映射关系,这是初始化过程要做的。在系统运行过程中,dpm会自动选择适当的模式,如果有多种policy,应用程序也可以激活适当的policy。
2. 内核框架代码。它的主要功能包括对policy的管理,各种状态的切换等平台无关的操作,同时还提供了一些sysfs和proc文件用来和用户空间的应用程序交互。它一部分的代码主要分布在下列文件中:
drivers/base/core.c drivers/base/power/Makefile drivers/base/power/power-dpm.c drivers/base/power/resume.c drivers/base/power/suspend.c drivers/base/power/sysfs.c drivers/dpm/Kconfig drivers/dpm/Makefile drivers/dpm/dpm-idle.c drivers/dpm/dpm-ui.c drivers/dpm/dpm.c drivers/dpm/proc.c fs/proc/base.c include/linux/device.h include/linux/dpm-trace.h include/linux/dpm.h include/linux/init_task.h include/linux/pm.h include/linux/sched.h kernel/sched.c kernel/softirq.c kernel/workqueue.c |
其中drivers/dpm/dpm.c和dpm-idle.c是核心代码,dpm-ui.c和proc.c主要是用于与用户空间应用程序交互的,其它代码则是用于与系统其它部分协调工作的。这一层代码不算太复杂,其中最重要的部分是状态切换,其主要过程如下:
dpm_set_os: 切换到新状态运行。 dpm_enter_state:把dpm_active_state置为新状态。 dpm_resync:让新状态生效。 dpm_choose_opt:找到适当的operating point。 1.对于单个operating point:满足设备的约束(constraint)条件吗?满足则OK,否则再判断是否要强制切换。如果是则OK,否则切换失败。 2.对于一组operating point(class): 从列表中找到第一个满足约束条件的operating point,找到了则OK。否则切换失败。 dpm_set_opt: 让operating point生效。 dpm_md.set_opt: 调用依赖于具体平台的函数设置新的operating point。 |
3. 平台相关代码。为了让operating point真正生效,通常要修改某些特定的寄存器,这是平台相关的。比如,在PXA27x上,要修改CCSR、CCCR和CLKCFG等寄存器。这一层要求实现下面几个接口函数:
struct dpm_md { int (*init_opt)(struct dpm_opt *opt); int (*set_opt)(struct dpm_opt *cur, struct dpm_opt *new); int (*get_opt)(struct dpm_opt *opt); int (*check_constraint)(struct constraint_param *param, struct dpm_opt *opt); void (*idle)(void); void (*startup)(void); void (*cleanup)(void); }; |
要了解这一层的代码,先要熟读平台datasheet相关的章节,这里不再多说。