分享

电源管理基本观念之一

 WUCANADA 2015-09-24

1.Linux 描述的电源状态

- On(on)                                                 S0 -  Working

- Standby (standby)                              S1 -  CPU and RAM are powered but not executed

Suspend to RAM(mem)                        S3 -  RAM is powered and the running content is saved to RAM

- Suspend to Disk,Hibernation(disk)    S4 -  All content is saved to Disk and power down

 

S3 aka STR(suspend to ram),挂起到内存,简称待机。计算机将目前的运行状态等数据存放在内存,关闭硬盘、外设等设备,进入等待状态。此时内存仍然需要电力维持其数据,但整机耗电很少。恢复时计算机从内存读出数据,回到挂起前的状态,恢复速度较快。对DDR的耗电情况进行优化是S3性能的关键,大多数手持设备都是用S3待机。

S4 aka STD(suspend to disk),挂起到硬盘,简称休眠。把运行状态等数据存放在硬盘上某个文件或者某个特定的区域,关闭硬盘、外设等设备,进入关机状态。此时计算机完全关闭,不耗电。恢复时计算机从休眠文件/分区中读出数据,回到休眠前的状态,恢复速度较慢。电子书项目中,见过一款索尼的电子书,没有定义关机状态,只定义了S4,从而提高开机速度。


2.Linux内核电源管理接口

Linux把电源接口框架纳入设备模型中,通过power_kobj对象的属性文件提供操作电源策略的接口。
  1. static int __init pm_init(void 
  2.  
  3.     int error pm_start_workqueue();  
  4.     if (error)  
  5.         return error;  
  6.     hibernate_image_size_init();  
  7.     hibernate_reserved_size_init();  
  8.     power_kobj kobject_create_and_add("power"NULL);  
  9.     if (!power_kobj)  
  10.         return -ENOMEM;  
  11.     error sysfs_create_group(power_kobj, &attr_group);  
  12.     if (error)  
  13.         return error;  
  14.     return pm_autosleep_init();  
  15.  
power_kobj的属性都定义在attr_group中,每一个属性文件都有对应的一组show和store方法。
  1. static struct attribute g[]  
  2.     &state_attr.attr,  
  3. #ifdef CONFIG_PM_TRACE  
  4.     &pm_trace_attr.attr,  
  5.     &pm_trace_dev_match_attr.attr,  
  6. #endif  
  7. #ifdef CONFIG_PM_SLEEP  
  8.     &pm_async_attr.attr,  
  9.     &wakeup_count_attr.attr,  
  10. #ifdef CONFIG_PM_AUTOSLEEP  
  11.     &autosleep_attr.attr,  
  12. #endif  
  13. #ifdef CONFIG_PM_WAKELOCKS  
  14.     &wake_lock_attr.attr,  
  15.     &wake_unlock_attr.attr,  
  16. #endif  
  17. #ifdef CONFIG_PM_DEBUG  
  18.     &pm_test_attr.attr,  
  19. #endif  
  20. #endif  
  21.     NULL,  
  22. };  
  23.   
  24. static struct attribute_group attr_group  
  25.     .attrs g,  
  26. };  
从代码可以看出来,state属性是主属性文件,必须具备,其他属性文件都有相应的宏开关,根据需要定制。

3. 电源状态切换(state属性文件)

Linux的世界里,一切皆文件。state属性用来在S0,S1,S3,S4四种不同的电源状态之间切换。通过向state文件中写入不同的值来让系统进入不同的电源状态。接收状态值的函数是state_store。
  1. static ssize_t state_store(struct kobject *kobj, struct kobj_attribute *attr,  
  2.                const char *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.     else if (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.  
autosleep是android内核为了跟主线内核兼容所引入的。切换电源状态的入口函数是pm_suspend。针对嵌入式系统,S3是一种比较常用的节电状态。俗称待机。待机过程可以粗略的分为五个阶段,但不一定有严格的界限。
a)冻结用户态进程

b)挂起设备

c)针对soc相应的节电操作和为唤醒做准备工作

d)smp中非启动cpu的挂起

e)cpu core的挂起


3.1 冻结用户进程

进程冻结模块由CONFIG_FREEZER宏开关控制。

