Android or Linux 的休眠与唤醒
2019-07-13 06:28 发布
生成海报
Linux休眠 /唤醒简介
休眠 / 唤醒在嵌入式 Linux 中是非常重要的部分 , 嵌入式设备尽可能的进入休眠状态来延长电池的续航时间。这篇文章就详细介绍一下 linux 中休眠 / 唤醒是如何工作的 , 还有 Android 中如何把这部分和 Linux 的机制联系起来的 .
在 Linux 中 , 休眠主要分三个主要的步骤 :
1 )冻结用户态进程和内核态任务
2)调用注册的设备的 suspend 的回调函数,顺序是按照注册顺序
3)休眠核心设备和使 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(), 用户可以写入 constchar
* const pm_state[] 中定义的字符串 , 比如 "mem","standby".
然后 state_store() 会调用 enter_state(), 它首先会检查一些状态参数 , 然后同步文件系统 . 。
准备 ,
冻结进程
当进入到 suspend_prepare() 中以后 , 它会给 suspend 分配一个虚拟终端来输出信息 , 然后广播一个系统要进入 suspend 的 Notify, 关闭掉用户态的 helper 进程 , 然后一次调用 suspend_freeze_processes() 冻结所有的进程 , 这里会保存所有进程当前的状态 , 也许有一些进程会拒绝进入冻结状态 , 当有这样的进程存在的时候 , 会导致冻结失败 , 此函数就会放弃冻结进程 , 并且解冻刚才冻结的所有进程。
让外设进入休眠
现在 , 所有的进程 ( 也包括 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 是板级的电源管理操作 , 通常注册在文件 arch/xxx/mach-xxx/pm.c 中。
当所有的设备休眠以后 suspend_enter() 调用 suspend_ops->prepare() 会被调用,执行 device_prepare 这个函数通常会作一些准备工作来让板机进入休眠 . 接下来 Linux, 在多核的 CPU 中的非启动 CPU 会被关掉 , 通过注释看到是避免这些其他的 CPU 造成 racecondion, 接下来的以后只有一个 CPU 在运行了。
核心设备的停止
接下来 suspend_enter() 会被调用 , 这个函数会关闭 archirq, 调用 device_power_down(), 它会调用 suspend_late() 函数 , 这个函数是系统真正进入休眠最后调用的函数 , 通常会在这个函数中作最后的检查 . 如果检查没问题 , 接
下来休眠所有的系统设备和总线 , 并且调用 suspend_pos->enter() 来使 CPU 进入省电状态,这时候 , 就已经休眠了,代码的执行也就停在这里了。
Linux
Resume 流程
如果在休眠中系统被中断或者其他事件唤醒 , 接下来的代码就会开始执行 , 这个唤醒的顺序是和休眠的循序相反的 , 所以系统设备和总线会首先唤醒,使能系统中断 , 使能休眠时候停止掉的非启动 CPU, 以及调用 suspend_ops->finish(), 而且在 suspend_devices_and_enter() 函数中也会继续唤醒每个设备 , 使能虚拟终端 , 最后调用 suspend_ops->end().
在返回到 enter_state() 函数中的 , 当 suspend_devices_and_enter() 返回以后 , 外设已经唤醒了 , 但是进程和任务都还是冻结状态 , 这里会调用 suspend_finish() 来解冻这些进程和任务 , 而且发出 Notify 来表示系统已经从 suspend 状态退出 , 唤醒终端 .
到这里 , 所有的休眠和唤醒就已经完毕了 , 系统继续运行了 .
Android系统 Suspend 和 resume 的函数流程
Android
休眠 (suspend)介绍
在一个打过 android 补丁的内核中 ,state_store() 函数会走另外一条路 , 会进入到 request_suspend_state() 中 , 这个文件在 earlysuspend.c 中 . 这些功能都是 android 系统加的 , 后面会对 earlysuspend 和 lateresume 进行介绍。
涉及到的文件 :
linux_source/kernel/power/main.c
linux_source/kernel/power/earlysuspend.c
linux_source/kernel/power/wakelock.c
特性介绍
1 ) EarlySuspend
Early suspend 是 android 引进的一种机制 , 这个机制作用在关闭显示的时候 , 一些和显示有关的设备 , 比如 LCD 背光 , 重力感应器 , 触摸屏 , 这些设备都会关掉,但是系统可能还是在运行状态 ( 这时候还有 wakelock) 进行任务的处理,例如在扫描 SD 卡上的文件等 . 在嵌入式设备中 , 背光是一个很大的电源消耗,所以 android 会加入这样一种机制。
2 ) LateResume
Late Resume 是和 suspend 配套的一种机制 , 是在内核唤醒完毕开始执行的,主要就是唤醒在 EarlySuspend 的时候休眠的设备 .
当所有的唤醒已经结束以后 , 用户进程都已经开始运行了 , 唤醒通常会是以下的几种原因 :
来电
如果是来电 , 那么 Modem 会通过发送命令给 rild 来让 rild 通知 WindowManager 有来电响应 , 这样就会远程调用 PowerManagerService 来写 "on" 到 /sys/power/state 来执行 lateresume 的设备 , 比如点亮屏幕等 .
用户按键用户按键事件会送到 WindowManager 中 ,WindowManager 会处理这些按键事件 , 按键分为几种情况 , 如果案件不是唤醒键 ( 能够唤醒系统的按键 ) 那么 WindowManager 会主动放弃 wakeLock 来使系统进入再次休眠 , 如果按键是唤醒键 , 那么 WindowManger 就会调用 PowerManagerService 中的接口来执行 Late
Resume.
Late Resume 会依次唤醒前面调用了 EarlySuspend 的设备 .
3 ) WakeLock
Wake Lock 在 Android 的电源管理系统中扮演一个核心的角 {MOD} .Wake
Lock 是一种锁的机制 , 只要有人拿着这个锁,系统就无法进入休眠 , 可以被用户态程序和内核获得。这个锁可以是有超时的或者是没有超时的 , 超时的锁会在时间过去以后自动解锁。如果没有锁了或者超时了 , 内核就会启动休眠的那套机制来进入休眠。
3 ) AndroidSuspend
当用户写入 mem 或者 standby 到 /sys/power/state 中的时候 ,state_store() 会被调用 , 然后 Android 会在这里调用 request_suspend_state() 而标准的 Linux 会在这里进入 enter_state() 这个函数 . 如果请求的是休眠 , 那么 early_suspend 这个 workqueue 就会被调用 , 并且进入 early_suspend 状态。调用 request_suspend_state() 后在 suspend_work_queue 工作线程上面注册一个 early_suspend_work 工作者,
然后又通过 staticDECLARE_WORK(early_suspend_work, early_suspend); 注册一个工作任务 early_suspend 。所以系统最终会调用 early_suspend 函数。
注册加入 suspend 和 resume 流程
platform_device_register()-->platform_device_add()-->device_add()-->device_pm_add()--> ,最终加入到了 dpm_list 的链表中,在其中的 dpm_suspend 和 dpm_suspend 中通过遍历这个链表来进行查看哪个 device 中包含 suspend 和 resume 项。
系统唤醒和休眠
Kernel 层 [ 针对 AndroidLinux2.6.28 内核 ]:
其主要代码在下列位置 :
Drivers/base /main.c
kernel/power /main.c
kernel/power/wakelock.c
kernel/power/earlysuspend.c
其对 Kernel 提供的接口函数有
EXPORT_SYMBOL(wake_lock_init);// 初始化 Suspendlock, 在使用前必须做初始化
EXPORT_SYMBOL(wake_lock);// 申请 lock, 必须调用相应的 unlock 来释放它
static DEFINE_TIMER(expire_timer,expire_wake_locks, 0, 0);// 定时时间到,加入到 suspend 队列中;
EXPORT_SYMBOL(wake_unlock);// 释放 lock
EXPORT_SYMBOL_GPL(device_power_up);// 打开特殊的设备
EXPORT_SYMBOL_GPL(device_power_down);// 关闭特殊设备
EXPORT_SYMBOL_GPL(device_resume);// 重新存储设备的状态;
EXPORT_SYMBOL_GPL(device_suspend);: 保存系统状态,并结束掉系统中的设备;
EXPORT_SYMBOL(register_early_suspend);// 注册 earlysuspend 的驱动
EXPORT_SYMBOL(unregister_early_suspend);// 取消已经注册的 earlysuspend 的驱动
Android的 suspent执行流程
函数的流程如下所示:
应用程序通过对 /sys/power/state 的写入操作可以使系统进行休眠的状态,会调用 /kernel/power/main.c 中的 state_store 函数。 pm_states 包括:
PM_SUSPEND_ON , PM_SUSPEND_STANDBY , PM_SUSPEND_MEM 满足的状态。
1 )当状态位 PM_SUSPEND_ON 的状态的时候, request_suspend_state() ;当满足休眠的状态的时候,调用 request_suspend_state 在 suspend_work_queue 工作线程上创建 early_suspend_work 队列, queue_work(suspend_work_queue,&early_suspend_work) 。
2 )然后通过 DECLARE_WORK(early_suspend_work,early_suspend); 在 early_suspend_work 工作队列中添加工作任务调用 early_suspend ,所以 early_suspend 函数会被调用。
3 ) early_suspend 函数中通过
list_for_each_entry(pos,&early_suspend_handlers, link) {
if (pos->suspend != NULL)
pos->suspend(pos) ;
在链表中找注册的 suspend 函数,这个 suspend 是 early 的。 early_suspend 后面调用 wake_unlock 函数。语句: wake_unlock(&main_wake_lock);
4 ) wake_unlock() 中调用 mod_timer 启动 expire_timer 定时器,当定时时间到了,则执行 expire_wake_locks 函数,将 suspend_work 加入到 suspend_work_queue 队列中,分析到这里就可以知道了 early_suspend_work 和 suspend_work 这两个队列的先后顺序了(先执行 early ,定义一段时间后才执行 suspend_work ),然后会在 suspend_work 队列中加入 suspend 的工作任务,所以 wakelock.c 中的 suspend 函数会被调用。
5 ) suspend 调用了 pm_suspend ,通过判断当前的状态,选择 enter_state() ,在 enter_state 中,经过了 suspend_prepare , suspend_test 和 suspend_device_and_enter() ,在 suspend_device_and_enter 中调用 dpm_suspend_start() ,然后调用 dpm_suspend() 。
6 ) dpm_suspend 中利用 while 循环在 dpm_list 链表查找所有 devic ,然后调用 device_suspend 来保存状态和结束系统的设备。到了这里,我们就又可以看见在初始化的时候所看到的队列 dpm_list 。
dpm_list 链表的添加是在 device_pm_add 中完成,请看上一节中。
Wake Lock
我们接下来看一看 wakelock 的机制是怎么运行和起作用的 , 主要关注 wakelock.c 文件就可以了。
wake lock 有加锁和解锁两种状态 , 加锁的方式有两种 , 一种是永久的锁住 , 这样的锁除非显示的放开 , 是不会解锁的 , 所以这种锁的使用是非常小心的 . 第二种是超时锁 , 这种锁会锁定系统唤醒一段时间 , 如果这个时间过去了 , 这个锁会自动解除 .
锁有两种类型 :
WAKE_LOCK_SUSPEND 这种锁会防止系统进入睡眠
WAKE_LOCK_IDLE 这种锁不会影响系统的休眠 , 作用我不是很清楚 .
在 wakelock 中 , 会有 3 个地方让系统直接开始 suspend(), 分别是 :
1 )在 wake_unlock() 中 , 如果发现解锁以后没有任何其他的 wakelock 了 ,
打开微信“扫一扫”,打开网页后点击屏幕右上角分享按钮