Linux电源管理(6)_Generic PM之Suspend功能

本文深入探讨了Linux内核中的Suspend机制,包括Freeze、Standby和STR三种模式,详细介绍了Suspend过程中的关键步骤和技术细节,如PM Core、Device PM、Platform PM等模块的作用,以及VT switch、Freezing of tasks等重要概念。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1. 前言

Linux内核提供了三种Suspend: Freeze、Standby和STR(Suspend to RAM),在用户空间向”/sys/power/state”文件分别写入”freeze”、”standby”和”mem”,即可触发它们。

内核中,Suspend及Resume过程涉及到PM Core、Device PM、各个设备的驱动、Platform dependent PM、CPU control等多个模块,涉及了console switch、process freeze、CPU hotplug、wakeup处理等过个知识点。就让我们跟着内核代码,一一见识它们吧。

2. Suspend功能有关的代码分布

内核中Suspend功能有关的代码包括PM core、Device PM、Platform PM等几大块,具体如下:

1)PM Core

kernel/power/main.c----提供用户空间接口(/sys/power/state)

kernel/power/suspend.c----Suspend功能的主逻辑

kernel/power/suspend_test.c----Suspend功能的测试逻辑

kernel/power/console.c----Suspend过程中对控制台的处理逻辑

kernel/power/process.c----Suspend过程中对进程的处理逻辑

2)Device PM

drivers/base/power/*----具体可参考“Linux电源管理(4)_Power Management Interface”的描述。

设备驱动----具体设备驱动的位置,不再涉及。

3)Platform dependent PM

include/linux/suspend.h----定义platform dependent PM有关的操作函数集

arch/xxx/mach-xxx/xxx.c或者

arch/xxx/plat-xxx/xxx.c----平台相关的电源管理操作

3. suspend&resume过程概述

下面图片对Linux suspend&resume过程做了一个概述,读者可以顺着这个流程阅读内核源代码。具体的说明,可以参考后面的代码分析。

suspend_flow

4. 代码分析

4.1 suspend入口

在用户空间执行如下操作:

echo "freeze" > /sys/power/state

echo "standby" > /sys/power/state

echo "mem" > /sys/power/state

会通过sysfs触发suspend的执行,相应的处理代码如下:

 
  1. staticssize_t state_store(struct kobject *kobj,struct kobj_attribute *attr,
  2. constchar*buf,size_t n)
  3. {
  4. suspend_state_t state;
  5. int error;
  6.  
  7. error = pm_autosleep_lock();
  8. if(error)
  9. return error;
  10.  
  11. if(pm_autosleep_state()> PM_SUSPEND_ON){
  12. error =-EBUSY;
  13. goto out;
  14. }
  15.  
  16. state = decode_state(buf, n);
  17. if(state < PM_SUSPEND_MAX)
  18. error = pm_suspend(state);
  19. elseif(state == PM_SUSPEND_MAX)
  20. error = hibernate();
  21. else
  22. error =-EINVAL;
  23.  
  24. out:
  25. pm_autosleep_unlock();
  26. return error ? error : n;
  27. }
  28.  
  29. power_attr(state);

power_attr定义了一个名称为state的attribute文件,该文件的store接口为state_store,该接口在lock住autosleep功能后,解析用户传入的buffer(freeze、standby or mem),转换成state参数。

state参数的类型为suspend_state_t,在include\linux\suspend.h中定义,为电源管理状态在内核中的表示。具体如下:
 
  1. typedefint __bitwise suspend_state_t;
  2.  
  3. #define PM_SUSPEND_ON ((__force suspend_state_t)0)
  4. #define PM_SUSPEND_FREEZE ((__force suspend_state_t)1)
  5. #define PM_SUSPEND_STANDBY ((__force suspend_state_t)2)
  6. #define PM_SUSPEND_MEM ((__force suspend_state_t)3)
  7. #define PM_SUSPEND_MIN PM_SUSPEND_FREEZE
  8. #define PM_SUSPEND_MAX ((__force suspend_state_t)4)

根据state的值,如果不是(PM_SUSPEND_MAX,对应hibernate功能),则调用pm_suspend接口,进行后续的处理。 

pm_suspend在kernel/power/suspend.c定义,处理所有的suspend过程。  

4.2 pm_suspend & enter_state

pm_suspend的实现非常简单,简单的做一下参数合法性判断,直接调用enter_state接口,如下:

 
  1. int pm_suspend(suspend_state_t state)
  2. {
  3. int error;
  4.  
  5. if(state <= PM_SUSPEND_ON || state >= PM_SUSPEND_MAX)
  6. return-EINVAL;
  7.  
  8. error = enter_state(state);
  9. if(error){
  10. suspend_stats.fail++;
  11. dpm_save_failed_errno(error);
  12. }else{
  13. suspend_stats.success++;
  14. }
  15. return error;
  16. }

enter_state代码为:

 
  1. staticint enter_state(suspend_state_t state)
  2. {
  3. int error;
  4.  
  5. if(!valid_state(state))
  6. return-ENODEV;
  7.  
  8. if(!mutex_trylock(&pm_mutex))
  9. return-EBUSY;
  10.  
  11. if(state == PM_SUSPEND_FREEZE)
  12. freeze_begin();
  13.  
  14. printk(KERN_INFO "PM: Syncing filesystems ... ");
  15. sys_sync();
  16. printk("done.\n");
  17.  
  18. pr_debug("PM: Preparing system for %s sleep\n", pm_states[state]);
  19. error = suspend_prepare(state);
  20. if(error)
  21. gotoUnlock;
  22.  
  23. if(suspend_test(TEST_FREEZER))
  24. gotoFinish;
  25.  
  26. pr_debug("PM: Entering %s sleep\n", pm_states[state]);
  27. pm_restrict_gfp_mask();
  28. error = suspend_devices_and_enter(state);
  29. pm_restore_gfp_mask();
  30.  
  31. Finish:
  32. pr_debug("PM: Finishing wakeup.\n");
  33. suspend_finish();
  34. Unlock:
  35. mutex_unlock(&pm_mutex);
  36. return error;
  37. }

主要工作包括:

a)调用valid_state,判断该平台是否支持该电源状态。

suspend的最终目的,是让系统进入可恢复的挂起状态,而该功能必须有平台相关代码的参与才能完成,因此内核PM Core就提供了一系列的回调函数(封装在platform_suspend_ops中),让平台代码(如arch/arm/mach-xxx/pm.c)实现,然后由PM Core在合适的时机调用。这些回调函数包含一个valid函数,就是用来告知PM Core,支持哪些state。

最后看一下valid_state的实现(删除了无关代码):
 
  1. bool valid_state(suspend_state_t state)
  2. {
  3. if(state == PM_SUSPEND_FREEZE){
  4. returntrue;
  5. }
  6. /*
  7. * PM_SUSPEND_STANDBY and PM_SUSPEND_MEMORY states need lowlevel
  8. * support and need to be valid to the lowlevel
  9. * implementation, no valid callback implies that none are valid.
  10. */
  11. return suspend_ops && suspend_ops->valid && suspend_ops->valid(state);
  12. }

