版本信息 Linux Kernel: v2.6.28 Android: v2.0 对于休眠(suspend)的简单介绍 在Linux中,休眠主要分三个主要的步骤: 冻结用户态进程和内核态任务 调用注册的设备的suspend的回调函数 顺序是按照注册顺序 休眠核心设备和使CPU进入休眠态冻结进程是内核把进程列表中所有的进程的状态都设置为停止,并且保存下所有进程的上下文. 当这些进程被解冻的时候,他们是不知道自己被冻结过的,只是简单的继续执行.如何让Linux进入休眠呢?用户可以通过读写sys文件/sys /power/state 是实现控制系统进入休眠. 比如 # echo standby > /sys/power/state 命令系统进入休眠. 也可以使用# cat /sys/power/state 来得到内核支持哪几种休眠方式. Linux Suspend 的流程 相关的文件: 你可以通过访问 Linux内核网站 来得到源代码,下面是文件的路径: linux_soruce/kernel/power/main.c linux_source/kernel/arch/xxx/mach-xxx/pm.c linux_source/driver/base/power/main.c 接下来让我们详细的看一下Linux是怎么休眠/唤醒的. Let 's going to see how these happens.用户对于/sys/power/state 的读写会调用到 main.c中的state_store(), 用户可以写入 const char * const pm_state[] 中定义的字符串, 比如"mem", "standby".然后state_store()会调用enter_state(), 它首先会检查一些状态参数,然后同步文件系统. 下面是代码:/** * enter_state - Do common work of entering low-power state. * @state: pm_state structure for state we're entering. * * Make sure we're the only ones trying to enter a sleep state. Fail * if someone has beat us to it, since we don't want anything weird to * happen when we wake up. * Then, do the setup for suspend, enter the state, and cleaup (after * we've woken up). */ static int enter_state(suspend_state_t state) { int error; if (!valid_state(state)) return -ENODEV; if (!mutex_trylock(&pm_mutex)) return -EBUSY; printk(KERN_INFO "PM: Syncing filesystems ... "); sys_sync(); printk("done./n"); pr_debug("PM: Preparing system for %s sleep/n", pm_states[state]); error = suspend_prepare(); if (error) goto Unlock; if (suspend_test(TEST_FREEZER)) goto Finish; pr_debug("PM: Entering %s sleep/n", pm_states[state]); error = suspend_devices_and_enter(state); Finish: pr_debug("PM: Finishing wakeup./n"); suspend_finish(); Unlock: mutex_unlock(&pm_mutex); return error; }
准备, 冻结进程 当进入到suspend_prepare()中以后, 它会给suspend分配一个虚拟终端来输出信 息, 然后广播一个系统要进入suspend的Notify, 关闭掉用户态的helper进程, 然后一次调用suspend_freeze_processes()冻结所有的进程, 这里会保存所有进程 当前的状态, 也许有一些进程会拒绝进入冻结状态, 当有这样的进程存在的时候, 会导致冻结失败,此函数就会放弃冻结进程,并且解冻刚才冻结的所有进程./** * suspend_prepare - Do prep work before entering low-power state. * * This is common code that is called for each state that we're entering. * Run suspend notifiers, allocate a console and stop all processes. */ static int suspend_prepare(void) { int error; unsigned int free_pages; if (!suspend_ops || !suspend_ops->enter) return -EPERM; pm_prepare_console(); error = pm_notifier_call_chain(PM_SUSPEND_PREPARE); if (error) goto Finish; error = usermodehelper_disable(); if (error) goto Finish; if (suspend_freeze_processes()) { error = -EAGAIN; goto Thaw; } free_pages = global_page_state(NR_FREE_PAGES); if (free_pages < FREE_PAGE_NUMBER) { pr_debug("PM: free some memory/n"); shrink_all_memory(FREE_PAGE_NUMBER - free_pages); if (nr_free_pages() < FREE_PAGE_NUMBER) { error = -ENOMEM; printk(KERN_ERR "PM: No enough memory/n"); } } if (!error) return 0; Thaw: suspend_thaw_processes(); usermodehelper_enable(); Finish: pm_notifier_call_chain(PM_POST_SUSPEND); pm_restore_console(); return error; }
让外设进入休眠 现在, 所有的进程(也包括workqueue/kthread) 都已经停止了, 内核态人物有 可能在停止的时候握有一些信号量, 所以如果这时候在外设里面去解锁这个信号 量有可能会发生死锁, 所以在外设的suspend()函数里面作lock/unlock锁要非常小心,这里建议设计的时候就不要在suspend()里面等待锁. 而且因为suspend的时候,有一些Log是无法输出的,所以一旦出现问题,非常难调试.然后kernel在这里会尝试释放一些内存.最后会调用suspend_devices_and_enter()来把所有的外设休眠, 在这个函数中, 如果平台注册了suspend_pos(通常是在板级定义中定义和注册), 这里就会调用 suspend_ops->begin(), 然后driver/base/power/main.c 中的 device_suspend()->dpm_suspend() 会被调用,他们会依次调用驱动的suspend() 回调来休眠掉所有的设备.当所有的设备休眠以后, suspend_ops->prepare()会被调用, 这个函数通常会作 一些准备工作来让板机进入休眠. 接下来Linux,在多核的CPU中的非启动CPU会被关掉, 通过注释看到是避免这些其他的CPU造成race condion,接下来的以后只有一个CPU在运行了.suspend_ops 是板级的电源管理操作, 通常注册在文件 arch/xxx/mach-xxx/pm.c 中.接下来, suspend_enter()会被调用, 这个函数会关闭arch irq, 调用 device_power_down(), 它会调用suspend_late()函数, 这个函数是系统真正进入 休眠最后调用的函数, 通常会在这个函数中作最后的检查. 如果检查没问题, 接 下来休眠所有的系统设备和总线, 并且调用 suspend_pos->enter() 来使CPU进入 省电状态. 这时候,就已经休眠了.代码的执行也就停在这里了./** * suspend_devices_and_enter - suspend devices and enter the desired system * sleep state. * @state: state to enter */ int suspend_devices_and_enter(suspend_state_t state) { int error, ftrace_save; if (!suspend_ops) return -ENOSYS; if (suspend_ops->begin) { error = suspend_ops->begin(state); if (error) goto Close; } suspend_console(); ftrace_save = __ftrace_enabled_save(); suspend_test_start(); error = device_suspend(PMSG_SUSPEND); if (error) { printk(KERN_ERR "PM: Some devices failed to suspend/n"); goto Recover_platform; } suspend_test_finish("suspend devices"); if (suspend_test(TEST_DEVICES)) goto Recover_platform; if (suspend_ops->prepare) { error = suspend_ops->prepare(); if (error) goto Resume_devices; } if (suspend_test(TEST_PLATFORM)) goto Finish; error = disable_nonboot_cpus(); if (!error && !suspend_test(TEST_CPUS)) suspend_enter(state); enable_nonboot_cpus(); Finish: if (suspend_ops->finish) suspend_ops->finish(); Resume_devices: suspend_test_start(); device_resume(PMSG_RESUME); suspend_test_finish("resume devices"); __ftrace_enabled_restore(ftrace_save); resume_console(); Close: if (suspend_ops->end) suspend_ops->end(); return error; Recover_platform: if (suspend_ops->recover) suspend_ops->recover(); goto Resume_devices; }
提供给Android Framework层的proc文件如下: "/sys/android_power/acquire_partial_wake_lock" //申请partial wake lock "/sys/android_power/acquire_full_wake_lock" //申请full wake lock "/sys/android_power/release_wake_lock" //释放相应的wake lock "/sys/android_power/request_state" //请求改变系统状态,进standby和回到wakeup两种状态 "/sys/android_power/state" //指示当前系统的状态
2、Android的电源管理主要是通过Wake lock来实现的,在最底层主要是通过如下三个队列来实现其管 理: static LIST_HEAD(g_inactive_locks); static LIST_HEAD(g_active_partial_wake_locks); static LIST_HEAD(g_active_full_wake_locks); 所有初始化后的lock都会被插入到g_inactive_locks的队列中,而当前活动的partial wake lock都会被插入到g_active_partial_wake_locks队列中, 活动的full wake lock被插入到 g_active_full_wake_locks队列中, 所有的partial wake lock 和full wake lock在过期 后或unlock后都会被移到inactive的队列,等待下次的调用。
3、在Kernel层使用wake lock步骤如下: 1) 调用函数android_init_suspend_lock初始化一个wake lock 2) 调用相关申请lock的函数android_lock_suspend或Android_lock_suspend_auto_expire 请求lock,这里只能申请partial wake lock, 如果要申请Full wake lock,则需要调用函 数android_lock_partial_suspend_auto_expire(该函数没有EXPORT出来),这个命名有点 奇怪,不要跟前面的android_lock_suspend_auto_expire搞混了. 3) 如果是auto expire的wake lock则可以忽略,不然则必须及时的把相关的wake lock释放掉, 否则会造成系统长期运行在高功耗的状态。 4) 在驱动卸载或不再使用Wake lock时请记住及时的调用android_uninit_suspend_lock释放 资源. 4、系统的状态: USER_AWAKE, //Full on status USER_NOTIFICATION, //Early suspended driver but CPU keep on USER_SLEEP // CPU enter sleep mode
五、Android电源管理流程
系统正常开机后进入到AWAKE状态, Backlight会从最亮慢慢调节到用户设定的亮度,系统 screen off timer(settings->sound & display-> Display settings -> Screen timeout)开始计时,在计时时间到之前,如果有任何的activity事件发生,如Touch click, keyboard pressed等事件, 则将Reset screen off timer, 系统保持在AWAKE状态. 如果有应用 程序在这段时间内申请了Full wake lock,那么系统也将保持在AWAKE状态, 除非用户按下 power key. 在AWAKE状态下如果电池电量低或者是用AC供电screen off timer时间到并且选中 Keep screen on while pluged in选项,backlight会被强制调节到DIM的状态.
如果Screen off timer时间到并且没有Full wake lock或者用户按了power key,那么系统状态将 被切换到NOTIFICATION,并且调用所有已经注册的g_early_suspend_handlers函数, 通常会把LCD 和Backlight驱动注册成early suspend类型,如有需要也可以把别的驱动注册成early suspend, 这 样就会在第一阶段被关闭. 接下来系统会判断是否有partial wake lock acquired, 如果有则等待 其释放, 在等待的过程中如果有user activity事件发生,系统则马上回到AWAKE状态; 如果没有partial wake lock acquired, 则系统会马上调用函数pm_suspend关闭其它相关的驱 动, 让CPU进入休眠状态.系统在Sleep状态时如果检测到任何一个Wakeup source, 则CPU会从Sleep 状态被唤醒,并且调用相关的驱动的resume函数,接下来马上调用前期注册的early suspend驱动的 resume函数,最后系统状态回到AWAKE状态.这里有个问题就是所有注册过early suspend的函数在进 Suspend的第一阶段被调用可以理解,但是在resume的时候, Linux会先调用所有驱动的resume函数, 而此时再调用前期注册的early suspend驱动的resume函数有什么意义呢?个人觉得android的这个 early suspend和late resume函数应该结合Linux下面的suspend和resume一起使用,而不是单独的 使用一个队列来进行管理.