相关代码在kernel/power/process.c中,程序的结构比较简洁,总共就五个函数。

freeze process的控制过程都在freeze_processes函数中

 

  1. int freeze_processes(void)  
  2.  
  3.     int error;  
  4.   
  5.     error __usermodehelper_disable(UMH_FREEZING);  
  6.     if (error)  
  7.         return error;  
  8.   
  9.     if (!pm_freezing)  
  10.         atomic_inc(&system_freezing_cnt);  
  11.   
  12.     printk("Freezing user space processes ... ");  
  13.     pm_freezing true;  
  14.     error try_to_freeze_tasks(true);  
  15.     if (!error)  
  16.         printk("done.");  
  17.         __usermodehelper_set_disable_depth(UMH_DISABLED);  
  18.         oom_killer_disable();  
  19.      
  20.     printk("\n");  
  21.     BUG_ON(in_atomic());  
  22.   
  23.     if (error)  
  24.         thaw_processes();  
  25.     return error;  
  26.  

进程的冻结控制过程如下:

 

 

1. 冻结khelper内核线程。khelper是一个用于从内核空间调用用户空间应用程序的内核模块。能够在动态加载模块,热插拔等场景中发挥作用。

2.调用try_to_freeze_tasks冻结task_struct表中的任务。

3.如果try_to_freeze_tasks没有返回error则disable usermode helper 和 oom killer并成功返回。

4.如果try_to_freeze_tasks返回error则调用thaw_processes解冻操作。

try_to_freeze_tasks和thaw_process是一对反函数。冻结进程的工作是在两个循环中完成的。冻结进程要访问task_struct列表,所以先要加锁,然后用do_each_thread(g, p) 和while_each_thread(g, p)循环改变task_struct列表中进程的状态。把两个宏展开后的代码如下:

  1. for循环从init_task(idle process)开始遍历所有进程×/  
  2. for (g &init_task (g next_task(g)) != &init_task    
  3.     do  
  4.         /×如果是当前进程或者freeze_task失败则回到for循环开始下一个进程×/  
  5.         if (p == current || !freeze_task(p))                      
  6.             continue 
  7.         /×如果进程是stop或者应该跳过的进程则标记todo++,后面会专门针对这类进程重新处理×/  
  8.         if (!task_is_stopped_or_traced(p) && !freezer_should_skip(p))         
  9.             todo++;  
  10.     }while ((p next_thread(p)) != g)  //while循环则从主线程开始遍历每个进程的所有线程  

3.2 挂起设备(Device Power Manager)

设备电源的管理涉及到两种不同的场景:一种是在系统电源状态发生转变的情况下,譬如从S0->S3或者S0->S4。另外一种是在系统处在S0,但是仍然有些不被使用的设备进入节电的状态,这种情况就是runtime device manager,对应在手持设备上,直接影响所谓的场景功耗。设备电源管理,同时涉及到器件,总线,控制器,子系统,同类设备的状态改变,对于操作的顺序上有一定的要求。举个例子,一条i2c总线上挂有camera,g ensor,lighter sensor,那么i2c总线控制器必须要确保总线上的设备先进入low power或者off状态才能进入节电模式,因为Linux 的驱动模型把总线和设备是分开控制的。另外许多设备都能作为系统的唤醒源,这个要求也要纳入统一的考虑。Linux2.5及以后的内核引入设备模型,把总线,设备,子设备,同一类型设备,子系统通过kobject组织起来,为device power manager提供了统一的控制平台。

通常情况下,设备进入suspend状态,通常都会I/O口停止工作,DAM停止工作,也不会请求中断,停止数据的读写传输。但是如果设备作为唤醒源,则依然为出发中断控制器的中断信号线。对于设备的管理,Linux在DPM(dynamic power manager)的架构下,提供了一整套机制,可以灵活的针对不同特性的设备。在include/linux/pm.h文件中定义了一组回调函数指针用于各种情况:
  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. };  
每个函数的应用场景都有专门的注释说明,详细的说明也在include/linux/pm.h文件中。针对设备电源管理的代码在driver/base/power/main.c文件中。电源管理相关的操作紧密的跟设备模型结合在一起。
device结构体中有两个与电源管理关系密切的成员:
  1. struct dev_pm_info  power;  
  2. struct dev_pm_domain    *pm_domain;  
