Linux Kernel and Android休眠与唤醒
- 格式:pdf
- 大小:217.44 KB
- 文档页数:7
LKD中的讲解休眠(被阻塞)的进程处于一个特殊的不可执行状态。
这点非常重要,否则,没有这种特殊状态的话,调度程序就可能选出一个本不愿意被执行的进程,更糟糕的是,休眠就必须以轮询的方式实现了。
进程休眠有各种原因,但肯定都是为了等待一些事件。
事件可能是一段时间、从文件I/O读更多数据,或者是某个硬件事件。
一个进程还有可能在尝试获得一个已经占用的内核信号量时被迫进入休眠。
休眠的一个常见原因就是文件I/O --如进程对一个文件执行了read()操作,而这需要从磁盘里读取。
还有,进程在获取键盘输入的时候也需要等待。
无论哪种情况,内核的操作都相同:进程把它自己标记成休眠状态,把自己从可执行队列移出,放入等待队列,然后调用schedule()选择和执行一个其他进程。
唤醒的进程刚好相反:进程被设置为可执行状态,然后再从等待队列中移到可执行队列。
休眠有两种相关的进程状态:TASK_INTERRUPTIBLE andTASK_UNINTERRUPTIBLE。
它们的惟一区别是处于TASK_UNINTERRUPTIBLE状态的进程会忽略信号,而处于TASK_INTERRUPTIBLE状态的进程如果收到信号会被唤醒并处理信号(然后再次进入等待睡眠状态)。
两种状态的进程位于同一个等待队列上,等待某些事件,不能够运行。
休眠通过等待队列进行处理。
等待队列是由等待某些事件发生的进程组成的简单链表。
内核用wake_queue_head_t来代表等待队列。
等待队列可以通过DECLARE_W AITQUEUE()静态创建,也可以有init_waitqueue_head()动态创建。
进程把自己放入等待队列中并设置成不可执行状态。
等与等待队列相关的事件发生的时候,队列上的进程会被唤醒。
为了避免产生竞争条件,休眠和唤醒的实现不能有纰漏。
针对休眠,以前曾经使用过一些简单的接口。
但那些接口会带来竞争条件;有可能导致在判断条件变为真后进程却开始了休眠,那样就会使进程无限期地休眠下去。
android休眠与唤醒驱动流程分析标准linux休眠过程:●power management notifiers are executed with PM_SUSPEND_PREPARE●tasks are frozen●target system sleep state is announced to the platform-handling code●devices are suspended●platform-specific global suspend preparation methods are executed●non-boot CPUs are taken off-line●interrupts are disabled on the remaining (main) CPU●late suspend of devices is carried out (一般有一些BUS driver的动作进行)●platform-specific global methods are invoked to put the system to sleep标准linux唤醒过程:●t he main CPU is switched to the appropriate mode, if necessary●early resume of devices is carried out (一般有一些BUS driver的动作进行)●interrupts are enabled on the main CPU●non-boot CPUs are enabled●platform-specific global resume preparation methods are invoked●devices are woken up●tasks are thawed●power management notifiers are executed with PM_POST_SUSPEND用户可以通过sys文件系统控制系统进入休眠:查看系统支持的休眠方式:#cat /sys/power/state常见有standby(suspend to RAM)、mem(suspend to RAM)和disk(suspend to disk),只是standby耗电更多,返回到正常工作状态的时间更短。
Linux 休眠流程可以分为以下几个步骤:
1. 用户使用特定的命令将计算机系统进入休眠状态。
2. Linux内核根据用户输入的命令,调用硬件层接口。
3. 在硬件层,根据Linux中断请求,实现系统唤醒或者跳过该中断请求。
4. 休眠事件发生后,系统将进入低功耗状态,周围的硬件设备也会受到影响,如PCI设备电源会断掉。
5. 程序会进行保存,以节省内存,消耗更少电量。
6. 系统运行休眠事件后,内存状态将被保存。
7. Linux内核设定一定的定时器和应答函数,可以跳过或实际处理系统中断。
8. 处理完成后,系统将在一定时间后唤醒,继续运行休眠前的程序。
以上步骤完成后,Linux系统休眠流程就完成了。
android休眠唤醒流程2android系统⼀段时间没有操作,屏幕(screen)将从⾼亮(bright)变为暗淡(dim),如果再过段时间还是没有操作,屏幕(screen)从暗淡(dim)变为关闭(off).这时,系统将进⼊休眠.⽽对于某些需要保持系统唤醒甚⾄屏幕唤醒的应⽤(⽐如视频播放器和⾳乐播放器)来说,就必须要有⼀个机制,使得系统不进⼊休眠状态,设置保持屏幕亮屏状态.wakelock即⽤来实现以上⽬的先上⽹上偷来的⼀副图,说⼀下android系统的整个电源管理流程图接下来对每⼀个模块具体分析:powermanager对应⽂件是android/frameworks/base/core/java/android/os/PowerManager.java在Android中应⽤程序并不是直接同PowerManagerService交互的,⽽是通过PowerManager间接地与PowerManagerService打交道。
此⽂件定义了⼀个powermanager类.主要实现了1,wakelock的申请与释放public WakeLock newWakeLock(int flags, String tag)2,系统延时进⼊休眠public void userActivity(long when, boolean noChangeLights)3,系统强制休眠public void goToSleep(long time)4,屏幕亮度设置public void setBacklightBrightness(int brightness)5,屏幕状态查询public boolean isScreenOn()6,系统重启public void reboot(String reason)细节wakelock的申请与释放{@samplecode*PowerManager pm = (PowerManager)mContext.getSystemService(* Context.POWER_SERVICE);*PowerManager.WakeLock wl = pm.newWakeLock(* PowerManager.SCREEN_DIM_WAKE_LOCK* | PowerManager.ON_AFTER_RELEASE,* TAG);*wl.acquire();* // ...*wl.release();⼀共有如下⼏个flag来进⾏不⼀样的唤醒⽅式.可以根据需要设置Flag Value CPU Screen KeyboardPARTIAL_WAKE_LOCK On* can-off OffSCREEN_DIM_WAKE_LOCK On Dim OffPROXIMITY_SCREEN_OFF_WAKE_LOCK on 距离传感器时关闭 offSCREEN_BRIGHT_WAKE_LOCK On Bright OffFULL_WAKE_LOCK On Bright BrightACQUIRE_CAUSES_WAKEUP 确保wakelock,主要⽤于视频播放器ON_AFTER_RELEASE = 0x20000000 release后倒计时,关闭屏幕...userActivity的作⽤:使系统从其他状态进⼊全部打开状态,⽐如从暗屏(dim)切换到亮屏,并重置倒计时计数器(待续)。
android休眠与唤醒驱动流程分析标准linux休眠过程:●power management notifiers are executed with PM_SUSPEND_PREPARE●tasks are frozen●target system sleep state is announced to the platform-handling code●devices are suspended●platform-specific global suspend preparation methods are executed●non-boot CPUs are taken off-line●interrupts are disabled on the remaining (main) CPU●late suspend of devices is carried out (一般有一些BUS driver的动作进行)●platform-specific global methods are invoked to put the system to sleep标准linux唤醒过程:●t he main CPU is switched to the appropriate mode, if necessary●early resume of devices is carried out (一般有一些BUS driver的动作进行)●interrupts are enabled on the main CPU●non-boot CPUs are enabled●platform-specific global resume preparation methods are invoked●devices are woken up●tasks are thawed●power management notifiers are executed with PM_POST_SUSPEND用户可以通过sys文件系统控制系统进入休眠:查看系统支持的休眠方式:#cat /sys/power/state常见有standby(suspend to RAM)、mem(suspend to RAM)和disk(suspend to disk),只是standby耗电更多,返回到正常工作状态的时间更短。
Linux Kernel and Android 休眠与唤醒(中文版) (转)简介休眠/唤醒在嵌入式Linux中是非常重要的部分,嵌入式设备尽可能的进入休眠状态来延长电池的续航时间.这篇文章就详细介绍一下Linux中休眠/唤醒是如何工作的, 还有Android中如何把这部分和Linux的机制联系起来的.版本信息∙Linux Kernel: v2.6.28∙Android: v2.0对于休眠(suspend)的简单介绍在Linux中,休眠主要分三个主要的步骤:1.冻结用户态进程和内核态任务2.调用注册的设备的suspend的回调函数o顺序是按照注册顺序3.休眠核心设备和使CPU进入休眠态冻结进程是内核把进程列表中所有的进程的状态都设置为停止,并且保存下所有进程的上下文. 当这些进程被解冻的时候,他们是不知道自己被冻结过的,只是简单的继续执行.如何让Linux进入休眠呢?用户可以通过读写sys文件/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(), 它首先会检查一些状态参数,然后同步文件系统. 下面是代码:准备, 冻结进程当进入到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->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进入省电状态. 这时候,就已经休眠了.代码的执行也就停在这里了.Resume如果在休眠中系统被中断或者其他事件唤醒, 接下来的代码就会开始执行, 这个唤醒的顺序是和休眠的循序相反的,所以系统设备和总线会首先唤醒,使能系统中断, 使能休眠时候停止掉的非启动CPU, 以及调用suspend_ops->finish(), 而且在suspend_devices_and_enter()函数中也会继续唤醒每个设备,使能虚拟终端, 最后调用 suspend_ops->end().在返回到enter_state()函数中的, 当 suspend_devices_and_enter() 返回以后, 外设已经唤醒了, 但是进程和任务都还是冻结状态, 这里会调用suspend_finish()来解冻这些进程和任务, 而且发出Notify来表示系统已经从suspend状态退出, 唤醒终端.到这里, 所有的休眠和唤醒就已经完毕了, 系统继续运行了.Android 休眠(suspend)在一个打过android补丁的内核中, state_store()函数会走另外一条路,会进入到request_suspend_state()中, 这个文件在earlysuspend.c中. 这些功能都是android系统加的, 后面会对earlysuspend和late resume 进行介绍.涉及到的文件:∙linux_source/kernel/power/main.c∙linux_source/kernel/power/earlysuspend.c∙linux_source/kernel/power/wakelock.c特性介绍Early SuspendEarly suspend 是android 引进的一种机制, 这种机制在上游备受争议,这里不做评论. 这个机制作用在关闭显示的时候, 在这个时候, 一些和显示有关的设备, 比如LCD背光, 比如重力感应器, 触摸屏, 这些设备都会关掉, 但是系统可能还是在运行状态(这时候还有wake lock)进行任务的处理, 例如在扫描SD卡上的文件等. 在嵌入式设备中, 背光是一个很大的电源消耗,所以android会加入这样一种机制.Late ResumeLate Resume 是和suspend 配套的一种机制, 是在内核唤醒完毕开始执行的. 主要就是唤醒在Early Suspend的时候休眠的设备.Wake LockWake Lock 在Android的电源管理系统中扮演一个核心的角色. Wake Lock是一种锁的机制, 只要有人拿着这个锁, 系统就无法进入休眠, 可以被用户态程序和内核获得. 这个锁可以是有超时的或者是没有超时的, 超时的锁会在时间过去以后自动解锁. 如果没有锁了或者超时了, 内核就会启动休眠的那套机制来进入休眠.Android Suspend当用户写入mem 或者 standby到 /sys/power/state中的时候, state_store()会被调用, 然后Android会在这里调用 request_suspend_state() 而标准的Linux会在这里进入enter_state()这个函数. 如果请求的是休眠, 那么early_suspend这个workqueue就会被调用,并且进入early_suspend状态.Early Suspend在early_suspend()函数中, 首先会检查现在请求的状态还是否是suspend, 来防止suspend的请求会在这个时候取消掉(因为这个时候用户进程还在运行),如果需要退出, 就简单的退出了. 如果没有, 这个函数就会把early suspend中注册的一系列的回调都调用一次, 然后同步文件系统, 然后放弃掉main_wake_lock, 这个wake lock是一个没有超时的锁,如果这个锁不释放, 那么系统就无法进入休眠.Late Resume当所有的唤醒已经结束以后, 用户进程都已经开始运行了, 唤醒通常会是以下的几种原因:∙来电如果是来电, 那么Modem会通过发送命令给rild来让rild通知WindowManager 有来电响应,这样就会远程调用PowerManagerService来写"on" 到/sys/power/state 来执行late resume的设备, 比如点亮屏幕等.∙用户按键用户按键事件会送到WindowManager中, WindowManager会处理这些按键事件,按键分为几种情况, 如果案件不是唤醒键(能够唤醒系统的按键) 那么WindowManager会主动放弃wakeLock来使系统进入再次休眠, 如果按键是唤醒键,那么WindowManger就会调用PowerManagerService中的接口来执行 Late Resume.∙Late Resume 会依次唤醒前面调用了Early Suspend的设备.Wake Lock我们接下来看一看wake lock的机制是怎么运行和起作用的, 主要关注wakelock.c文件就可以了.wake lock 有加锁和解锁两种状态, 加锁的方式有两种, 一种是永久的锁住, 这样的锁除非显示的放开, 是不会解锁的, 所以这种锁的使用是非常小心的. 第二种是超时锁, 这种锁会锁定系统唤醒一段时间, 如果这个时间过去了, 这个锁会自动解除.锁有两种类型:1.WAKE_LOCK_SUSPEND 这种锁会防止系统进入睡眠2.WAKE_LOCK_IDLE 这种锁不会影响系统的休眠, 作用我不是很清楚.在wake lock中, 会有3个地方让系统直接开始suspend(), 分别是:1.在wake_unlock()中, 如果发现解锁以后没有任何其他的wake lock了,就开始休眠2.在定时器都到时间以后, 定时器的回调函数会查看是否有其他的wakelock, 如果没有, 就在这里让系统进入睡眠.3.在wake_lock() 中, 对一个wake lock加锁以后, 会再次检查一下有没有锁, 我想这里的检查是没有必要的, 更好的方法是使加锁的这个操作原子化, 而不是繁冗的检查. 而且这样的检查也有可能漏掉. Suspend当wake_lock 运行 suspend()以后, 在wakelock.c的suspend()函数会被调用,这个函数首先sync文件系统,然后调用pm_suspend(request_suspend_state),接下来pm_suspend()就会调用enter_state()来进入Linux的休眠流程..Android于标准Linux休眠的区别pm_suspend() 虽然会调用enter_state()来进入标准的Linux休眠流程,但是还是有一些区别:∙当进入冻结进程的时候, android首先会检查有没有wake lock,如果没有, 才会停止这些进程, 因为在开始suspend和冻结进程期间有可能有人申请了 wake lock,如果是这样, 冻结进程会被中断.∙在 suspend_late()中, 会最后检查一次有没有wake lock, 这有可能是某种快速申请wake lock,并且快速释放这个锁的进程导致的,如果有这种情况, 这里会返回错误, 整个suspend就会全部放弃.如果pm_suspend()成功了,LOG的输出可以通过在kernel cmd里面增加"no_console_suspend" 来看到suspend和resume过程中的log输出。
【Android休眠】之Android休眠机制⼀、休眠概述休眠,简⽽⾔之就是设备在不需要⼯作的时候把⼀些部件、外设关掉(掉电或让它进⼊低功耗模式)。
为什么要休眠呢?⼀⾔以蔽之:省电。
休眠分主动休眠和被动休眠。
主动休眠:⽐如我电脑不⽤了,就通过设置让系统进⼊休眠模式;被动休眠:系统检测到⾃⼰闲的慌,为了节约故,⾃⼰就休眠去了。
废话不叙。
⼆、Android休眠休眠是内核的核⼼⼯作,⽽Android是基于Linux内核的,所以Android休眠和内核有着千丝万缕的联系;由于Android的特殊应⽤场景:移动设备,所以Android休眠和内核⼜有着特别的需求。
1、联系:Android设备停⽌使⽤,系统没有什么事情可做,进⼊休眠状态的功能最终是由内核去实现的;每⼀类硬件都有⾃⼰的驱动,具体的驱动决定怎么进⼊休眠以及处于何种层次的休眠。
⽐如:对于platform_device,就按照platform_driver定义的规则,在suspend调⽤的时候,去做上⾯提到的事情:struct platform_driver {int (*probe)(struct platform_device *);int (*remove)(struct platform_device *);void (*shutdown)(struct platform_device *);int (*suspend)(struct platform_device *, pm_message_t state);int (*resume)(struct platform_device *);struct device_driver driver;const struct platform_device_id *id_table;};2、Android的特别需求:⽐如对于⾃⼰的电脑,不⽤让它休眠好了;但是对于我们形影不离的⼿机,在休眠的时候还要睁⼀只眼:来电了要通知你,QQ啊微信啊什么的由信息了也要通知你,所以Android在Linux内核休眠机制之上,提出了“Opportunistic Suspend”。
Linux内核与Android的关系对Android感兴趣的朋友都知道,Android系统是建立在Linux内核之上的。
那么Linux内核和Android什么关系?Linux内核是怎样引导起Android呢?本文进行简单的描述。
Android虽然建立在Linux内核之上,但是他对内核进行了一些扩展,增加了一些驱动。
比如Binder,loger等等驱动。
可以拿Android内核代码和其Baseline版本进行对比。
可以看到Android对Linux内核的所有扩展。
熟悉Linux启动的朋友知道,首先Linux引导完成之后,会启动用户态的init进程(pid 为0),这个进程在整个系统运行过程中起着非常重要的作用,如果你对init进程不了解请查相关资料。
init完成系统的初始化工作,然后进入shell,接收用户的输入。
Android启动也没有什么神秘的,就是用自己的init进程替换了Linux内核的init进程,完成自己初始化工作(设备,文件系统等等初始化)。
然后启动自己的虚拟机,程序等等的东西。
Android的init进程的代码位于system/core/init/init.c下面,可以去查看其源码,来了解Android启动详细流程。
Android启动流程的资料网上已经比较多,这里就不赘述了。
可以看到移植Android过程中,调试init非常重要。
因为所有和硬件平台相关的东西都这里初始化,所以init进程有可能需要移植或者配置。
其他的进程都是和硬件无关的,理论上不需要修改就应该能够运行起来。
经过上面的描述可以看出,Android的init进程起着一个承上启下的作用。
承上——自动由Linux内核调用;启下——初始化Android各种设备和资源,然后启动Android相关的进程,直到引导起整个Android系统。
46.Linux电源管理-休眠与唤醒1.休眠⽅式在内核中,休眠⽅式有很多种,可以通过下⾯命令查看# cat /sys/power/state//来得到内核⽀持哪⼏种休眠⽅式.常⽤的休眠⽅式有freeze,standby, mem, diskfreeze:冻结I/O设备,将它们置于低功耗状态,使处理器进⼊空闲状态,唤醒最快,耗电⽐其它standby, mem, disk⽅式⾼standby:除了冻结I/O设备外,还会暂停系统,唤醒较快,耗电⽐其它 mem, disk⽅式⾼mem: 将运⾏状态数据存到内存,并关闭外设,进⼊等待模式,唤醒较慢,耗电⽐disk⽅式⾼disk: 将运⾏状态数据存到硬盘,然后关机,唤醒最慢⽰例:# echo standby > /sys/power/state// 命令系统进⼊standby休眠.2.唤醒⽅式当我们休眠时,如果想唤醒,则需要添加中断唤醒源,使得在休眠时,这些中断是设为开启的,当有中断来,则会退出唤醒,常见的中断源有按键,USB等.3.以按键驱动为例(基于内核3.10.14)在内核中,有个input按键⼦系统"gpio-keys"(位于driver/input/keyboard/gpio.keys.c),该平台驱动platform_driver已经在内核中写好了(后⾯会简单分析)我们只需要在内核启动时,注册"gpio-keys"平台设备platform_device,即可实现⼀个按键驱动.3.1⾸先使板卡⽀持input按键⼦系统(基于mips君正X1000的板卡)查看Makefile,找到driver/input/keyboard/gpio.keys.c需要CONFIG_KEYBOARD_GPIO宏⽅式1-修改对应板卡的defconfig⽂件,添加宏:CONFIG_INPUT=y //⽀持input⼦系统(加载driver/input⽂件)CONFIG_INPUT_KEYBOARD=y //⽀持input->keyboards(加载driver/input/keyboard⽂件)CONFIG_KEYBOARD_GPIO=y //⽀持input->keyboards->gpio按键(加载gpio.keys.c)⽅式2-进⼊make menuconfig-> Device Drivers-> Input device support-> [*]Keyboards[*] GPIO Buttons3.2修改好后,接下来写my_button.c⽂件,来注册platform_device#include <linux/platform_device.h>#include <linux/gpio_keys.h>#include <linux/input.h>struct gpio_keys_button __attribute__((weak)) board_buttons[] = {{.gpio = GPIO_PB(31), //按键引脚.code = KEY_POWER, //⽤来定义按键产⽣事件时,要上传什么按键值.desc = "power key", //描述信息,不填的话会默认设置为"gpio-keys".wakeup =1, //设置为唤醒源. debounce_interval =10, //设置按键防抖动时间,也可以不设置.type = EV_KEY,.active_low = 1, //低电平有效},};static struct gpio_keys_platform_data board_button_data = {.buttons = board_buttons,.nbuttons = ARRAY_SIZE(board_buttons),};struct platform_device my_button_device = {.name = "gpio-keys",.id = -1,.num_resources = 0,.dev = {.platform_data = &board_button_data,}};static int __init button_base_init(void){platform_device_register(&my_button_device);return0;}arch_initcall(button_base_init);上⾯的arch_initcall()表⽰:会将button_base_init函数放在内核链接脚本.initcall3.init段中,然后在内核启动时,会去读链接脚本,然后找到button_base_init()函数,并执⾏它.通常,在内核中,platform 设备的初始化(注册)⽤arch_initcall()调⽤⽽驱动的注册则⽤module_init()调⽤,因为module_init()在arch_initcall()之后才调⽤因为在init.h中定义:#define pure_initcall(fn) __define_initcall(fn, 0)#define core_initcall(fn) __define_initcall(fn, 1)#define core_initcall_sync(fn) __define_initcall(fn, 1s)#define postcore_initcall(fn) __define_initcall(fn, 2)#define postcore_initcall_sync(fn) __define_initcall(fn, 2s)#define arch_initcall(fn) __define_initcall(fn, 3) // arch_initcall()优先级为3,⽐module_init()先执⾏#define arch_initcall_sync(fn) __define_initcall(fn, 3s)#define subsys_initcall(fn) __define_initcall(fn, 4)#define subsys_initcall_sync(fn) __define_initcall(fn, 4s)#define fs_initcall(fn) __define_initcall(fn, 5)#define fs_initcall_sync(fn) __define_initcall(fn, 5s)#define rootfs_initcall(fn) __define_initcall(fn, rootfs)#define device_initcall(fn) __define_initcall(fn, 6) //module_init()优先级为6#define device_initcall_sync(fn) __define_initcall(fn, 6s)#define late_initcall(fn) __define_initcall(fn, 7)#define late_initcall_sync(fn) __define_initcall(fn, 7s)... ...#define __initcall(fn) device_initcall(fn)#define module_init(x) __initcall(fn) //module_init 等于 device_initcall3.3然后将my_button.c⽂件添加到Makefile中编译内核后,便实现⼀个简单的按键唤醒休眠了.接下来开始分析platform_driver(位于driver/input/keyboard/gpio.keys.c),看看是如何注册按键和实现唤醒的.4.分析driver/input/keyboard/gpio.keys.c4.1该⽂件⾥有常⽤的函数有static int gpio_keys_probe(struct platform_device *pdev);设置按键和input_dev,注册input-key⼦系统static int gpio_keys_setup_key(struct platform_device *pdev,struct input_dev *input, struct gpio_button_data *bdata,const struct gpio_keys_button *button);设置GPIO,设置input结构体⽀持的按键值,设置中断,设置防抖动机制static irqreturn_t gpio_keys_irq_isr(int irq, void *dev_id);按键中断函数,如果是中断源,则通过pm_stay_awake()通知pm⼦系统唤醒,如果有防抖动,则延时并退出,否则通过schedule_work()来调⽤gpio_keys_gpio_work_func()⼀次static void gpio_keys_gpio_timer(unsigned long _data);定时器超时处理函数,⽤来实现防抖动,⾥⾯会通过schedule_work()来调⽤⼀次gpio_keys_gpio_work_func();static void gpio_keys_gpio_work_func(struct work_struct *work);处理gpio事件函数,⽤来上报input事件,并判断按键中断源,如果是的话,则调⽤pm_relax(),通知pm⼦系统唤醒⼯作结束void pm_wakeup_event(struct device *dev, unsigned int msec);通知pm(power manager), 唤醒休眠static int gpio_keys_suspend(struct device *dev);休眠函数,休眠之前会被调⽤static int gpio_keys_resume(struct device *dev);唤醒函数,唤醒之前被调⽤static SIMPLE_DEV_PM_OPS(gpio_keys_pm_ops, gpio_keys_suspend, gpio_keys_resume);SIMPLE_DEV_PM_OPS宏位于pm.h,它将会定义⼀个dev_pm_ops结构体,⽤来被pm⼦系统调⽤,实现休眠唤醒4.2 ⾸先来看probe函数如下图所⽰,probe函数为gpio_keys_probe()gpio_keys_probe()函数定义如下所⽰:static int gpio_keys_probe(struct platform_device *pdev){struct device *dev = &pdev->dev; //获取平台设备的.devconst struct gpio_keys_platform_data *pdata = dev_get_platdata(dev); //获取my_button.c⽂件的board_button_data成员struct gpio_keys_drvdata *ddata; //按键驱动数据const struct gpio_keys_platform_data *pdata = dev_get_platdata(dev); //获取平台总线设备数据if (!pdata) {pdata = gpio_keys_get_devtree_pdata(dev);if (IS_ERR(pdata))return PTR_ERR(pdata);}ddata = kzalloc(sizeof(struct gpio_keys_drvdata) +pdata->nbuttons * sizeof(struct gpio_button_data),GFP_KERNEL); //给平台设备数据分配空间input = input_allocate_device(); //分配input 按键⼦系统if (!ddata || !input) {dev_err(dev, "failed to allocate state\n");error = -ENOMEM;goto fail1;}ddata->pdata = pdata;ddata->input = input;mutex_init(&ddata->disable_lock);platform_set_drvdata(pdev, ddata);//将ddata保存到平台总线设备的私有数据。
Linux Kernel and Android休眠与唤醒时间:2010-06-26 21:47:04来源:网络作者:未知点击:1767次版本信息Linux Kernel: v2.6.28Android: v2.0对于休眠(suspend)的简单介绍在Linux中,休眠主要分三个主要的步骤:Android 休眠(suspend)在一个打过android补丁的内核中, state_store()函数会走另外一条路,会进入到request_suspend_state()中, 这个文件在earlysuspend.c中. 这些功能都是android系统加的, 后面会对earlysuspend和late resume 进行介绍.涉及到的文件:linux_source/kernel/power/main.clinux_source/kernel/power/earlysuspend.clinux_source/kernel/power/wakelock.c特性介绍Early SuspendEarly suspend 是android 引进的一种机制, 这种机制在上游备受争议,这里不做评论. 这个机制作用在关闭显示的时候, 在这个时候, 一些和显示有关的设备, 比如LCD背光, 比如重力感应器, 触摸屏, 这些设备都会关掉, 但是系统可能还是在运行状态(这时候还有wake lock)进行任务的处理, 例如在扫描SD卡上的文件等. 在嵌入式设备中, 背光是一个很大的电源消耗,所以android会加入这样一种机制.Late ResumeLate Resume 是和suspend 配套的一种机制, 是在内核唤醒完毕开始执行的. 主要就是唤醒在Early Suspend的时候休眠的设备.Wake LockWake Lock 在Android的电源管理系统中扮演一个核心的角色. Wake Lock是一种锁的机制, 只要有人拿着这个锁, 系统就无法进入休眠, 可以被用户态程序和内核获得. 这个锁可以是有超时的或者是没有超时的, 超时的锁会在时间过去以后自动解锁. 如果没有锁了或者超时了, 内核就会启动休眠的那套机制来进入休眠.Android Suspend当用户写入mem 或者standby到/sys/power/state中的时候, state_store()会被调用, 然后Android会在这里调用request_suspend_state() 而标准的Linux会在这里进入enter_state()这个函数. 如果请求的是休眠, 那么early_suspend这个workqueue就会被调用,并且进入early_suspend状态.void request_suspend_state(suspend_state_t new_state){unsigned long irqflags;int old_sleep;spin_lock_irqsave(&state_lock, irqflags);old_sleep = state & SUSPEND_REQUESTED;if (debug_mask & DEBUG_USER_STATE) {struct timespec ts;struct rtc_time tm;getnstimeofday(&ts);rtc_time_to_tm(_sec, &tm);pr_info("request_suspend_state: %s (%d->%d) at %lld ""(%d-%02d-%02d %02d:%02d:%02d.%09lu UTC)",new_state != PM_SUSPEND_ON ? "sleep" : "wakeup",requested_suspend_state, new_state,ktime_to_ns(ktime_get()),tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday,tm.tm_hour, tm.tm_min, tm.tm_sec, _nsec);}if (!old_sleep && new_state != PM_SUSPEND_ON) {state |= SUSPEND_REQUESTED;queue_work(suspend_work_queue, &early_suspend_work);} else if (old_sleep && new_state == PM_SUSPEND_ON){state &= ~SUSPEND_REQUESTED;wake_lock(&main_wake_lock);queue_work(suspend_work_queue, &late_resume_work);}requested_suspend_state = new_state;spin_unlock_irqrestore(&state_lock, irqflags);}Early Suspend在early_suspend()函数中, 首先会检查现在请求的状态还是否是suspend, 来防止suspend的请求会在这个时候取消掉(因为这个时候用户进程还在运行),如果需要退出, 就简单的退出了. 如果没有, 这个函数就会把early suspend中注册的一系列的回调都调用一次, 然后同步文件系统, 然后放弃掉main_wake_lock, 这个wake lock是一个没有超时的锁,如果这个锁不释放, 那么系统就无法进入休眠.static void early_suspend(struct work_struct *work){struct early_suspend *pos;unsigned long irqflags;int abort = 0;mutex_lock(&early_suspend_lock);spin_lock_irqsave(&state_lock, irqflags);if (state == SUSPEND_REQUESTED)state |= SUSPENDED;elseabort = 1;spin_unlock_irqrestore(&state_lock, irqflags);if (abort) {if (debug_mask & DEBUG_SUSPEND)pr_info("early_suspend: abort, state %d", state);mutex_unlock(&early_suspend_lock);goto abort; }if (debug_mask & DEBUG_SUSPEND)pr_info("early_suspend: call handlers");list_for_each_entry(pos, &early_suspend_handlers, link) {if (pos->suspend != NULL)pos->suspend(pos);}mutex_unlock(&early_suspend_lock);if (debug_mask & DEBUG_SUSPEND)pr_info("early_suspend: sync");sys_sync();abort:spin_lock_irqsave(&state_lock, irqflags);if (state == SUSPEND_REQUESTED_AND_SUSPENDED)wake_unlock(&main_wake_lock);spin_unlock_irqrestore(&state_lock, irqflags);}Late Resume当所有的唤醒已经结束以后, 用户进程都已经开始运行了, 唤醒通常会是以下的几种原因:来电如果是来电, 那么Modem会通过发送命令给rild来让rild通知WindowManager有来电响应,这样就会远程调用PowerManagerService来写"on" 到/sys/power/state 来执行late resume的设备, 比如点亮屏幕等.用户按键用户按键事件会送到WindowManager中, WindowManager会处理这些按键事件,按键分为几种情况, 如果案件不是唤醒键(能够唤醒系统的按键) 那么WindowManager会主动放弃wakeLock来使系统进入再次休眠, 如果按键是唤醒键,那么WindowManger就会调用PowerManagerService中的接口来执行Late Resume.Late Resume 会依次唤醒前面调用了Early Suspend的设备.static void late_resume(struct work_struct *work){ struct early_suspend *pos; unsigned long irqflags; int abort = 0; mutex_lock(&early_suspend_lock); spin_lock_irqsave(&state_lock, irqflags); if (state == SUSPENDED)state &= ~SUSPENDED; else abort = 1;spin_unlock_irqrestore(&state_lock, irqflags); if (abort) { if (debug_mask & DEBUG_SUSPEND) pr_info("late_resume: abort, state %d", state); goto abort; } if (debug_mask &DEBUG_SUSPEND) pr_info("late_resume: call handlers"); list_for_each_entry_reverse(pos, &early_suspend_handlers, link)if (pos->resume != NULL) pos->resume(pos); if (debug_mask & DEBUG_SUSPEND) pr_info("late_resume: done");abort: mutex_unlock(&early_suspend_lock);}Wake Lock我们接下来看一看wake lock的机制是怎么运行和起作用的, 主要关注wakelock.c文件就可以了.wake lock 有加锁和解锁两种状态, 加锁的方式有两种, 一种是永久的锁住, 这样的锁除非显示的放开, 是不会解锁的, 所以这种锁的使用是非常小心的. 第二种是超时锁,这种锁会锁定系统唤醒一段时间, 如果这个时间过去了, 这个锁会自动解除.锁有两种类型:WAKE_LOCK_SUSPEND 这种锁会防止系统进入睡眠WAKE_LOCK_IDLE 这种锁不会影响系统的休眠, 作用我不是很清楚.在wake lock中, 会有3个地方让系统直接开始suspend(), 分别是:在wake_unlock()中, 如果发现解锁以后没有任何其他的wake lock了, 就开始休眠在定时器都到时间以后, 定时器的回调函数会查看是否有其他的wake lock, 如果没有, 就在这里让系统进入睡眠.在wake_lock() 中, 对一个wake lock加锁以后, 会再次检查一下有没有锁, 我想这里的检查是没有必要的, 更好的方法是使加锁的这个操作原子化, 而不是繁冗的检查. 而且这样的检查也有可能漏掉.Suspend当wake_lock 运行suspend()以后, 在wakelock.c的suspend()函数会被调用,这个函数首先sync文件系统,然后调用pm_suspend(request_suspend_state),接下来pm_suspend()就会调用enter_state()来进入Linux的休眠流程..static void suspend(struct work_struct *work){ int ret; int entry_event_num; if (has_wake_lock(WAKE_LOCK_SUSPEND)){ if (debug_mask & DEBUG_SUSPEND)pr_info("suspend: abort suspend"); return; } entry_event_num = current_event_num; sys_sync(); if (debug_mask & DEBUG_SUSPEND)pr_info("suspend: enter suspend"); ret = pm_suspend(requested_suspend_state); if(current_event_num == entry_event_num){ wake_lock_timeout(&unknown_wakeup, HZ / 2); }}Android 于标准Linux休眠的区别pm_suspend() 虽然会调用enter_state()来进入标准的Linux休眠流程,但是还是有一些区别:当进入冻结进程的时候, android首先会检查有没有wake lock,如果没有, 才会停止这些进程, 因为在开始suspend和冻结进程期间有可能有人申请了wake lock,如果是这样, 冻结进程会被中断.在suspend_late()中, 会最后检查一次有没有wake lock, 这有可能是某种快速申请wake lock,并且快速释放这个锁的进程导致的,如果有这种情况, 这里会返回错误, 整个suspend就会全部放弃.如果pm_suspend()成功了,LOG的输出可以通过在kernel cmd里面增加"no_console_suspend" 来看到suspend和resume过程中的log输出。