如果是freeze,无需平台代码参与即可支持,直接返回true。对于standby和mem,则需要调用suspend_ops的valid回掉,由底层平台代码判断是否支持。 

b)加互斥锁,只允许一个实例处理suspend。

c)如果state是freeze,调用freeze_begin,进行suspend to freeze相关的特殊动作。我会在后面统一分析freeze的特殊动作,这里暂不描述。

d)打印提示信息,同步文件系统。

e)调用suspend_prepare,进行suspend前的准备,主要包括switch console和process&thread freezing。如果失败,则终止suspend过程。

f)然后,调用suspend_devices_and_enter接口,该接口负责suspend和resume的所有实际动作。前半部分,suspend console、suspend device、关中断、调用平台相关的suspend_ops使系统进入低功耗状态。后半部分,在系统被事件唤醒后,处理相关动作,调用平台相关的suspend_ops恢复系统、开中断、resume device、resume console。

g)最后,调用suspend_finish,恢复(或等待恢复)process&thread,还原console。  

4.3 suspend_prepare

suspend_prepare的代码如下:
 
  1. staticint suspend_prepare(suspend_state_t state)
  2. {
  3. int error;
  4.  
  5. if(need_suspend_ops(state)&&(!suspend_ops ||!suspend_ops->enter))
  6. return-EPERM;
  7.  
  8. pm_prepare_console();
  9.  
  10. error = pm_notifier_call_chain(PM_SUSPEND_PREPARE);
  11. if(error)
  12. gotoFinish;
  13.  
  14. error = suspend_freeze_processes();
  15. if(!error)
  16. return0;
  17.  
  18. suspend_stats.failed_freeze++;
  19. dpm_save_failed_step(SUSPEND_FREEZE);
  20. Finish:
  21. pm_notifier_call_chain(PM_POST_SUSPEND);
  22. pm_restore_console();
  23. return error;
  24. }

