标准linux休眠和唤醒机制分析
2019-07-13 01:13发布
生成海报
标准linux休眠和唤醒机制分析(一)
说明:
1. Based on linux2.6.32, only for mem(SDR)
2.
有兴趣请先参考阅读:电源管理方案APM和ACPI比较.doc
Linux系统的休眠与唤醒简介.doc
3.
本文先研究标准linux的休眠与唤醒,android对这部分的增改在另一篇文章中讨论
4.
基于手上的一个项目来讨论,这里只讨论共性的地方
虽然linux支持三种省电模式:standby、suspend
to ram、suspend to disk,但是在使用电池供电的手持设备上,几乎所有的方案都只支持STR模式(也有同时支持standby模式的),因为STD模式需要有交换分区的支持,但是像手机类的嵌入式设备,他们普遍使用nand来存储数据和代码,而且其上使用的文件系统yaffs一般都没有划分交换分区,所以手机类设备上的linux都没有支持STD省电模式。
一、项目power相关的配置
目前我手上的项目的linux电源管理方案配置如下,.config文件的截图,当然也可以通过make
menuconfig使用图形化来配置:
#
# CPU Power Management
#
# CONFIG_CPU_IDLE is not set
#
# Power management options
#
CONFIG_PM=y
# CONFIG_PM_DEBUG is not set
CONFIG_PM_SLEEP=y
CONFIG_SUSPEND=y
CONFIG_SUSPEND_FREEZER=y
CONFIG_HAS_WAKELOCK=y
CONFIG_HAS_EARLYSUSPEND=y
CONFIG_WAKELOCK=y
CONFIG_WAKELOCK_STAT=y
CONFIG_USER_WAKELOCK=y
CONFIG_EARLYSUSPEND=y
# CONFIG_NO_USER_SPACE_SCREEN_ACCESS_CONTROL is not set
# CONFIG_CONSOLE_EARLYSUSPEND is not set
CONFIG_FB_EARLYSUSPEND=y
# CONFIG_APM_EMULATION is not set
# CONFIG_PM_RUNTIME is not set
CONFIG_ARCH_SUSPEND_POSSIBLE=y
CONFIG_NET=y
上面的配置对应下图中的下半部分图形化配置。。。,看来是直接在Kconfig文件中删除了配置STD模式的选项。
使用上面的配置编译出来的系统,跑起来之后,进入sys目录可以看到相关的接口:
# pwd
/sys/power
# ls
state wake_lock wake_unlock wait_for_fb_sleep wait_for_fb_wake
# cat state
mem
如果配置了宏CONFIG_PM_DEBUG,那么在power目录下会多出一个pm_test文件,cat
pm_test后,列出的测试选项有:[none] core processors platform devices freezer。关于这个test模式的使用,可以参考kernel文档:/kernel/documentation/power/Basic-pm-debugging.txt
这个文档我也有详细的阅读和分析。
二、sys/power和相关属性文件创建
系统bootup时候在sys下新建power和相关属性文件,相关源码位置:
kernel/kernel/power/main.c
static int __init pm_init(void)
{
int error = pm_start_workqueue();// CONFIG_PM_RUNTIME not set, so this fun is null
if (error)
return error;
power_kobj = kobject_create_and_add("power", NULL);
//建立power对应的kobject和sysfs_dirent对象,同时建立联系:kobject.sd
=
// &sysfs_dirent, sysfs_dirent.s_dir->kobj = &kobject。
if (!power_kobj)
return -ENOMEM;
return sysfs_create_group(power_kobj, &attr_group);
//建立一组属性文件,可以在power下建立一个子目录来存放这些属性文件,
//不过需要在结构体attr_group中指定name,否则直接将这些属性文件放在
// power_kobj对应的目录下。
}
core_initcall(pm_init);//看的出来,该函数是很早就被调用,initcall等级为1
static struct attribute_group attr_group = {
.attrs = g,
};
struct attribute_group {
const char *name;
mode_t (*is_visible)(struct kobject *,
struct attribute *, int);
struct attribute **attrs;
};
//属性文件都是以最基本得属性结构struct attribute来建立的
static struct attribute * g[] = {
&state_attr.attr,
#ifdef CONFIG_PM_TRACE // not set
&pm_trace_attr.attr,
#endif
#if defined(CONFIG_PM_SLEEP) && defined(CONFIG_PM_DEBUG) // not set
&pm_test_attr.attr,
#endif
#ifdef CONFIG_USER_WAKELOCK // set
&wake_lock_attr.attr,
&wake_unlock_attr.attr,
#endif
NULL,
};
#ifdef CONFIG_PM_SLEEP
#ifdef CONFIG_PM_DEBUG
power_attr(pm_test);
#endif
#endif
power_attr(state);
#ifdef CONFIG_PM_TRACE
power_attr(pm_trace);
#endif
#ifdef CONFIG_USER_WAKELOCK
power_attr(wake_lock);
power_attr(wake_unlock);
#endif
#define power_attr(_name)
static struct kobj_attribute _name##_attr = {
.attr = {
.name = __stringify(_name),
.mode = 0644,
},
.show = _name##_show,
.store = _name##_store,
}
//而这些被封装过的属性结构体,将来会使用kobject的ktype.sysfs_ops->show(store)这两个通用函数通过container_of()宏找到实际的属性结构体中的show和store函数来调用。
关于更多sysfs的内容,请查看其他关于这部分内容的详细解析文档。
标准linux休眠和唤醒机制分析(二)
三、pm_test属性文件读写
int pm_test_level = TEST_NONE;
static const char * const pm_tests[__TEST_AFTER_LAST] = {
[TEST_NONE] = "none",
[TEST_CORE] = "core",
[TEST_CPUS] = "processors",
[TEST_PLATFORM] = "platform",
[TEST_DEVICES] = "devices",
[TEST_FREEZER] = "freezer",
};
// core >> processors >> platform >> devices >> freezer,控制范围示意
cat pm_test的时候最终会调用函数pm_test_show(),在终端上打印出上面数组中的字符串,当前的模式用[]表示出来。
echo devices > pm_test的时候会最终调用到函数pm_test_store()中去,该函数中设置全局变量pm_test_level的值,可以是0-5,分别代表上none
~ freezer。该全局变量会在后面的suspend和resume中被引用到。
memchr函数说明:
原型:extern void *memchr(void *buf, char ch, unsigned int count);
用法:#include
功能:从buf所指内存区域的前count个字节查找字符ch。
说明:当第一次遇到字符ch时停止查找。如果成功,返回指向字符ch的指针;否则返回NULL。
四、state属性文件
power_attr(state)宏定义了一个struct kobj_attribute结构体state_attr:
static struct kobj_attribute state_attr = {
.attr = {
.name = __stringify(state),
.mode = 0644,
},
.show = state_show,
.store = state_store,
}
kobj_attribute结构体封装了struct attribute结构体,新建属性文件是依据struct
attribute结构体。最终通过函数kobj_attr_show和kobj_attr_store回调到实际的show和store函数(kobject.c)。
state_show()函数主要是显示当前系统支持哪几种省电模式。
static ssize_t state_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf)
{
char *s = buf;
#ifdef CONFIG_SUSPEND //def
int i;
for (i = 0; i < PM_SUSPEND_MAX; i++) {
if (pm_states[i] && valid_state(i))
s += sprintf(s,"%s ", pm_states[i]);
}
#endif
#ifdef CONFIG_HIBERNATION// undef, don't support STD mode
s += sprintf(s, "%s
", "disk");
#else
if (s != buf)
/* convert the last space to a newline */
*(s-1) = '
';
#endif
return (s - buf);
}
@ kernel/include/linux/suspend.h
#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_DISK ((__force suspend_state_t) 4)
#define PM_SUSPEND_MAX ((__force suspend_state_t) 5)
@ kernel/kernel/power/suspend.c
const char *const pm_states[PM_SUSPEND_MAX] = {
#ifdef CONFIG_EARLYSUSPEND// android修改了标准linux的休眠唤醒机制,增加了eraly
suspend和late resume机制,如果是android内核,则这个宏是需要定义的。
[PM_SUSPEND_ON] = "on",
#endif
[PM_SUSPEND_STANDBY] = "standby",
[PM_SUSPEND_MEM] = "mem",
};
该函数中值得注意的地方应该是valid_state(i),这个函数是用户配置的支持省电模式的验证函数,如果没有这个验证过程,cat时候打印出来的模式则是on
standby mem,给上层用户的使用造成困扰。
那这个valid_state()函数在哪里定义的呢?一般定义于文件kernel/kernel/power/suspend.c
static struct platform_suspend_ops *suspend_ops;
void suspend_set_ops(struct platform_suspend_ops *ops) //该函数调用见后面
{
mutex_lock(&pm_mutex);
suspend_ops = ops;
mutex_unlock(&pm_mutex);
}
bool valid_state(suspend_state_t state)
{
return suspend_ops && suspend_ops->valid && suspend_ops->valid(state);
}
而实际平台的platform_suspend_ops结构体一般都是在文件arch/arm/mach-xxxx/pm.c中进行定义,对于mtk的平台是文件mtkpm.c,如下:
@ kernel/include/linux/suspend.h
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);
void (*end)(void);
void (*recover)(void);
};
经过后面的代码分析,得出了如下结论:
休眠唤醒过程依次会执行的函数是:begin,prepare,prepare_late,enter,wake,
finish, end。同颜 {MOD}的函数执行了恰好相反的工作。休眠的时候代码执行是停留在函数enter中,wake之后也是从suspend的时候停留的地方继续运行。
至于recover函数貌似只有在pm_test处于devices的模式下,才会被调用到。
@ kernel/arch/arm/mach-mt6516/mtkpm.c
static struct platform_suspend_ops mtk_pm_ops = {
.valid = mtk_pm_state_valid,
.begin = mtk_pm_begin,
.prepare = mtk_pm_prepare,
.enter = mtk_pm_enter,
.finish = mtk_pm_finish,
.end = mtk_pm_end,
};
static int mtk_pm_state_valid(suspend_state_t pm_state)
{
return pm_state == PM_SUSPEND_MEM ;
}
void mtk_pm_init(void)
{
_Chip_PM_init();
/* Register and set suspend operation */
suspend_set_ops(&mtk_pm_ops);
}
而函数mtk_pm_init()是在函数mt6516_init_irq()中调用。可以看出该平台只支持mem的省电模式。
state_store()函数:
static ssize_t state_store(struct kobject *kobj, struct kobj_attribute *attr,
const char *buf, size_t n)
{
#ifdef CONFIG_SUSPEND // set
#ifdef CONFIG_EARLYSUSPEND //对标准linux而言,这个宏不存在
suspend_state_t state = PM_SUSPEND_ON;
#else
suspend_state_t state = PM_SUSPEND_STANDBY;
#endif
const char * const *s;
#endif
char *p;
int len;
int error = -EINVAL;
p = memchr(buf, '
', n);
len = p ? p - buf : n;
/* First, check if we are requested to hibernate */
if (len == 4 && !strncmp(buf, "disk", len)) {
error = hibernate(); //如果值是disk,那么进入STD模式,该模式暂不讨论
goto Exit;
}
#ifdef CONFIG_SUSPEND // def
for (s = &pm_states[state]; state < PM_SUSPEND_MAX; s++, state++) {
if (*s && len == strlen(*s) && !strncmp(buf, *s, len))
break;
}
if (state < PM_SUSPEND_MAX && *s)
#ifdef CONFIG_EARLYSUSPEND
// android的linux内核会定义该宏,首先进入eraly suspend模式
if (state == PM_SUSPEND_ON || valid_state(state)) {
error = 0;
request_suspend_state(state);
}
#else
// 标准linux内核直接enter_state()函数
error = enter_state(state); // kernel/kernel/power/suspend.c
#endif
#endif
Exit:
return error ? error : n;
}
标准linux休眠和唤醒机制分析(三)
五、suspend和resume代码走读
下面对suspend分的几个阶段都是按照pm
test的5中模式来划分的:freezer、devices、platform、processors、core。
suspend第一阶段:freezer
int enter_state(suspend_state_t state)
{
int error;
if (!valid_state(state))
return -ENODEV;
if (!mutex_trylock(&pm_mutex))// def in kernel/kernel/power/main.c
return -EBUSY;
printk(KERN_INFO "PM: Syncing filesystems ... ");
sys_sync();
printk("done.
");//同步文件系统
pr_debug("PM: Preparing system for %s sleep
", pm_states[state]);
error = suspend_prepare();
// suspend前准备工作:Run suspend notifiers, allocate a console
and stop all processes
if (error) //如果一些准备工作失败,通常为冻结进程的时候某些进程拒绝进入冻结模式
goto Unlock; //释放锁,然后退出
if (suspend_test(TEST_FREEZER))
//检查上层下达的命令是否是:
// echo freezer > /sys/power/pm_test
// echo mem > /sys/power/state
//是的话,延时5s后,然后做一些解冻进程等工作就返回
goto Finish;
pr_debug("PM: Entering %s sleep
", pm_states[state]);
error = suspend_devices_and_enter(state);//休眠外设
Finish:
pr_debug("PM: Finishing wakeup.
");
suspend_finish();//解冻进程,发广播通知等
Unlock:
mutex_unlock(&pm_mutex);
return error;
}
static int suspend_prepare(void)
{
int error;
if (!suspend_ops || !suspend_ops->enter) // mtk_pm_enter() in mtkpm.c
return -EPERM;
pm_prepare_console(); //给suspend分配一个虚拟终端来输出信息
error = pm_notifier_call_chain(PM_SUSPEND_PREPARE);
//广播一个通知给通知链pm_chain_head,该通知链上都是用函数register_pm_notifier()注册的pm通知项(也可以用宏pm_notifier定义和注册),这里按照注册时候的定义的优先级来调用该通知链上注册的回调函数。
// PM_SUSPEND_PREPARE是事件值,表示将要进入suspend
if (error)
goto Finish;
error = usermodehelper_disable();//关闭用户态的helper进程
if (error)
goto Finish;
error = suspend_freeze_processes();
//冻结所有的进程,这里会保存所有进程当前的状态。
if (!error)
return 0;
//也许有一些进程会拒绝进入冻结状态,当有这样的进程存在的时候,会导致冻结失败,此函数就会放弃冻结进程,并且解冻刚才冻结的所有进程.
suspend_thaw_processes(); //进程解冻
usermodehelper_enable(); // enable helper process
Finish:
pm_notifier_call_chain(PM_POST_SUSPEND);//广播退出suspend的通知
pm_restore_console();
return error;
}
//
如果不支持pm debug的话,该函数直接返回0
static int suspend_test(int level)
{
#ifdef CONFIG_PM_DEBUG
if (pm_test_level == level) {
printk(KERN_INFO "suspend debug: Waiting for 5 seconds.
");
mdelay(5000);
return 1;
}
#endif /* !CONFIG_PM_DEBUG */
return 0;
}
static void suspend_finish(void)
{
suspend_thaw_processes();
usermodehelper_enable();
pm_notifier_call_chain(PM_POST_SUSPEND);
pm_restore_console();
}
同步文件系统函数sys_sync()调用后,将会执行函数suspend_prepare()来做一些进入suspend之前的准备工作:Run
suspend notifiers, allocate a console and stop all processes,disable user mode hleper process。之后将会调用suspend_test(TEST_FREEZER)来判断上层是否有下这样的命令下来:echo
freezer > /sys/power/pm_test(如果pm debug支持),如果没有下这个命令或者系统根本就不支持pm
debug,那么直接返回0。如果该测试函数返回了1,那么就不会继续往下走suspend的流程了,而是调用函数suspend_finish()来结束freezer模式的pm
test。返回0,将会进入第二阶段继续suspend的流程。
suspend第二阶段:devices
后续所有对suspend划分的阶段都包含在函数suspend_devices_and_enter(state)之中,所以这个函数是关键所在。
int suspend_devices_and_enter(suspend_state_t state)
{
int error;
if (!suspend_ops)
//一个重要的函数指针结构体,特定平台的不一样,
// kernel/arch/arm/mach-mt6516/mtkpm.c
return -ENOSYS;
if (suspend_ops->begin) {
error = suspend_ops->begin(state);
//调用特定平台实现的suspend_begin函数,
//这里没做什么实际的工作,打印了点字符串
if (error)
goto Close; //如果有错,执行特定平台的suspend_end函数
}
suspend_console();// suspend console subsystem
suspend_test_start(); // suspend devices超时警告测试
error = dpm_suspend_start(PMSG_SUSPEND);// suspend all devices,外设休眠
//关键函数之一, kernel/drivers/base/power/main.c
if (error) {
printk(KERN_ERR "PM: Some devices failed to suspend
");
goto Recover_platform; //如果外设休眠过程出现错误,将会终止suspend过程,直接跳到某标签出resume外设
}
suspend_test_finish("suspend devices");
if (suspend_test(TEST_DEVICES)) // suspend第二阶段以此为界
goto Recover_platform;
suspend_enter(state);//关键函数之一, pm test的后三种模式都在该函数中进行测试:platform、cpus、core
Resume_devices:
suspend_test_start();
dpm_resume_end(PMSG_RESUME);
suspend_test_finish("resume devices");
resume_console();
Close:
if (suspend_ops->end)
suspend_ops->end();
return error;
Recover_platform:
if (suspend_ops->recover)
suspend_ops->recover();//该函数目前的平台没有实现
goto Resume_devices;
}
@kernel/drivers/base/power/main.c
int dpm_suspend_start(pm_message_t state)
{
int error;
might_sleep();
error = dpm_prepare(state);
if (!error)
error = dpm_suspend(state); //这两个函数的架构有一些类似
return error;
这两个函数如果在执行某一个device的->prepare()
打开微信“扫一扫”,打开网页后点击屏幕右上角分享按钮