1.前言
Linux内核提供了一个Suspend: Freeze、Standby和STR(Suspend to RAM),在用户向”/sys/power/state”文件分别写入“freeze”、“standby”和“mem”,可以触发他们。
内核中,Suspend及Resume Process PM Core、Device PM freeze、CPU冻结等设备的驱动、Platform PM、CPU的多个模块,涉及到控制台开关、进程、hotplug、wakeup等处理过的点。就让我们跟着核心代码,一见识他们吧。
2.暂停功能相关的代码分发
内核中Suspend功能相关的代码包括PM core、Device PM、Platform PM等几大块,具体如下:
1)PM核心
kernel/power/main.c----提供用户空间接口(/sys/power/state)
kernel/power/suspend.c----Suspend功能的主逻辑
kernel/power/suspend_test.c----Suspend功能的测试逻辑
kernel/powersole.c----暂停过程中对分析/的处理逻辑
kernel/power/process.c----Suspend过程中对进程的处理逻辑
2)设备PM
drivers/base/power/*----具体可参考“ Linux电源管理(4)_电源管理接口”的描述。
设备驱动----具体设备驱动的位置,不再涉及。
3)平台依赖PM
include/linux/suspend.h----定义平台依赖PM相关的操作函数集
arch/xxx/mach-xxx/xxx.c或者
arch/xxx/plat-xxx/xxx.c----平台相关的电源管理操作
3.suspend&resume过程概述
下面图片对Linux suspend&resume过程做了一个概述,读者可以顺着这个阅读说明内核源参考代码。具体的流程,可以后面的代码分析。
4.代码分析
4.1 暂停入口
在用户空间执行如下操作:
回声“冻结”> /sys/power/state
回声“待机”> /sys/电源/状态
回声“内存”> /sys/power/state
会通过 sysfs 触发暂停执行,相应的处理代码如下:
静态ssize_t state_store ( struct kobject * kobj , struct kobj_attribute * attr ,
const char * buf , size_t n )
{
suspend_state_t状态;
诠释错误;
错误= pm_autosleep_lock ();
如果(错误)
返回错误;
如果( pm_autosleep_state () > PM_SUSPEND_ON ) {
错误= - EBUSY ;
出去; _
}
state = decode_state ( buf , n );
如果(状态< PM_SUSPEND_MAX )
错误= pm_suspend (状态);
否则如果(状态== PM_SUSPEND_MAX )
错误=休眠();
别的
错误= - EINVAL ;
出:
pm_autosleep_unlock ();
返回错误?错误:n ;
}
power_attr (状态);
power__store的接口定义了一个名称的属性,存储接口为住_,接口在lockautosep功能后,该接口文件为用户的状态缓存(解析、待机或mem)状态的缓存,转换成状态参数。
state参数的类型为suspend_state_t,在include\linux\suspend.h中定义,为电源管理状态在内核中的表示。具体如下:
typedef int __bitwise suspend_state_t ;
#define PM_SUSPEND_ON (( __force suspend_state_t ) 0 )
#define PM_SUSPEND_FREEZE (( __force suspend_state_t ) 1 )
#define PM_SUSPEND_STANDBY (( __force suspend_state_t ) 2 )
#define PM_SUSPEND_MEM (( __force suspend_state_t ) 3 )
#define PM_SUSPEND_MIN PM_SUSPEND_FREEZE
#define PM_SUSPEND_MAX (( __force suspend_state_t ) 4 )
可知的,不是(SUSPEND_MAX,hibernate功能),而是调用state_pm_suspend接口,如果进行状态的处理。
pm_suspend在kernel/power/suspend.c中定义,处理所有的suspend过程。
4.2 pm_suspend & enter_state
pm_suspend 的非常简单,简单的做一下参数判断,直接调用enter_state接口,如下:
int pm_suspend ( suspend_state_t状态)
{
诠释错误;
如果(状态<= PM_SUSPEND_ON ||状态>= PM_SUSPEND_MAX )
返回- EINVAL ;
错误= enter_state (状态);
如果(错误){
暂停统计。失败++;
dpm_save_failed_errno (错误);
}其他{
暂停统计。成功++;
}
返回错误;
}
enter_state代码为:
静态int enter_state ( suspend_state_t状态)
{
诠释错误;
如果(!valid_state (状态))
返回-ENODEV ;_
if (! mutex_trylock (& pm_mutex ))
返回-EBUSY ;_
如果(状态== PM_SUSPEND_FREEZE )
冻结开始();
printk ( KERN_INFO "PM: 同步文件系统 ..." );
sys_sync ();
printk (“完成。\n” );
pr_debug ( "PM: 为 %s 睡眠准备系统\n" , pm_states [ state ]);
错误= suspend_prepare (状态);
如果(错误)
转到解锁;
if ( suspend_test ( TEST_FREEZER ))
转到完成;
pr_debug ( "PM: 进入 %s sleep\n" , pm_states [ state ]);
pm_restrict_gfp_mask ();
错误= suspend_devices_and_enter (状态);
pm_restore_gfp_mask ();
完成:
pr_debug ( "PM: 完成唤醒。\n" );
暂停完成();
解锁:
mutex_unlock (& pm_mutex );
返回错误;
}
主要工作包括:
a)调用valid_state,判断该平台是否支持该电源状态。
暂停的最终目标,并且该系统功能必须有平台才能完成,内核参与提供了相关的悬挂功能(在平台_挂起_操作中),平台代码代码/mach-xxx/pm.c实现,然后由PM核心的适当时机调用。如在这些函数包含有效函数,就是用于指示(PM核心,支持哪些状态)
最后看一下valid_state的实现(去掉了多余的代码):
bool valid_state ( suspend_state_t状态)
{
如果(状态== PM_SUSPEND_FREEZE ){
返回真;
}
/*
- PM_SUSPEND_STANDBY 和 PM_SUSPEND_MEMORY 状态需要低级
- 支持且需要对低级有效
- 实现,没有有效的回调意味着没有一个是有效的。
*/
返回暂停操作&&暂停操作->有效&&暂停操作->有效(状态);
}
是冻结,独立的平台参与代码自动支持,则由我自己等待,需要调用suspend_opvalid回掉,如果由运行的平台代码判断是否支持。
b)加互斥锁,只允许一个实例处理暂停。
c)如果state是freeze,调用freeze_begin to freeze的特殊动作。我会在后面描述相关的特殊动作。同时进行分析freeze的动作,这里暂不
d)打印提示信息,同步文件系统。
e)调用包括suspend_prepare,进行suspend前的准备,主要是switch console和process&thread freeze。如果失败,则终止suspend过程。
f),调用suspend_devices_and_enter接口,然后该接口负责部分挂起和恢复的所有实际操作。前半部分,挂起控制台、挂起设备、关断、调用平台相关的suspend_ops使系统进入低状态。后半部分,在系统中被事件启动后平台,处理相关动作,调用相关的suspend_ops恢复系统、中断、恢复设备、恢复控制台。
g)最后,调用suspend_finish,恢复(或等待恢复)进程和线程,控制台。
4.3 暂停准备
suspend_prepare 的代码如下:
静态int suspend_prepare ( suspend_state_t状态)
{
诠释错误;
if ( need_suspend_ops ( state ) && (! suspend_ops || ! suspend_ops -> enter ))
返回-EPERM ;_
pm_prepare_console ();
错误= pm_notifier_call_chain ( PM_SUSPEND_PREPARE );
如果(错误)
转到完成;
错误= suspend_freeze_processes ();
如果(!错误)
返回0 ;
暂停统计。失败的冻结++;
dpm_save_failed_step ( SUSPEND_FREEZE );
完成:
pm_notifier_call_chain ( PM_POST_SUSPEND );
pm_restore_console ();
返回错误;
}
主要工作为:
a)检查是否提供了。进入拒绝,没有的话,返回错误。
b)该调用pmprepare_cons将当前的一个虚拟控制台和内核的km_g运行一下。更具体的分析,要在分析子系统的分析文章中说明。
c)调用开始的pm_notifier_call_chain,发送暂停消息(PM_SUSPEND_PREPARE),后面会详细描述。
d. 冻结用户空间进程)
e)如果freezing-of-tasks失败,调用pm_restore_console,将console切换回原来的console,并返回错误,以便能够终止suspend。
4.4 suspend_devices_and_enter
suspend_devices_and_enter 的流程复杂,代码实现如下:
int suspend_devices_and_enter ( suspend_state_t状态)
{
诠释错误;
布尔唤醒=假;
如果(需要_suspend_ops (状态)&& !suspend_ops )
返回-ENOSYS ;_
trace_machine_suspend (状态);
如果(需要暂停操作(状态)&&暂停操作->开始){
错误= suspend_ops- >开始(状态);
如果(错误)
转到关闭;
}
挂起控制台();
ftrace_stop ();
暂停测试开始();
错误= dpm_suspend_start ( PMSG_SUSPEND );
如果(错误){
printk ( KERN_ERR "PM: 一些设备挂起失败\n" );
转到恢复平台;
}
suspend_test_finish ( "挂起设备" );
if ( suspend_test ( TEST_DEVICES ))
转到恢复平台;
做{
错误= suspend_enter (状态,&唤醒);
} while (! error && ! wakeup && need_suspend_ops ( state )
&&暂停操作->暂停_再次&&暂停操作->暂停_再次());
简历设备:
暂停测试开始();
dpm_resume_end ( PMSG_RESUME );
suspend_test_finish (“恢复设备” );
ftrace_start ();
恢复控制台();
关闭:
如果(需要暂停操作(状态)&&暂停操作->结束)
暂停操作->结束();
trace_machine_suspend ( PWR_EVENT_EXIT );
返回错误;
恢复平台:
如果(需要暂停操作(状态)&&暂停操作->恢复)
暂停操作->恢复();
转到Resume_devices ;
}
a)再次检查平台代码是否需要提供以及是否提供了suspend_ops。
b)调用的开始其通知等待(有话),平台执行代码,以使可能的处理(需要的话)。
c)该调用suspend_console,挂起console。该由“kernel\printk.c”实现,主要是hold住一个lock,lock会阻止其他代码访问console。
d)调用ftrace_stop,停止ftrace。ftrace是一个很好的功能,后面再介绍。
e)调用d dpend_start,调用所有设备的-和->参考失败需要挂起_挂起功能(具体可描述设备电源管理(4)电源管理接口的),挂起设备可能的。挂起设备可能,,跳至Recover_platform,执行recover操作(suspend_ops->recover)。
f)以上都是suspend前的准备工作,此时,调用suspend_enter接口,使系统进入指定的电源状态。该接口的内容如下:
静态int suspend_enter ( suspend_state_t状态,布尔*唤醒)
{
诠释错误;
如果(需要暂停操作(状态)&&暂停操作->准备){
错误=暂停操作->准备();
如果(错误)
转到Platform_finish ;
}
错误= dpm_suspend_end ( PMSG_SUSPEND );
如果(错误){
printk ( KERN_ERR "PM: 某些设备无法关机\n" );
转到Platform_finish ;
}
如果(需要暂停操作(状态)&&暂停操作->准备延迟){
错误= suspend_ops- > prepare_late ();
如果(错误)
转到Platform_wake ;
}
if ( suspend_test ( TEST_PLATFORM ))
转到Platform_wake ;
/*
- PM_SUSPEND_FREEZE 等于
- 冻结的进程 + 挂起的设备 + 空闲的处理器。
- 因此我们应该在不久之后调用 freeze_enter()
*所有设备都被暂停。
*/
如果(状态== PM_SUSPEND_FREEZE ){
freeze_enter ();
转到Platform_wake ;
}
错误= disable_nonboot_cpus ();
如果(错误|| suspend_test (TEST_CPUS ))
转到Enable_cpus ;
arch_suspend_disable_irqs ();
BUG_ON (! irqs_disabled ());
错误= syscore_suspend ();
如果(!错误){
*唤醒= pm_wakeup_pending ();
如果(!(suspend_test (TEST_CORE )|| *唤醒)){
错误= suspend_ops- >进入(状态);
events_check_enabled = false ;
}
syscore_resume ();
}
arch_suspend_enable_irqs ();
BUG_ON ( irqs_disabled ());
Enable_cpus :
enable_nonboot_cpus ();
平台唤醒:
如果(需要暂停操作(状态)&&暂停操作->唤醒)
暂停操作->唤醒();
dpm_resume_start ( PMSG_RESUME );
平台完成:
如果(需要暂停操作(状态)&&暂停操作->完成)
暂停操作->完成();
返回错误;
}
f1)该结束后,会通过返回值反馈输入是否成功,同时通过唤醒指针,反馈调用者,唤醒事件是否发生,导致电源切换失败。
f2)调用ops的出现准备挂起(有_ops的表示,通知平台,以便让一些代码在重新进行状态切换时,做处理(需要的话)。可能会失败(平台代码拒绝),失败,需要跳到Platform_finish处,调用操作的finishsuspend,执行操作恢复。
f3)调用d暂停设备,调用所有的->suspend_late和->suspend_noir设备和pm_suspend功能(具体可参考“ Linux管理(4)_电源管理接口”的描述),暂停延迟设备和需要在关闭下暂停的需要说明,这里的noirq,通过所有的执行断线形式,而不是通过关闭的可能的方式,操作会失败。
f4)调用等待等待的prepare_一些平台指令(有代码的话),通知以使其处于关头,重新做处理(需要)。最终可能导致失败(平台代码出现失败),失败,需要跳platform_wake,调用suspend_ops的唤醒、调用,执行设备的resume_opfinish指令,执行操作。
f5)如果是suspend to freeze,执行相应的操作状态,包括暂停进程、暂停设备(参数为_SUSPEND_FREEZE)、cpu进入idle。如果有事件使CPU从idle,跳至Platform_wake,执行wake操作。
f6)调用disable_nonboot_cpus,杜绝所有的非boot cpu。同样失败,执行操作即可。
f7)调用暂停_disable_irqs,则关闭为中断。
f8)调用syscore_suspend,暂停系统核心。同样会失败,执行恢复操作。有关syscore,我会在另一篇文章中可以详细描述。
f9),如果早点成功,一下子,切换很可能吧。
f1如果一切状态顺利,调用suspend_ops的进入……,系统进行切换。
f11)暂停过程中,事件触发,系统启动,然后继续执行,并返回。恢复动作原是暂停的动作,就不再是最终的系统了。
f12)或者,由于意外,暂停,该函数也返回。
g)suspend_enter 返回,如果返回的原因不是发生的,并且不是唤醒事件的错误,而且不是唤醒事件的错误。suspend_ops_again 了。则要调用再次挂起呢。是否需要再次挂起呢?需要具体看的平台,谁。
h)继续resume操作,resume device、start ftrace、resume console、suspend_ops->end等。
i)该函数返回后,表示系统已经恢复。
4.5 挂起_完成
比较简单:
静态无效suspend_finish (无效)
{
暂停解冻进程();
pm_notifier_call_chain ( PM_POST_SUSPEND );
pm_restore_console ();
}
a)恢复所有的用户空间进程和内核线程。
b)发送暂停结束的通知。
c)将控制台切换回原来的。
五、重要知识点回顾
5.1 VT 开关
情况下,过程控制在这个问题(驱动程序\tty\一个问题)中,重新分配一个console,然后在恢复时,通常是旧的console。 VT开关是一种很容易使用的开关功能,因此控制内核提供了不同的功能:
1)提供一个接口功能pm_set_vt_switch(drivers\tty\vt\vt_ioctl.c),方便其他内核模块从整体上关闭或者开启VT switch功能。
2)VT开关切换不同的相关描述开关时,可以参考kernel\power\console.c的条件,即可以进行VT开关
a)有console driver调用pm_vt_switch_required接口,显式要求能VT switch。
b) 系统禁止在suspend的过程中暂停控制台(由kernel/printk.c中的console_suspend_enabled需要有参数控制)。有可能使用consolesus过程,以便使控制台不混乱,需要进行VT切换。
c.sole driver是否需要VT switch或没有任何旧的VT开关或没有任何 VT 开关pm调用功能。
因此,暂停过程对控制台的处理分为4 个步骤:
prepare console:负责在需要VT swich时,将当前的console切换到SUSPEND console。
int pm_prepare_console (无效)
{
如果(!pm_vt_switch ())
返回0 ;
orig_fgconsole = vt_move_to_console ( SUSPEND_CONSOLE , 1 );
如果( orig_fgconsole < 0 )
返回1 ;
orig_kmsg = vt_kmsg_redirect ( SUSPEND_CONSOLE );
返回0 ;
}
suspend console:挂起console,由kernel/printk.c实现,主要是hold住console用的互斥锁,使别人无法使用console。
resume console:对console解锁。
restore console:将console恢复为最初的console。
无效pm_restore_console (无效)
{
如果(!pm_vt_switch ())
返回;
如果(orig_fgconsole >= 0 ){
vt_move_to_console ( orig_fgconsole , 0 );
vt_kmsg_redirect ( orig_kmsg );
}
}
留着,您会问,为什么要切换 VT?先留着这个问题吧,等到分析时再回答。
5.2 冻结任务
进程的冻结功能,是suspend、hibernate等电源管理功能的组成部分,在新版本作为核心中,它被独立出来的电源管理状态(freeze)。该功能的目的,是电源管理中的状态切换过程中,确保有关进程和部分内核负责所有用户应该稳定的空间。
5.3 下午通知
PM notifier 是基于内核阻塞通知器消息功能实现的。blocking notifier提供了一种内核内部的消息通知机制,接受者通过notifier注册的,方式注册一个消息函数,关注发送者发出通知器。 ,产生的消息可以通过调用者接受调用,调用者的消息。
那暂停功能为什么使用通知程序呢?原因可能有多种,我举一个例子,这是我们日常开发中可能会遇到的。
由之前的描述,过程中,suspend device发生在进程被freeze,resume device在进程中发生。
1)如果某些设备需要在freeze进程之前暂停怎么办?
2)如果恢复操作者需要执行什么操作,或者设备需要什么时间等待,那么设备要在恢复进程之前,如果不是触发所有进程的恢复,那么是否可以触发所有进程?等待进程的数据执行能力,如何?
再来看suspend_prepare和suspend_finish中的处理:
静态intsuspend_prepare (suspend_state_t状态){ _
…
错误= pm_notifier_call_chain ( PM_SUSPEND_PREPARE );
如果(错误)
转到完成;
错误= suspend_freeze_processes ();
…
}
静态无效suspend_finish (无效)
{
暂停解冻进程();
pm_notifier_call_chain ( PM_POST_SUSPEND );
pm_restore_console ();
}
PM原来不是设备模型的框架,是在查看后门那些特殊的驱动程序,执行器可以比较设备的发送,直接PM接收暂停信息,以便自己的暂停操作。特别是恢复时可以在其他进程都工作的时候,只让暂停进程驱动的恢复。
那位读者,可以围观一下下面活生生的
驱动程序\视频\omap2\dss\core.c
5.4 device PM ops和platform PM ops的调用时机
对Linux驱动工程师来说,设备 PM ops就是电源管理(所有的,只要在适当的地方,实现适当的功能)管理和能够实现系统的电源。内核提供的这两个数据结构也很复杂,再回顾一下,如下:
结构dev_pm_ops {
int (* prepare )( 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 )(结构设备* dev );
int (* resume_early )( struct device * dev );
int (* freeze_late )(结构设备* dev );
int (* thaw_early )( struct device * dev );
int (* poweroff_late )( struct device * dev );
int (* restore_early )( struct device * dev );
int (* suspend_noirq )(结构设备* dev );
int (* resume_noirq )(结构设备* dev );
int (* freeze_noirq )(结构设备* dev );
int (* thaw_noirq )(结构设备* 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 );
};
结构平台_suspend_ops {
int (*有效)( suspend_state_t state );
int (* begin )( suspend_state_t state );
int (*准备)(无效);
int (* prepare_late )( void );
int (* enter )( suspend_state_t state );
无效(*唤醒)(无效);
无效(*完成)(无效);
bool (* suspend_again )( void );
无效(*结束)(无效);
无效(*恢复)(无效);
};
这个内核的注释相当详细,到底应该实现什么错误?但这些我们的应用场景是什么方法? 。可以总结一下在电源状态切换时,除了这个调用时机,从帮助。
5.5 暂停过程的同步和PM wakeup
最重要的事情,如果过程中,有过程的暂停或特殊处理?在内核都不能的内部,这也很好地解决了这个问题。
因此,在同步的时间里,暂停大部分的电视节目,(因为这个问题的主要作用是影响时代的热点)。但新的推出,事件变了,Android用暂停作日常的追踪(这个模块就在接下来的即将结束的过程中完成了?分析,这里就不再继续了。
原作者:wowo 蜗窝科技