主要工作为:

a)检查suspend_ops是否提供了.enter回调,没有的话,返回错误。

b)调用pm_prepare_console,将当前console切换到一个虚拟console并重定向内核的kmsg(需要的话)。该功能称作VT switch,后面我会在稍微详细的介绍一下,但Linux控制台子系统是相当复杂的,更具体的分析,要在控制台子系统的分析文章中说明。

c)调用pm_notifier_call_chain,发送suspend开始的消息(PM_SUSPEND_PREPARE),后面会详细描述。

d)调用suspend_freeze_processes,freeze用户空间进程和一些内核线程。该功能称作freezing-of-tasks,我会专门用一篇文章去分析它。本文就不再详细说明了。

e)如果freezing-of-tasks失败,调用pm_restore_console,将console切换回原来的console,并返回错误,以便能终止suspend。  

4.4 suspend_devices_and_enter

suspend_devices_and_enter的过程较为复杂,代码实现如下:
 
  1. int suspend_devices_and_enter(suspend_state_t state)
  2. {
  3. int error;
  4. bool wakeup =false;
  5.  
  6. if(need_suspend_ops(state)&&!suspend_ops)
  7. return-ENOSYS;
  8.  
  9. trace_machine_suspend(state);
  10. if(need_suspend_ops(state)&& suspend_ops->begin){
  11. error = suspend_ops->begin(state);
  12. if(error)
  13. gotoClose;
  14. }
  15. suspend_console();
  16. ftrace_stop();
  17. suspend_test_start();
  18. error = dpm_suspend_start(PMSG_SUSPEND);
  19. if(error){
  20. printk(KERN_ERR "PM: Some devices failed to suspend\n");
  21. gotoRecover_platform;
  22. }
  23. suspend_test_finish("suspend devices");
  24. if(suspend_test(TEST_DEVICES))
  25. gotoRecover_platform;
  26.  
  27. do{
  28. error = suspend_enter(state,&wakeup);
  29. }while(!error &&!wakeup && need_suspend_ops(state)
  30. && suspend_ops->suspend_again && suspend_ops->suspend_again());
  31.  
  32. Resume_devices:
  33. suspend_test_start();
  34. dpm_resume_end(PMSG_RESUME);
  35. suspend_test_finish("resume devices");
  36. ftrace_start();
  37. resume_console();
  38. Close:
  39. if(need_suspend_ops(state)&& suspend_ops->end)
  40. suspend_ops->end();
  41. trace_machine_suspend(PWR_EVENT_EXIT);
  42. return error;
  43.  
  44. Recover_platform:
  45. if(need_suspend_ops(state)&& suspend_ops->recover)
  46. suspend_ops->recover();
  47. gotoResume_devices;
  48. }

a)再次检查平台代码是否需要提供以及是否提供了suspend_ops。

b)调用suspend_ops的begin回调(有的话),通知平台代码,以便让其作相应的处理(需要的话)。可能失败,需要跳至Close处执行恢复操作(suspend_ops->end)。

c)调用suspend_console,挂起console。该接口由"kernel\printk.c"实现,主要是hold住一个lock,该lock会阻止其它代码访问console。

d)调用ftrace_stop,停止ftrace功能。ftrace是一个很有意思的功能,后面再介绍。

e)调用dpm_suspend_start,调用所有设备的->prepare和->suspend回调函数(具体可参考“Linux电源管理(4)_Power Management Interface”的描述),suspend需要正常suspend的设备。suspend device可能失败,需要跳至 Recover_platform,执行recover操作(suspend_ops->recover)。

f)以上都是suspend前的准备工作,此时,调用suspend_enter接口,使系统进入指定的电源状态。该接口的内容如下:
 
  1. staticint suspend_enter(suspend_state_t state,bool*wakeup)
  2. {
  3. int error;
  4.  
  5. if(need_suspend_ops(state)&& suspend_ops->prepare){
  6. error = suspend_ops->prepare();
  7. if(error)
  8. gotoPlatform_finish;
  9. }
  10.  
  11. error = dpm_suspend_end(PMSG_SUSPEND);
  12. if(error){
  13. printk(KERN_ERR "PM: Some devices failed to power down\n");
  14. gotoPlatform_finish;
  15. }
  16.  
  17. if(need_suspend_ops(state)&& suspend_ops->prepare_late){
  18. error = suspend_ops->prepare_late();
  19. if(error)
  20. gotoPlatform_wake;
  21. }
  22.  
  23. if(suspend_test(TEST_PLATFORM))
  24. gotoPlatform_wake;
  25.  
  26. /*
  27. * PM_SUSPEND_FREEZE equals
  28. * frozen processes + suspended devices + idle processors.
  29. * Thus we should invoke freeze_enter() soon after
  30. * all the devices are suspended.
  31. */
  32. if(state == PM_SUSPEND_FREEZE){
  33. freeze_enter();
  34. gotoPlatform_wake;
  35. }
  36.  
  37. error = disable_nonboot_cpus();
  38. if(error || suspend_test(TEST_CPUS))
  39. gotoEnable_cpus;
  40.  
  41. arch_suspend_disable_irqs();
  42. BUG_ON(!irqs_disabled());
  43.  
  44. error = syscore_suspend();
  45. if(!error){
  46. *wakeup = pm_wakeup_pending();
  47. if(!(suspend_test(TEST_CORE)||*wakeup)){
  48. error = suspend_ops->enter(state);
  49. events_check_enabled =false;
  50. }
  51. syscore_resume();
  52. }
  53.  
  54. arch_suspend_enable_irqs();
  55. BUG_ON(irqs_disabled());
  56.  
  57. Enable_cpus:
  58. enable_nonboot_cpus();
  59.  
  60. Platform_wake:
  61. if(need_suspend_ops(state)&& suspend_ops->wake)
  62. suspend_ops->wake();
  63.  
  64. dpm_resume_start(PMSG_RESUME);
  65.  
  66. Platform_finish:
  67. if(need_suspend_ops(state)&& suspend_ops->finish)
  68. suspend_ops->finish();
  69.  
  70. return error;
  71. }

        f1)该接口处理完后,会通过返回值告知是否enter成功,同时通过wakeup指针,告知调用者,是否有wakeup事件发生,导致电源状态切换失败。

        f2)调用suspend_ops的prepare回调(有的话),通知平台代码,以便让其在即将进行状态切换之时,再做一些处理(需要的话)。该回调可能失败(平台代码出现意外),失败的话,需要跳至Platform_finish处,调用suspend_ops的finish回调,执行恢复操作。

        f3)调用dpm_suspend_end,调用所有设备的->suspend_late和->suspend_noirq回调函数(具体可参考“Linux电源管理(4)_Power Management Interface”的描述),suspend late suspend设备和需要在关中断下suspend的设备。需要说明的是,这里的noirq,是通过禁止所有的中断线的形式,而不是通过关全局中断的方式。同样,该操作可能会失败,失败的话,跳至Platform_finish处,执行恢复动作。

        f4)调用suspend_ops的prepare_late回调(有的话),通知平台代码,以便让其在最后关头,再做一些处理(需要的话)。该回调可能失败(平台代码出现意外),失败的话,需要跳至Platform_wake处,调用suspend_ops的wake回调,执行device的resume、调用suspend_ops的finish回调,执行恢复操作。

        f5)如果是suspend to freeze,执行相应的操作,包括冻结进程、suspended devices(参数为PM_SUSPEND_FREEZE)、cpu进入idle。如果有任何事件使CPU从idle状态退出,跳至Platform_wake处,执行wake操作。

        f6)调用disable_nonboot_cpus,禁止所有的非boot cpu。也会失败,执行恢复操作即可。

        f7)调用arch_suspend_disable_irqs,关全局中断。如果无法关闭,则为bug。

        f8)调用syscore_suspend,suspend system core。同样会失败,执行恢复操作即可。有关syscore,我会在另一篇文章中详细描述。

        f9)如果很幸运,以上操作都成功了,那么,切换吧。不过,别高兴太早,还得调用pm_wakeup_pending检查一下,这段时间内,是否有唤醒事件发生,如果有就要终止suspend。

        f10)如果一切顺利,调用suspend_ops的enter回调,进行状态切换。这时,系统应该已经suspend了……

        f11)suspend过程中,唤醒事件发生,系统唤醒,该函数接着执行resume动作,并最终返回。resume动作基本上是suspend的反动作,就不再继续分析了。

        f12)或者,由于意外,suspend终止,该函数也会返回。