DPM则利用dev_pm_info结构体的power->entry成员和dev_pm_domain结构体的dev_pm_domain成员把参入系统电源管理的设备以及相关的操作串联起来的。下面是一个简单的数据流程:

设备初始化流程:
device_register(dev)->device_initialize(dev)->device_pm_init(dev)->INIT_LIST_HEAD(&dev->power.entry);
设备添加流程:
device_add(dev)->device_pm_add(dev)->list_add_tail(&dev->power.entry, &dpm_list);

从设备初始化和添加到设备模型的流程可以看出,每个设备在注册和添加的过程中对应的device->power.entry被添加到了dpm_list链表中。

device suspend由suspend模块完成,suspend模块由CONFIG_SUSPEND宏开关控制

 

  1. obj-$(CONFIG_SUSPEND)       += suspend.o  
代码就在kernel/power/suspend.c中

 

suspend模块对外导出了pm_suspend接口:

 

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

pm_suspend被用来控制系统的设备进入指定的状态。前面提到的Linux定义的四种电源状态会被传递到这个函数,pm_suspend会对电源状态做检查,如果传入的是非法之,直接返回EINVAL。

四种电源状态定在include/linux/suspend.h文件中

 

  1. typedef int __bitwise suspend_state_t;  
  2.   
  3. #define PM_SUSPEND_ON       ((__force suspend_state_t) 0)  
  4. #define PM_SUSPEND_STANDBY  ((__force suspend_state_t) 1)  
  5. #define PM_SUSPEND_MEM      ((__force suspend_state_t) 3)  
  6. #define PM_SUSPEND_MAX      ((__force suspend_state_t) 4)  

 

针对具体设备休眠的操作都在针对设备休眠的驱动里面。相关的文件在driver/base/power/目录下。针对设备的休眠动作在driver/base/power/main.c文件中。该文件对外导出了三个接口,suspend模块用到了这些接口。
  1.   
  2. int dpm_suspend_start(pm_message_t state)  
  3.  
  4.     int error;  
  5.   
  6.     error dpm_prepare(state);  
  7.     if (error)  
  8.         suspend_stats.failed_prepare++;  
  9.         dpm_save_failed_step(SUSPEND_PREPARE);  
  10.     else  
  11.         error dpm_suspend(state);  
  12.     return error;  
  13.  
  14. EXPORT_SYMBOL_GPL(dpm_suspend_start);  
  15.   
  16. void __suspend_report_result(const char *function, void *fn, int ret)  
  17.  
  18.     if (ret)  
  19.         printk(KERN_ERR "%s(): %pF returns %d\n"function, fn, ret);  
  20.  
  21. EXPORT_SYMBOL_GPL(__suspend_report_result);  
  22.   
  23.   
  24. int device_pm_wait_for_dev(struct device *subordinate, struct device *dev)  
  25.  
  26.     dpm_wait(dev, subordinate->power.async_suspend);  
  27.     return async_error;  
  28.  
  29. EXPORT_SYMBOL_GPL(device_pm_wait_for_dev);  


3.3 平台相关挂起操作(platform suspending)

在设备挂起操作完成之后,会针对特定平台做状态转换操作。Linux内核电源管理模块也为此定义了一组标准函数接口。不同架构只需要实现相应接口即可。
  1. struct platform_suspend_ops  
  2.     int (*valid)(suspend_state_t state);  
  3.     int (*begin)(suspend_state_t state);  
  4.     int (*prepare)(void);  
  5.     int (*prepare_late)(void);  
  6.     int (*enter)(suspend_state_t state);  
  7.     void (*wake)(void);  
  8.     void (*finish)(void);  
  9.     bool (*suspend_again)(void);  
  10.     void (*end)(void);  
  11.     void (*recover)(void);  
  12. };  

这组函数功能如下:
struct platform_suspend_ops定义了一组用于管理不同平台的下系统进入休眠状态的回调函数。这部分跟MCU关系非常紧密,涉及到时钟,PLL,电压域,频率,总线等系统级的物理模块进入休眠状态。每个具体的函数功能,在source code中有详细的注释。

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多