Linux系统中电源管理框架详解

2019-07-13 21:47发布

转载地址:http://www.wowotech.net/linux_kenrel/suspend_and_resume.html


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,在includelinuxsuspend.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. ");
  17.  
  18. pr_debug("PM: Preparing system for %s sleep ", 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 ", 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. ");
  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 ");
  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。该接口由"kernelprintk.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 ");
  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,关全