g)suspend_enter返回,如果返回原因不是发生错误,且不是wakeup事件。则调用suspend_ops的suspend_again回调,检查是否需要再次suspend。再什么情况下要再次suspend呢?需要看具体的平台了,谁知道呢。

h)继续resume操作,resume device、start ftrace、resume console、suspend_ops->end等等。

i)该函数返回后,表示系统已经resume。 

4.5 suspend_finish

比较简单:

 
  1. staticvoid suspend_finish(void)
  2. {
  3. suspend_thaw_processes();
  4. pm_notifier_call_chain(PM_POST_SUSPEND);
  5. pm_restore_console();
  6. }

a)恢复所有的用户空间进程和内核线程。

b)发送suspend结束的通知。

c)将console切换回原来的。   

 

5. 重要知识点回顾

5.1 VT switch

通常情况下,系统控制台模块(drivers\tty\vt\)会在suspend的过程中,重新分配一个console,并将控制台切换到该console上。然后在resume时,切换回旧的console。这就是VT switch功能。VT switch是很耗时的,因此内核提供了一些机制,控制是否使用这个功能:

1)提供一个接口函数pm_set_vt_switch(drivers\tty\vt\vt_ioctl.c),方便其它内核模块从整体上关闭或者开启VT switch功能。

2)VT switch全局开关处于开启状态时,满足如下的一种条件(可参考kernel\power\console.c相关的描述),即会使能VT switch

        a)有console driver调用pm_vt_switch_required接口,显式的要求使能VT switch。PM core的console模块会把这些信息记录在一个名称为pm_vt_switch_list的链表中。

       b)系统禁止在suspend的过程中suspend console(由kernel/printk.c中的console_suspend_enabled变量控制)。很有可能需要使用console查看suspend过程,此时为了使console不混乱,有必要进行VT switch。

       c)没有任何console driver关心是否需要VT switch,换句话说没有任何driver调用pm_vt_switch_required接口要求使能或禁止VT switch功能。此时会按照旧的习惯,进行VT switch。  

 

因此,suspend过程对console的处理分为4步:

prepare console:负责在需要 VT swich 时,将当前 console 切换到 SUSPEND console
 
  1. int pm_prepare_console(void)
  2. {
  3. if(!pm_vt_switch())
  4. return0;
  5.  
  6. orig_fgconsole = vt_move_to_console(SUSPEND_CONSOLE,1);
  7. if(orig_fgconsole <0)
  8. return1;
  9.  
  10. orig_kmsg = vt_kmsg_redirect(SUSPEND_CONSOLE);
  11. return0;
  12. }

suspend console:挂起console,由kernel/printk.c实现,主要是hold住console用的互斥锁,使他人无法使用console。  

resume console:对console解锁。

restore console:将console恢复为初始的console。

 
  1. void pm_restore_console(void)
  2. {
  3. if(!pm_vt_switch())
  4. return;
  5.  
  6. if(orig_fgconsole >=0){
  7. vt_move_to_console(orig_fgconsole,0);
  8. vt_kmsg_redirect(orig_kmsg);
  9. }
  10. }

也许,您会问,why VT switch?先留着这个疑问吧,等到分析控制台时再回答。

5.2 freezing of task

进程的freezing功能,是suspend、hibernate等电源管理功能的组成部分,在新版本内核中,它被独立出来,作为一个独立的电源管理状态(freeze)。该功能的目的,是在电源管理的状态切换过程中,确保所有用户空间进程和部分内核线程处于一个稳定的状态。有关该功能的具体描述,请参考wowotech后续的文章。 

5.3 PM notifier

PM notifier是基于内核blocking notifier功能实现的。blocking notifier提供了一种kernel内部的消息通知机制,消息接受者通过notifier注册的方式,注册一个回调函数,关注消息发送者发出的notifier。当消息产生时,消息产生者通过调用回调函数的形式,通知消息接受者。这种调用,是可以被阻塞的,因此称作blocking notifier。

那suspend功能为什么使用notifier呢?原因可能有多种,这里我举一个例子,这是我们日常开发中可能会遇到的。

