转载:http://blog.sina.com.cn/s/blog_a6559d920101i52j.html
1.Linux 描述的电源状态
- On(on)
- Standby (standby)
-
- Suspend to Disk,Hibernation(disk)
S3 aka STR(suspend to ram),挂起到内存,简称待机。计算机将目前的运行状态等数据存放在内存,关闭硬盘、外设等设备,进入等待状态。此时内存仍然需要电力维持其数据,但整机耗电很少。恢复时计算机从内存读出数据,回到挂起前的状态,恢复速度较快。对DDR的耗电情况进行优化是S3性能的关键,大多数手持设备都是用S3待机。
S4 aka STD(suspend to disk),挂起到硬盘,简称休眠。把运行状态等数据存放在硬盘上某个文件或者某个特定的区域,关闭硬盘、外设等设备,进入关机状态。此时计算机完全关闭,不耗电。恢复时计算机从休眠文件/分区中读出数据,回到休眠前的状态,恢复速度较慢。电子书项目中,见过一款索尼的电子书,没有定义关机状态,只定义了S4,从而提高开机速度。
2.Linux内核电源管理接口
- static
int __init pm_init(void) - {
-
int error = pm_start_workqueue(); -
if (error) -
return error; -
hibernate_image_size_init(); -
hibernate_reserved_size_init(); -
power_kobj = kobject_create_and_add("power", NULL); -
if (!power_kobj) -
return -ENOMEM; -
error = sysfs_create_group(power_kobj, &attr_group); -
if (error) -
return error; -
return pm_autosleep_init(); - }
- static
struct attribute * g[] = { -
&state_attr.attr, - #ifdef
CONFIG_PM_TRACE -
&pm_trace_attr.attr, -
&pm_trace_dev_match_attr.attr, - #endif
- #ifdef
CONFIG_PM_SLEEP -
&pm_async_attr.attr, -
&wakeup_count_attr.attr, - #ifdef
CONFIG_PM_AUTOSLEEP -
&autosleep_attr.attr, - #endif
- #ifdef
CONFIG_PM_WAKELOCKS -
&wake_lock_attr.attr, -
&wake_unlock_attr.attr, - #endif
- #ifdef
CONFIG_PM_DEBUG -
&pm_test_attr.attr, - #endif
- #endif
-
NULL, - };
-
- static
struct attribute_group attr_group = { -
.attrs = g, - };
3. 电源状态切换(state属性文件)
- static
ssize_t state_store(struct kobject *kobj, struct kobj_attribute *attr, -
const char *buf, size_t n) - {
-
suspend_state_t state; -
int error; -
-
error = pm_autosleep_lock(); -
if (error) -
return error; -
-
if (pm_autosleep_state() > PM_SUSPEND_ON) { -
error = -EBUSY; -
goto out; -
} -
-
state = decode_state(buf, n); -
if (state < PM_SUSPEND_MAX) -
error = pm_suspend(state); -
else if (state == PM_SUSPEND_MAX) -
error = hibernate(); -
else -
error = -EINVAL; -
-
out: -
pm_autosleep_unlock(); -
return error ? error : n; - }
b)挂起设备
c)针对soc相应的节电操作和为唤醒做准备工作
d)smp中非启动cpu的挂起
e)cpu core的挂起
3.1 冻结用户进程
进程冻结模块由CONFIG_FREEZER宏开关控制。
相关代码在kernel/power/process.c中,程序的结构比较简洁,总共就五个函数。
freeze process的控制过程都在freeze_processes函数中
- int
freeze_processes(void) - {
-
int error; -
-
error = __usermodehelper_disable(UMH_FREEZING); -
if (error) -
return error; -
-
if (!pm_freezing) -
atomic_inc(&system_freezing_cnt); -
-
printk("Freezing user space processes ... "); -
pm_freezing = true; -
error = try_to_freeze_tasks(true); -
if (!error) { -
printk("done."); -
__usermodehelper_set_disable_depth(UMH_DISABLED); -
oom_killer_disable(); -
} -
printk("\n"); -
BUG_ON(in_atomic()); -
-
if (error) -
thaw_processes(); -
return error; - }
进程的冻结控制过程如下:
1. 冻结khelper内核线程。khelper是一个用于从内核空间调用用户空间应用程序的内核模块。能够在动态加载模块,热插拔等场景中发挥作用。
2.调用try_to_freeze_tasks冻结task_struct表中的任务。
3.如果try_to_freeze_tasks没有返回error则disable usermode helper 和 oom killer并成功返回。
4.如果try_to_freeze_tasks返回error则调用thaw_processes解冻操作。
try_to_freeze_tasks和thaw_process是一对反函数。冻结进程的工作是在两个循环中完成的。冻结进程要访问task_struct列表,所以先要加锁,然后用do_each_thread(g, p) 和while_each_thread(g, p)循环改变task_struct列表中进程的状态。把两个宏展开后的代码如下:
- /×for循环从init_task(idle
process)开始遍历所有进程×/ - for
(g = p = &init_task ; (g = p = next_task(g)) != &init_task ; ) -
do{ -
/×如果是当前进程或者freeze_task失败则回到for循环开始下一个进程×/ -
if (p == current || !freeze_task(p)) -
continue; -
/×如果进程是stop或者应该跳过的进程则标记todo++,后面会专门针对这类进程重新处理×/ -
if (!task_is_stopped_or_traced(p) && !freezer_should_skip(p)) -
todo++; -
}while ((p = next_thread(p)) != g) //while循环则从主线程开始遍历每个进程的所有线程
3.2 挂起设备(Device Power Manager)
- struct
dev_pm_ops { -
int (*prepare)(struct device *dev); -
void (*complete)(struct device *dev); -
int (*suspend)(struct device *dev); -
int (*resume)(struct device *dev); -
int (*freeze)(struct device *dev); -
int (*thaw)(struct device *dev); -
int (*poweroff)(struct device *dev); -
int (*restore)(struct device *dev); -
int (*suspend_late)(struct device *dev); -
int (*resume_early)(struct device *dev); -
int (*freeze_late)(struct device *dev); -
int (*thaw_early)(struct device *dev); -
int (*poweroff_late)(struct device *dev); -
int (*restore_early)(struct device *dev); -
int (*suspend_noirq)(struct device *dev); -
int (*resume_noirq)(struct device *dev); -
int (*freeze_noirq)(struct device *dev); -
int (*thaw_noirq)(struct device *dev); -
int (*poweroff_noirq)(struct device *dev); -
int (*restore_noirq)(struct device *dev); -
int (*runtime_suspend)(struct device *dev); -
int (*runtime_resume)(struct device *dev); -
int (*runtime_idle)(struct device *dev); - };
- struct
dev_pm_info power; - struct
dev_pm_domain *pm_domain;
devicesuspend由suspend模块完成,suspend模块由CONFIG_SUSPEND宏开关控制
- obj-$(CONFIG_SUSPEND)
+= suspend.o
suspend模块对外导出了pm_suspend接口:
-
- int
pm_suspend(suspend_state_t state) - {
-
int error; -
-
if (state <= PM_SUSPEND_ON || state >= PM_SUSPEND_MAX) -
return -EINVAL; -
-
error = enter_state(state); -
if (error) { -
suspend_stats.fail++; -
dpm_save_failed_errno(error); -
} else { -
suspend_stats.success++; -
} -
return error; - }
- EXPORT_SYMBOL(pm_suspend);
pm_suspend被用来控制系统的设备进入指定的状态。前面提到的Linux定义的四种电源状态会被传递到这个函数,pm_suspend会对电源状态做检查,如果传入的是非法之,直接返回EINVAL。
四种电源状态定在include/linux/suspend.h文件中
- typedef
int __bitwise suspend_state_t; -
- #define
PM_SUSPEND_ON ((__force suspend_state_t) 0) - #define
PM_SUSPEND_STANDBY ((__force suspend_state_t) 1) - #define
PM_SUSPEND_MEM ((__force suspend_state_t) 3) - #define
PM_SUSPEND_MAX ((__force suspend_state_t) 4)
-
- int
dpm_suspend_start(pm_message_t state) - {
-
int error; -
-
error = dpm_prepare(state); -
if (error) { -
suspend_stats.failed_prepare++; -
dpm_save_failed_step(SUSPEND_PREPARE); -
} else -
error = dpm_suspend(state); -
return error; - }
- EXPORT_SYMBOL_GPL(dpm_suspend_start);
-
- void
__suspend_report_result(const char *function, void *fn, int ret) - {
-
if (ret) -
printk(KERN_ERR "%s(): %pF returns %d\n", function, fn, ret); - }
- EXPORT_SYMBOL_GPL(__suspend_report_result);
-
-
- int
device_pm_wait_for_dev(struct device *subordinate, struct device *dev) - {
-
dpm_wait(dev, subordinate->power.async_suspend); -
return async_error; - }
- EXPORT_SYMBOL_GPL(device_pm_wait_for_dev);
3.3 平台相关挂起操作(platform suspending)
在设备挂起操作完成之后,会针对特定平台做状态转换操作。Linux内核电源管理模块也为此定义了一组标准函数接口。不同架构只需要实现相应接口即可。
- struct
platform_suspend_ops { -
int (*valid)(suspend_state_t state); -
int (*begin)(suspend_state_t state); -
int (*prepare)(void); -
int (*prepare_late)(void); -
int (*enter)(suspend_state_t state); -
void (*wake)(void); -
void (*finish)(void); -
bool (*suspend_again)(void); -
void (*end)(void); -
void (*recover)(void); - };
这组函数功能如下: