Android电源管理基础知识整理

作者: GrayMonkey | 来源:发表于2017-11-28 22:47 被阅读257次

    前言

    本文主要围绕如下问题进行知识收集整理:

    1. 待机、睡眠与休眠的区别?
    2. Android开发者官网当中提到“idle states”,该如何理解,这个状态会对设备及我们的程序造成何种影响?
    3. 进入Doze模式中的idle状态,我们的程序还能运行吗?
    4. 手机睡眠之后,为何我们写Alarm程序、来电显示程序依旧会生效?

    如果你也有以上疑问,那么本文会对你解开疑惑有一定的帮助

    ACPI简介

    要理解第一个问题,得先从ACPI(高级配置与电源接口)说起,ACPI是一种规范(包含软件与硬件),用来供操作系统应用程序管理所有电源接口。
    ACPI将计算机系统的状态划分为四个全局状态(G0-G3),共7个状态,其中G0对应S0;G1将低功耗状态细分为四个状态,对应S1-S4;G2、G3代表关机状态分别对应S5、S6。

    ACPI State Description
    S0 正常工作状态
    S1 CPU与RAM供电正常,但CPU不执行指令
    S2 比S1更深的一个睡眠层次,这种模式通常不采用
    S3 挂起到内存
    S4 挂起到硬盘
    S5 Soft Off,CPU、外设等断电,但电源依旧会为部分极低耗设备供电
    S6 Mechanical Off,全部断电

    这里只需要对ACPI的七个状态有个大致了解即可,下一节会有具体的例子来说明各个状态。

    Linux系统电源状态

    在Linux操作系统中,将电源划分为如下几个状态:

    ACPI State Linux State Description
    S0 On(on) Working
    S1 Standby(standby) CPU and RAM are powered but not executed
    S2 ------ ------
    S3 Suspend to RAM(mem) CPU is Off,RAM is powered and the running content is saved to RAM
    S4 Suspend to Disk(disk) All content is saved to Disk and power down
    S5 Shutdown Shutdown the system

    On:正常工作状态

    STR(Suspend to RAM):
    挂起到内存,俗称待机、睡眠(Sleep),进入该状态,系统的主要工作如下:
    1、将系统当前的运行状态等数据保存在内存中,此时仍需要向RAM供电,以保证后续快速恢复至工作状态
    2、冻结用户态的进程和内核态的任务(进入内核态的进程或内核自己的task)
    3、关闭外围设备,如显示屏、鼠标等,中断唤醒外设不会关闭,如电源键
    4、CPU停止工作
    Standby也属于睡眠的一种方式,属于浅睡眠。该模式下CPU并未断电,依旧可以接收处理某些特定事件,视具体设备而定,恢复至正常工作状态的速度也比STR更快,但也更为耗电。举个例子来说,以该方式进入睡眠时,后续通过点击键盘也能将系统唤醒。而以mem进入的睡眠为深度睡眠,只能通过中断唤醒设备唤醒系统,如电源键(此时按电源键,不会经过正常的开机流程的BIOS、BOOTLOAD等),此时按键盘是无法唤醒系统的。

    STD(Suspend to Disk):
    挂起到硬盘,俗称休眠(Hibernation)将系统当前的运行状态等数据保存到硬盘上,并自动关机。下次开机时便从硬盘上读取之前保存的数据,恢复到休眠关机之前的状态。
    譬如在休眠关机时,桌面打开了一个应用,那么下一次开机启动时,该应用也处于打开状态。而正常的关机-开机流程,该应用是不会打开的。

    Linux内核代码声明如下,位于kernel/power/suspend.c

    状态定义.png

    在新版内核中,进程freeze的功能被单独抽离出来作为一个电源状态,该状态仅仅是冻结进程,并不会使系统进入低功耗状态(如切断CPU时钟源、关闭外设供电等)。
    相关宏定义位于:linux/include/linux/suspend.h

    宏定义.png

    其中状态4就是STD,所谓的休眠状态(Hibernation)

    小结:
    至此,我们可以知道,睡眠与休眠是2个不同的概念,睡眠属于STR,而休眠属于STD,切勿混为一谈。
    网上也有很多关于“Android休眠”的文章,事实上,Android手机压根儿就不支持休眠模式。

    查看Linux支持的电源模式

    #查看系统支持的电源模式
    $ cat /sys/power/state
    #休眠系统命令
    $ sudo pm-hibernate
    
    Ubutu-17.0.4

    看来Ubuntu-17.0.4版本是不支持休眠功能了,state当中并没有disk,执行休眠命令也提示找不到。
    在公司测试Ubuntu-16.0.4是支持休眠的,休眠时会将当前RAM中的数据保持至swap分区,以供后续恢复。


    Ubuntu-16.0.4

    查看Android支持的电源模式

    Android M

    这里我使用的是模拟器查看的,真机也一样,Android手机是不支持休眠模式的,休眠模式需要一块与RAM大小一致存储空间,这在移动设备上可是个不小的开销。

    Idle State

    Android上的Idle状态分为二类:Cpu Idle和Device Idle

    Cpu Idle

    Linux系统运行的基础是基于进程调度,实际上内核调度的线程(task),内核并不会区分线程与进程,都将他们当做一个线程(task)来处理;当所有的进程都没事儿干的时候,系统就会启用idle进程,使系统进入低功耗状态(如关闭一些服务、模块功能,降低CPU工作频率等),即idle状态,以达到省电的目的。

    idle状态又可以划分为不同的层级,以MTK的芯片为例,通常划分为以下几个状态:

    状态 描述
    soidle(screen on idle) 亮屏 Idle 模式,该模式下与正常工作状态差别不大,唯一的区别就cpu处于空闲状态
    rgidle 浅度 Idle 模式,cpu处于 WFI(wait for interrupt),屏幕熄灭,同时关闭一些不需要的服务及模块,注意此状态cpu的时钟源与RTC模块是工作正常的,此时是可以通过TimerTask的定时触发激活系统的,TimerTask依赖于CPU的RTC模块,而Alarm则依赖于PMIC的RTC模块
    dpidle(deep idle) 深度idle模式,该模式下cpu的时钟源和hrtimer(高精度定时器模块(RTC))被关闭,所有进程(包括系统进程)被冻结,即进入上文所述的睡眠状态

    idle进程是由原始进程(pid=0)在初始化init进程(pid=1)之后演变而来,可以说是init进程的祖先,关于其详细介绍可参考如下链接:
    Linux Idle基础
    魅族内核团队:CPUIDLE 之低功耗定时器

    Device Idle

    Device Idle属于Doze模式中概念,即指当手机屏幕熄屏、不充电、静置不动,有网友分析了源码,指出6.0手机需要静置1时4分30秒才能进入Doze模式。

    Doze模式的限制
    网络接入被暂停
    系统忽略wake locks
    标准的AlarmManager alarms(包括setExact()和setWindow())被延缓到下一个maintenance window
    如果你需要在Doze状态下启动设置的alarms,使用setAndAllowWhileIdle()或者setExactAndAllowWhileIdle()。当有setAlarmClock()的alarms启动时,系统会短暂退出Doze模式
    系统不会扫描Wi-Fi
    系统不允许sync adapters运行
    系统不允许JobScheduler运行

    结合上文分析的cpu idle不难发现Doze模式中的idle状态在概念属于浅idle状态,只是关闭了一些特定服务和模块,并非立即进入睡眠,当然这个过程当中依旧有可能满足睡眠条件而进入睡眠状态,至于如何进入请参考下文【睡眠触发入口】一节。

    Android Doze模式源码分析

    Android电源管理框架

    Android采用linux内核,所以电源状态整体上是与linux操作系统相同,下图是Android的电源管理框架:

    电源管理框架

    WakeLock

    唤醒锁,一种锁机制,用于阻止系统进入睡眠状态,只要有应用获取到改锁,那么系统就无法进入睡眠状态。
    该机制起初是早期Android为Linux内核打得一个补丁,并想合入到linux内核,但被Linux社区拒绝,后续Linux内核引入自己的Wakelock机制,Android系统也使用的是linux的Wakelock机制,所以该机制并非Android特有的机制。

    Android系统提供了两种类型的锁,每一个类型又可分为超时锁与普通锁,超时锁,超时会自动释放,而普通锁则必需要手动释放:

    类型 描述
    WAKE_LOCK_SUSPEND 阻止系统进入睡眠状态(STR)
    WAKE_LOCK_IDLE 阻止系统从idle进程进入那些具有较大中断时延、禁用了较多中断源的低功耗状态(睡眠除外),持有该类型的锁,不影响系统进入睡眠状态。自Android API-17(对应android linux内核版本3.4)移除了该类型的唤醒锁。

    中断时延:计算机接收到中断信号到操作系统作出响应,并完成转入中断服务程序(ISR)的时间。

    内核当中关于WakeLock的主要源码位于:
    kernel_common/include/linux/wakelock.h
    kernel_common/kernel/power/wakelock.c

    Android Linux内核3.0版本
    Android Linux内核3.4版本

    应用层提供的锁类型如下,这些锁都需要手动释放:

    FLAG CPU 屏幕 键盘
    PARTIAL_WAKE_LOCK 开启 关闭 关闭
    SCREEN_DIM_WAKE_LOCK 开启 变暗 关闭
    SCREEN_BRIGHT_WAKE_LOCK 开启 变亮 关闭
    FULL_WAKE_LOCK 开启 变亮 变亮
    锁的释放

    Linux3.4内核中摒弃了之前的wakelock机制,引入wakeup source机制来进行睡眠管理,为了保证上层接口不变,Android的Linux内核便将wakeup source包装成wakelock,WakeLock的数据结构如下:


    wakelock数据结构

    当我们应用层释放锁之后,它并不会马上消失。wakelock分为激活和非激活状态,非激活状态300S之内,无人在申请wakelock,那么它将从红黑二叉树,LRU链表当中删除,如此便可复用锁,节省系统开销。

    睡眠触发入口

    在wakelock中,有3个地方可以让系统从early_suspend进入suspend状态。

    1. wake_unlock,系统每释放一个锁,就会检查是否还存其他激活的wakelock,若不存在则执行Linux的标准suspend流程进入睡眠状态
    2. 在超时锁的超时回调函数,判断是否存在其他激活的wakelock,若不存在,则进入睡眠状态
    3. autosleep机制,android 4.1引入该机制,亮屏时会向autosleep节点写入off,熄屏则会写入mem。Android一灭屏,就会尝试进入睡眠,失败之后系统处于idle进程超过一定时间,则又尝试进入睡眠,判断标准同上,若存在wakelock则进入失败


    关于autosleep机制的内核源码分析,可以参考如下文章:
    Android autosleep机制

    Early Suspend

    预挂起机制是Android特有的挂起机制, 这个机制作用是关闭一些与显示相关的外设,比如LCD背光、重力感应器、 触摸屏,但是其他外设如WIFI、蓝牙等模块等并未关闭。
    此时,系统依旧可以处理事件,如音乐播放软件,息屏后依旧能播放音乐。
    需要注意的是Early Suspend机制与WakeLock机制相互独立,就算有应用持有wakelock锁,系统依旧可以通过Early Suspend机制关闭与显示相关的外设。
    注意:
    Android 4.4起,也就是引入ART的版本,摒弃了early suspend机制,改用了fb event通知机制,即后续版本只有suspend、resume以及runtime suspend、runtime resume。

    Late Resume

    迟唤醒机制,用于唤醒预挂起的设备

    睡眠状态转换

    一般情况下,当我们息屏后,系统将先通过Early Suspend机制进入Idle状态,如果满足进入睡眠的条件(没有进程持有唤醒锁)则会通过Linux的Suspend机制进入Sleep(睡眠)状态。

    睡眠状态转换
    内核源码流程分析可参考如下文章:
    源码位于kernel_common/kernel/power/main.c
    Android中休眠与唤醒之wake_lock, early_suspend, late_resume

    看到这儿,不知你是否疑问,既然系统睡眠了,CPU断电不执行指令了,为何我们定的Alarm会生效以及能接收到来电?

    手机来电与Alarm为何能唤醒系统

    原来Android在硬件架构上将处理器分为二类:Application Processor(AP)和Baseband Processor(BP),AP是ARM架构的处理器,用于运行Linux+Android系统,耗电量高;BP用于运行实时操作系统(RTOS),用于处理手机通信,耗电量低。


    性能优化典范截图

    当AP进入睡眠,有来电时,Modem(调制解调器)将唤醒AP;而我们平时所用的Alarm在硬件上则是依赖PMIC(电源管理芯片)中的RTC模块,所以即使AP断电进入睡眠,我们定的闹钟依旧会生效。


    若想更深入的了解,则可参考Android RIL机制相关的文章。

    总结

    1. 待机、睡眠与休眠的区别
      实际上待机(standby)与睡眠(mem)属于不同模式,但现在大多操作系统都不支持待机模式了,我们也习惯将待机等同于睡眠,睡眠属于STR,休眠属于STD,Android手机不支持休眠!!!

    2. Android开发者官网当中提到“idle state”,该如何理解,这个状态会对设备及我们的程序造成何种影响
      所谓的idle状态,就是指系统进入某个低功耗状态,以MTK为例,常见的状态有soidle、rgidle以及dpidle。rgidle只是限制我们程序使用某些模块,如Doze模式中不能访问网络;而dpidle则会冻结所有进程,系统进入睡眠。

    3. 进入Doze模式中的idle状态,我们的程序还能运行吗?
      Doze模式中的idle概念上属于rgidle状态,此时我们的程序是能运行的,只是不能访问网络等,但是在这个过程中,系统可能会满足进入睡眠条件,冻结所有进程,这样我们的程序就不会得到执行。
      可以自己写个死循环的线程(普通线程,非looper线程),强制手机进入Doze的idle模式,你会发现你的程序依旧在执行,但是静置在哪儿一段时间后,你会发现你的线程被冻结,不会执行,当你点亮屏幕,你的线程又会继续工作。

    4. 手机睡眠之后,为何我们写Alarm程序、来电显示程序依旧会生效?
      Android在硬件架构上将处理器分为AP与BP,应用程序运行与AP之中,睡眠只是将AP断电,BP(Modem)不会断电,当有来电时,BP将会唤醒AP。
      Alarm在硬件上依赖的是Modem中的PMIC的RTC模块,而不是AP中的RTC模块,当定时器触发时,可以唤醒AP,使我们的Alarm程序依旧会得到执行

    参考文章
    维基百科ACPI
    Linux电源管理子系统专题
    Android wacklock获取、释放流程分析

    相关文章

      网友评论

        本文标题:Android电源管理基础知识整理

        本文链接:https://www.haomeiwen.com/subject/tzivdxtx.html