由之前的描述可知,suspend过程中,suspend device发生在进程被freeze之后,resume device发生在进程被恢复之前。那么:

1)如果有些设备就需要在freeze进程之前suspend怎么办?

2)如果有些设备的resume动作需要较多延时,或者要等待什么事情发生,那么如果它的resume动作发生在进程恢复之前,岂不是要阻止所有进程的恢复?更甚者,如果该设备要等待某个进程的数据才能resume,怎么办?

再来看 suspend_prepare suspend_finish 中的处理:
 
  1. staticint suspend_prepare(suspend_state_t state){
  2. error = pm_notifier_call_chain(PM_SUSPEND_PREPARE);
  3. if(error)
  4. gotoFinish;
  5.  
  6. error = suspend_freeze_processes();
  7. }
  8.  
  9. staticvoid suspend_finish(void)
  10. {
  11. suspend_thaw_processes();
  12. pm_notifier_call_chain(PM_POST_SUSPEND);
  13. pm_restore_console();
  14. }

原来PM notifier是在设备模型的框架外,开了一个后门,那些比较特殊的driver,可以绕过设备模型,直接接收PM发送的suspend信息,以便执行自身的suspend动作。特别是resume时,可以在其它进程都正好工作的时候,只让suspend进程等待driver的resume。

感兴趣的读者,可以围观一下下面这个活生生的例子(顺便提一下,好的设计是不应该有例外的):

drivers\video\omap2\dss\core.c  

5.4 device PM ops 和platform PM ops的调用时机

Linux 驱动工程师来说, device PM ops platform PM ops 就是电源管理( suspend )的全部,只要在合适的地方,实现合适的回调函数,即可实现系统的电源管理。但现实太复杂了,以至于 kernel 提供的这两个数据结构也很复杂,再回忆一下,如下:
 
  1. struct dev_pm_ops {
  2. int(*prepare)(struct device *dev);
  3. void(*complete)(struct device *dev);
  4. int(*suspend)(struct device *dev);
  5. int(*resume)(struct device *dev);
  6. int(*freeze)(struct device *dev);
  7. int(*thaw)(struct device *dev);
  8. int(*poweroff)(struct device *dev);
  9. int(*restore)(struct device *dev);
  10. int(*suspend_late)(struct device *dev);
  11. int(*resume_early)(struct device *dev);
  12. int(*freeze_late)(struct device *dev);
  13. int(*thaw_early)(struct device *dev);
  14. int(*poweroff_late)(struct device *dev);
  15. int(*restore_early)(struct device *dev);
  16. int(*suspend_noirq)(struct device *dev);
  17. int(*resume_noirq)(struct device *dev);
  18. int(*freeze_noirq)(struct device *dev);
  19. int(*thaw_noirq)(struct device *dev);
  20. int(*poweroff_noirq)(struct device *dev);
  21. int(*restore_noirq)(struct device *dev);
  22. int(*runtime_suspend)(struct device *dev);
  23. int(*runtime_resume)(struct device *dev);
  24. int(*runtime_idle)(struct device *dev);
  25. };
  26.  
  27. struct platform_suspend_ops {
  28. int(*valid)(suspend_state_t state);
  29. int(*begin)(suspend_state_t state);
  30. int(*prepare)(void);
  31. int(*prepare_late)(void);
  32. int(*enter)(suspend_state_t state);
  33. void(*wake)(void);
  34. void(*finish)(void);
  35. bool(*suspend_again)(void);
  36. void(*end)(void);
  37. void(*recover)(void);
  38. };

虽然内核的注释已经相当详细了,但我们一定会犯晕,到底该实现哪些回调?这些回调的应用场景又是什么?蜗蜗以为,要熟练使用这些回调,唯一的方法就是多coding、多理解。除此之外,我们可以总结一下在电源状态切换时,这些回调的调用时机,从侧面帮助理解。如下(只介绍和suspend功能有关的,struct dev_pm_ops简称D,struct platform_suspend_ops简称P):

pm_ops_flow


5.5 suspend过程的同步和PM wakeup

最重要的事情,如果suspend的过程中,有唤醒事件产生怎么办?正常的流程,应该终止suspend,返回并处理事件。但由于suspend过程的特殊性,进程被freeze、关中断等等,导致事情并没有那么简单,以至于在很久的一段时间内,kernel都不能很好的处理。这也称作suspend过程的同步问题。

