标准linux休眠和唤醒机制分析.docx
《标准linux休眠和唤醒机制分析.docx》由会员分享,可在线阅读,更多相关《标准linux休眠和唤醒机制分析.docx(31页珍藏版)》请在冰豆网上搜索。
标准linux休眠和唤醒机制分析
标准linux休眠和唤醒机制分析
标准linux休眠和唤醒机制分析
(一)
说明:
1.Basedonlinux2.6.32,onlyformem(SDR)
2.有兴趣请先参考阅读:
电源管理方案APM和ACPI比较.doc
Linux系统的休眠与唤醒简介.doc
3.本文先研究标准linux的休眠与唤醒,android对这部分的增改在另一篇文章中讨论
4.基于手上的一个项目来讨论,这里只讨论共性的地方
虽然linux支持三种省电模式:
standby、suspendtoram、suspendtodisk,但是在使用电池供电的手持设备上,几乎所有的方案都只支持STR模式(也有同时支持standby模式的),因为STD模式需要有交换分区的支持,但是像手机类的嵌入式设备,他们普遍使用nand来存储数据和代码,而且其上使用的文件系统yaffs一般都没有划分交换分区,所以手机类设备上的linux都没有支持STD省电模式。
一、项目power相关的配置
目前我手上的项目的linux电源管理方案配置如下,.config文件的截图,当然也可以通过makemenuconfig使用图形化来配置:
#
#CPUPowerManagement
#
#CONFIG_CPU_IDLEisnotset
#
#Powermanagementoptions
#
CONFIG_PM=y
#CONFIG_PM_DEBUGisnotset
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_CONTROLisnotset
#CONFIG_CONSOLE_EARLYSUSPENDisnotset
CONFIG_FB_EARLYSUSPEND=y
#CONFIG_APM_EMULATIONisnotset
#CONFIG_PM_RUNTIMEisnotset
CONFIG_ARCH_SUSPEND_POSSIBLE=y
CONFIG_NET=y
上面的配置对应下图中的下半部分图形化配置。
。
。
,看来是直接在Kconfig文件中删除了配置STD模式的选项。
使用上面的配置编译出来的系统,跑起来之后,进入sys目录可以看到相关的接口:
#pwd
/sys/power
#ls
statewake_lockwake_unlockwait_for_fb_sleepwait_for_fb_wake
#catstate
mem
如果配置了宏CONFIG_PM_DEBUG,那么在power目录下会多出一个pm_test文件,catpm_test后,列出的测试选项有:
[none]coreprocessorsplatformdevicesfreezer。
关于这个test模式的使用,可以参考kernel文档:
/kernel/documentation/power/Basic-pm-debugging.txt
这个文档我也有详细的阅读和分析。
二、sys/power和相关属性文件创建
系统bootup时候在sys下新建power和相关属性文件,相关源码位置:
kernel/kernel/power/main.c
staticint__initpm_init(void)
{
interror=pm_start_workqueue();//CONFIG_PM_RUNTIMEnotset,sothisfunisnull
if(error)
returnerror;
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;
returnsysfs_create_group(power_kobj,&attr_group);
//建立一组属性文件,可以在power下建立一个子目录来存放这些属性文件,//不过需要在结构体attr_group中指定name,否则直接将这些属性文件放在//power_kobj对应的目录下。
}
core_initcall(pm_init);//看的出来,该函数是很早就被调用,initcall等级为1
staticstructattribute_groupattr_group={
.attrs=g,
};
structattribute_group{
constchar*name;
mode_t(*is_visible)(structkobject*,
structattribute*,int);
structattribute**attrs;
};
//属性文件都是以最基本得属性结构structattribute来建立的
staticstructattribute*g[]={
&state_attr.attr,
#ifdefCONFIG_PM_TRACE//notset
&pm_trace_attr.attr,
#endif
#ifdefined(CONFIG_PM_SLEEP)&&defined(CONFIG_PM_DEBUG)//notset
&pm_test_attr.attr,
#endif
#ifdefCONFIG_USER_WAKELOCK//set
&wake_lock_attr.attr,
&wake_unlock_attr.attr,
#endif
NULL,
};
#ifdefCONFIG_PM_SLEEP
#ifdefCONFIG_PM_DEBUG
power_attr(pm_test);
#endif
#endif
power_attr(state);
#ifdefCONFIG_PM_TRACE
power_attr(pm_trace);
#endif
#ifdefCONFIG_USER_WAKELOCK
power_attr(wake_lock);
power_attr(wake_unlock);
#endif
#definepower_attr(_name)\
staticstructkobj_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属性文件读写
intpm_test_level=TEST_NONE;
staticconstchar*constpm_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,控制范围示意
catpm_test的时候最终会调用函数pm_test_show(),在终端上打印出上面数组中的字符串,当前的模式用[]表示出来。
echodevices>pm_test的时候会最终调用到函数pm_test_store()中去,该函数中设置全局变量pm_test_level的值,可以是0-5,分别代表上none~freezer。
该全局变量会在后面的suspend和resume中被引用到。
memchr函数说明:
原型:
externvoid*memchr(void*buf,charch,unsignedintcount);
用法:
#include
功能:
从buf所指内存区域的前count个字节查找字符ch。
说明:
当第一次遇到字符ch时停止查找。
如果成功,返回指向字符ch的指针;否则返回NULL。
四、state属性文件
power_attr(state)宏定义了一个structkobj_attribute结构体state_attr:
staticstructkobj_attributestate_attr={
.attr={
.name=__stringify(state),
.mode=0644,
},
.show=state_show,
.store=state_store,
}
kobj_attribute结构体封装了structattribute结构体,新建属性文件是依据structattribute结构体。
最终通过函数kobj_attr_show和kobj_attr_store回调到实际的show和store函数(kobject.c)。
state_show()函数主要是显示当前系统支持哪几种省电模式。
staticssize_tstate_show(structkobject*kobj,structkobj_attribute*attr,char*buf)
{
char*s=buf;
#ifdefCONFIG_SUSPEND//def
inti;
for(i=0;iif(pm_states[i]&&valid_state(i))
s+=sprintf(s,"%s",pm_states[i]);
}
#endif
#ifdefCONFIG_HIBERNATION//undef,don'tsupportSTDmode
s+=sprintf(s,"%s\n","disk");
#else
if(s!
=buf)
/*convertthelastspacetoanewline*/
*(s-1)='\n';
#endif
return(s-buf);
}
@kernel/include/linux/suspend.h
#definePM_SUSPEND_ON((__forcesuspend_state_t)0)
#definePM_SUSPEND_STANDBY((__forcesuspend_state_t)1)
#definePM_SUSPEND_MEM((__forcesuspend_state_t)3)
#definePM_SUSPEND_DISK((__forcesuspend_state_t)4)
#definePM_SUSPEND_MAX((__forcesuspend_state_t)5)
@kernel/kernel/power/suspend.c
constchar*constpm_states[PM_SUSPEND_MAX]={
#ifdefCONFIG_EARLYSUSPEND//android修改了标准linux的休眠唤醒机制,增加了eralysuspend和lateresume机制,如果是android内核,则这个宏是需要定义的。
[PM_SUSPEND_ON]="on",
#endif
[PM_SUSPEND_STANDBY]="standby",
[PM_SUSPEND_MEM]="mem",
};
该函数中值得注意的地方应该是valid_state(i),这个函数是用户配置的支持省电模式的验证函数,如果没有这个验证过程,cat时候打印出来的模式则是onstandbymem,给上层用户的使用造成困扰。
那这个valid_state()函数在哪里定义的呢?
一般定义于文件kernel/kernel/power/suspend.c
staticstructplatform_suspend_ops*suspend_ops;
voidsuspend_set_ops(structplatform_suspend_ops*ops)//该函数调用见后面
{
mutex_lock(&pm_mutex);
suspend_ops=ops;
mutex_unlock(&pm_mutex);
}
boolvalid_state(suspend_state_tstate)
{
returnsuspend_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
structplatform_suspend_ops{
int(*valid)(suspend_state_tstate);
int(*begin)(suspend_state_tstate);
int(*prepare)(void);
int(*prepare_late)(void);
int(*enter)(suspend_state_tstate);
void(*wake)(void);
void(*finish)(void);
void(*end)(void);
void(*recover)(void);
};
经过后面的代码分析,得出了如下结论:
休眠唤醒过程依次会执行的函数是:
begin,prepare,prepare_late,enter,wake,finish,end。
同颜色的函数执行了恰好相反的工作。
休眠的时候代码执行是停留在函数enter中,wake之后也是从suspend的时候停留的地方继续运行。
至于recover函数貌似只有在pm_test处于devices的模式下,才会被调用到。
@kernel/arch/arm/mach-mt6516/mtkpm.c
staticstructplatform_suspend_opsmtk_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,
};
staticintmtk_pm_state_valid(suspend_state_tpm_state)
{
returnpm_state==PM_SUSPEND_MEM;
}
voidmtk_pm_init(void)
{
_Chip_PM_init();
/*Registerandsetsuspendoperation*/
suspend_set_ops(&mtk_pm_ops);
}
而函数mtk_pm_init()是在函数mt6516_init_irq()中调用。
可以看出该平台只支持mem的省电模式。
state_store()函数:
staticssize_tstate_store(structkobject*kobj,structkobj_attribute*attr,
constchar*buf,size_tn)
{
#ifdefCONFIG_SUSPEND//set
#ifdefCONFIG_EARLYSUSPEND//对标准linux而言,这个宏不存在
suspend_state_tstate=PM_SUSPEND_ON;
#else
suspend_state_tstate=PM_SUSPEND_STANDBY;
#endif
constchar*const*s;
#endif
char*p;
intlen;
interror=-EINVAL;
p=memchr(buf,'\n',n);
len=p?
p-buf:
n;
/*First,checkifwearerequestedtohibernate*/
if(len==4&&!
strncmp(buf,"disk",len)){
error=hibernate();//如果值是disk,那么进入STD模式,该模式暂不讨论
gotoExit;
}
#ifdefCONFIG_SUSPEND//def
for(s=&pm_states[state];stateif(*s&&len==strlen(*s)&&!
strncmp(buf,*s,len))
break;
}
if(state#ifdefCONFIG_EARLYSUSPEND
//android的linux内核会定义该宏,首先进入eralysuspend模式
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:
returnerror?
error:
n;
}
标准linux休眠和唤醒机制分析(三)
五、suspend和resume代码走读
下面对suspend分的几个阶段都是按照pmtest的5中模式来划分的:
freezer、devices、platform、processors、core。
suspend第一阶段:
freezer
intenter_state(suspend_state_tstate)
{
interror;
if(!
valid_state(state))
return-ENODEV;
if(!
mutex_trylock(&pm_mutex))//definkernel/kernel/power/main.c
return-EBUSY;
printk(KERN_INFO"PM:
Syncingfilesystems...");
sys_sync();
printk("done.\n");//同步文件系统
pr_debug("PM:
Preparingsystemfor%ssleep\n",pm_states[state]);
error=suspend_prepare();
//suspend前准备工作:
Runsuspendnotifiers,allocateaconsoleandstopallprocesses
if(error)//如果一些准备工作失败,通常为冻结进程的时候某些进程拒绝进入冻结模式
gotoUnlock;//释放锁,然后退出
if(suspend_test(TEST_FREEZER))
//检查上层下达的命令是否是:
//echofreezer>/sys/power/pm_test
//echomem>/sys/power/state
//是的话,延时5s后,然后做一些解冻进程等工作就返回
gotoFinish;
pr_debug("PM:
Entering%ssleep\n",pm_states[state]);
error=suspend_devices_and_enter(state);//休眠外设
Finish:
pr_debug("PM:
Finishingwakeup.\n");
suspend_finish();//解冻进程,发广播通知等
Unlock:
mutex_unlock(&pm_mutex);
returnerror;
}
staticintsuspend_prepare(void)
{
interror;
if(!
suspend_ops||!
suspend_ops->enter)//mtk_pm_enter()inmtkpm.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)
gotoFinish;
error=usermodehelper_disable();//关闭用户态的helper进程
if(error)
gotoFinish;
error=suspend_freeze_processes();
//冻结所有的进程,这里会保存所有进程当前的状态。
if(!
erro