在美好的旧时光里,suspend大多用于热关机,因此同步问题的影响并不突出(因为操作并不频繁)。但来到新时代之后,事情变了,Android竟然用suspend作日常的待机(操作就相当频繁了),这时问题就大了。那怎么解决呢?得靠system wakeup framework,也就是suspend过程中所调用的pm_wakeup_pending接口所在的模块。我会在下一篇文章中继续该模块的分析,这里就不再继续了。

<think>好的,我现在需要回答用户关于Linux内核电源管理框架以及如何实现设备休眠唤醒功能的问题。首先,我需要回忆一下Linux电源管理的基本知识,确保自己理解正确。 用户之前问过如何实现字符设备驱动,现在转向电源管理,可能是在开发驱动时需要考虑设备的电源管理功能。我需要从整体框架开始描述,然后具体到设备的休眠唤醒实现步骤。 首先,Linux电源管理框架主要包括哪几个部分?我记得有系统级别的PM,比如休眠、挂起,还有运行时PM,设备在空闲时进入低功耗状态。内核中的电源管理框架可能涉及PM核心、子系统、设备驱动等部分。需要明确几个关键组件,比如PM核心、CPUIdle、CPU Freq、Suspend/Hibernate、Runtime PM等。 然后,设备休眠唤醒功能的实现。这部分需要驱动程序的支持。设备驱动需要注册相应的回调函数,比如suspend和resume,或者使用Runtime PM的运行时 suspend/resume。可能还需要处理唤醒源(wakeup source)的注册,以便设备能够唤醒系统。 需要注意的是,不同内核版本可能有不同的API,比如旧版本使用struct dev_pm_ops,而新版本可能有更简化的方法。另外,唤醒源的管理,比如使用wakeup_source_init和device_init_wakeup函数。 用户可能需要具体的步骤,比如如何注册PM操作,如何启用唤醒功能,以及如何测试休眠唤醒。此外,可能还要提到电源管理的基础设施,比如sysfs中的电源管理接口,或者使用工具如echo mem > /sys/power/state来触发系统挂起。 需要确保回答结构清晰,分点说明框架和实现步骤,可能还要附上代码示例,就像之前的字符设备驱动回答那样。但要注意代码的正确性,比如在suspend回调中保存设备状态,resume中恢复,以及处理唤醒中断。 还要考虑潜在的问题,比如正确处理电源状态转换的顺序,确保在挂起前设备已经进入低功耗模式,唤醒后正确重新初始化硬件。可能提到使用自动睡眠(autosleep)或Runtime PM的自动管理功能,减少功耗。 最后,检查是否覆盖了用户的问题,是否有遗漏的部分。比如是否解释了电源管理框架的层次结构,以及设备驱动需要实现的具体步骤,如注册PM ops、处理唤醒事件等。</think>### Linux内核电源管理框架解析与设备休眠唤醒实现指南 #### 一、电源管理框架核心组件 $$ PM\_Core \xrightarrow{协调} \begin{cases} 系统级休眠(Suspend/Hibernate) \\ CPU频率调节(CPUFreq) \\ CPU空闲管理(CPUIdle) \\ 运行时电源管理(Runtime PM) \\ 设备特定PM操作(dev\_pm\_ops) \end{cases} $$ #### 二、休眠状态层级(ACPI标准) | 状态 | 功耗 | 唤醒延迟 | 特性 | |-------|------|----------|---------------------------| | S0 | 100% | 0ms | 正常工作状态 | | S1 | 90% | 2ms | CPU停止执行指令 | | S3 | 5% | 100ms | 挂起到内存(常用休眠状态) | | S4 | 1% | 1s | 挂起到磁盘 | | S5 | 0% | - | 完全关机 | #### 三、设备驱动实现关键步骤 **1. 定义电源管理操作集** ```c static const struct dev_pm_ops mydev_pm_ops = { .suspend = mydev_suspend, .resume = mydev_resume, .freeze = mydev_freeze, .thaw = mydev_thaw, .poweroff = mydev_poweroff, .restore = mydev_restore, .runtime_suspend = mydev_runtime_suspend, .runtime_resume = mydev_runtime_resume, }; /* 挂载到设备结构体 */ struct device_driver my_driver = { .pm = &mydev_pm_ops, }; ``` **2. 实现基础休眠函数** ```c static int mydev_suspend(struct device *dev) { struct mydev *hw = dev_get_drvdata(dev); // 保存设备状态 hw->reg_cache = readl(hw->base + REG_CONFIG); // 进入低功耗模式 writel(LOW_POWER_MODE, hw->base + REG_PWR); // 禁用中断(可选) disable_irq(hw->irq); return 0; } static int mydev_resume(struct device *dev) { struct mydev *hw = dev_get_drvdata(dev); // 恢复电源状态 writel(NORMAL_MODE, hw->base + REG_PWR); // 恢复寄存器配置 writel(hw->reg_cache, hw->base + REG_CONFIG); // 重新使能中断 enable_irq(hw->irq); return 0; } ``` #### 四、唤醒功能实现 **1. 注册唤醒源** ```c // 初始化唤醒源(内核4.12+) struct wakeup_source *ws = wakeup_source_register(dev, "mydev_wakesrc"); // 旧版本兼容方法 device_init_wakeup(dev, true); ``` **2. 中断处理中触发唤醒** ```c static irqreturn_t mydev_interrupt(int irq, void *dev_id) { struct mydev *hw = dev_id; // 标记唤醒事件 pm_wakeup_event(&hw->pdev->dev, 1000); // 超时1秒 // 清除中断标志 writel(ACK_IRQ, hw->base + REG_STATUS); return IRQ_HANDLED; } ``` #### 五、运行时电源管理(Runtime PM) ```c // 启用Runtime PM pm_runtime_enable(dev); // 设置自动挂起延迟(单位:毫秒) pm_runtime_set_autosuspend_delay(dev, 2000); pm_runtime_use_autosuspend(dev); // 电源状态变更示例 pm_runtime_get_sync(dev); // 增加引用计数,保持唤醒 pm_runtime_put_autosuspend(dev); // 减少引用计数,可能触发挂起 // 实现回调函数 static int mydev_runtime_suspend(struct device *dev) { struct mydev *hw = dev_get_drvdata(dev); writel(SLEEP_MODE, hw->base + REG_CTRL); return 0; } static int mydev_runtime_resume(struct device *dev) { struct mydev *hw = dev_get_drvdata(dev); writel(NORMAL_MODE, hw->base + REG_CTRL); return 0; } ``` #### 六、调试与测试方法 **1. 查看电源状态** ```bash # 查看设备电源状态 cat /sys/bus/usb/devices/usb1/power/runtime_status # 触发系统挂起 echo mem > /sys/power/state ``` **2. 唤醒源监控** ```bash # 查看当前唤醒源 cat /sys/kernel/debug/wakeup_sources # 动态调试输出 echo 8 > /proc/sys/kernel/printk dmesg | grep PM ``` #### 七、关键注意事项 1. **状态保存恢复** 必须完整保存硬件寄存器状态,包括: - 时钟配置 - GPIO状态 - 中断掩码 - DMA配置 2. **唤醒源管理** 遵循"谁申请,谁释放"原则: ```c // 正确使用模式 pm_stay_awake(&dev->device); // 开始关键操作 pm_relax(&dev->device); // 结束关键操作 ``` 3. **电源域划分** 复杂设备使用`genpd`框架管理依赖关系: ```c static struct generic_pm_domain my_power_domain = { .power_off = my_domain_power_off, .power_on = my_domain_power_on, }; pm_genpd_init(&my_power_domain, NULL, false); ``` #### 八、新版内核改进(5.10+) 1. **统一设备生命周期管理** 使用`devm_pm_runtime_enable`自动管理资源 ```c devm_pm_runtime_enable(dev); ``` 2. **异步操作支持** 通过`async_suspend`提高挂起效率 ```c static const struct dev_pm_ops my_pm_ops = { .suspend = my_suspend, .suspend_late = my_suspend_late, .resume_early = my_resume_early, .resume = my_resume, .async_suspend = my_async_suspend, }; ``` 3. **电源管理质量(QoS)控制** 设置延迟约束: ```c dev_pm_qos_add_request(dev, &qos->resume_latency, DEV_PM_QOS_RESUME_LATENCY, 10000); // 10ms ``` > **最佳实践建议**:使用`pm_trace`工具调试唤醒问题,通过`powertop`优化功耗,配合`turbostat`监控CPU状态。对于USB/PCIe设备,建议优先使用子系统自带的PM帮助函数(如`usb_enable_autosuspend()`)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值