android 电源管理

1.     介绍.... 4

2.     电池管理.... 4

2.1.      FrameWork.. 5

2.1.1.       监听... 5

2.1.1.1.     UEventObserver的实现... 6

2.1.1.2.     uevent 8

2.1.2.       状态读取... 9

2.1.3.       更新显示... 9

2.2.      驱动... 11

3.     用电统计.... 20


1.       介绍

              首先区分一下电源管理, 电池管理和用电统计三个概念。

l         电源管理:前一篇文章讲的是电源管理, 电源管理的目的是节电, 让设备在空闲的时间进入睡眠状态。

l         电池管理: 电池管理的目的是对电池电量和状态的管理, 比如电量变化时和进入充电状态时更新任务栏上的进度条, 当系统低电量时通知用户, 电量低到一定程度时自动关机等。

l         用电统计:用电统计的目的则是统计系统中一些模块, 服务和应用程序的耗电情况, 并反馈给用户。

       这篇文章是关于电池管理和用电统计的。

2.       电池管理

       Android电池管理的功能其实很简单:

l         监测电池电量的变化并更新显示界面

l         监听进入充电状态和退出充电状态消息并更新界面

2.1.      FrameWork

      



2.1.1.          监听

       在BatteryService定义了UEventObserver, uevent是Linux内核用来向用户空间主动上报事件的机制,关于uevent请参见2.1.1.2节, JAVA中的UEventObserver就用来监听uevent的。

private UEventObserver mUEventObserver = new UEventObserver() {

        @Override

        public void onUEvent(UEventObserver.UEvent event) {

            update();

        }

};

然后调用mUEventObserver.startObserving("SUBSYSTEM=power_supply")只监听属性SUBSYSTEM 为“power_supply”的消息。 也就是说当系统进入充电状态和退出充电状态时, 电量变化时就会调用到这个onUEvent函数来处理变化。

2.1.1.1.    UEventObserver的实现

      UEventObserver的实现分为三层,

UEventObserver.java->android_os_UEventObserver.cpp->hardware\libhardware_legacy\uevent\uevent.c

l         API层是com.android.os.UEventObserver

l         JNI层是android_os_UEventObserver.cpp, 很浅

l         Hal层是hardware\libhardware_legacy\uevent\uevent.c, 直接和内核通信

       当第一次调用mUEventObserver.startObserving会启动一个UEventThread, next_event会返回监听到的uevent事件, 然后再判断是不是我们所关心的, 如果是就会回调我们注册的onUEvent函数。如下:

private static class UEventThread extends Thread {

        public void run() {

            native_setup();

            byte[] buffer = new byte[1024];

            int len;

            while (true) {

                len = next_event(buffer);

                if (len > 0) {

                    String bufferStr = new String(buffer, 0, len);  // easier to search a String

                    synchronized (mObservers) {

                        for (int i = 0; i < mObservers.size(); i += 2) {

                            if (bufferStr.indexOf((String)mObservers.get(i)) != -1) {

                                ((UEventObserver)mObservers.get(i+1))

                                        .onUEvent(new UEvent(bufferStr));

                            }

                        }

                    }

                }

            }

        }

       native_setup()是jni 如下:

static void

android_os_UEventObserver_native_setup(JNIEnv *env, jclass clazz)

{

    if (!uevent_init()) {

        jniThrowException(env, "java/lang/RuntimeException",

                          "Unable to open socket for UEventObserver");

    }

}

    uevent_init()是hal,创建用于监听内核uevent事件的socket, 如下:

int uevent_init()

{

    struct sockaddr_nl addr;

    int sz = 64*1024;

    int s;

    memset(&addr, 0, sizeof(addr));

    addr.nl_family = AF_NETLINK;

    addr.nl_pid = getpid();

    addr.nl_groups = 0xffffffff;

    s = socket(PF_NETLINK, SOCK_DGRAM, NETLINK_KOBJECT_UEVENT);

    if(s < 0)

        return 0;

    setsockopt(s, SOL_SOCKET, SO_RCVBUFFORCE, &sz, sizeof(sz));

    if(bind(s, (struct sockaddr *) &addr, sizeof(addr)) < 0) {

        close(s);

        return 0;

    }

    fd = s;

    return (fd > 0);

}

    next_event是jni,如下:

static int

android_os_UEventObserver_next_event(JNIEnv *env, jclass clazz, jbyteArray jbuffer)

{

    int buf_sz = env->GetArrayLength(jbuffer);

    char *buffer = (char*)env->GetByteArrayElements(jbuffer, NULL);

    int length = uevent_next_event(buffer, buf_sz - 1);

    env->ReleaseByteArrayElements(jbuffer, (jbyte*)buffer, 0);

    return length;

}

    uevent_next_event是hal, 等待uevent事件, 如下:

int uevent_next_event(char* buffer, int buffer_length)

{

    while (1) {

        struct pollfd fds;

        int nr;

  

        fds.fd = fd;

        fds.events = POLLIN;

        fds.revents = 0;

        nr = poll(&fds, 1, -1);

   

        if(nr > 0 && fds.revents == POLLIN) {

            int count = recv(fd, buffer, buffer_length, 0);

            if (count > 0) {

                return count;

            }

        }

    }

  

    // won't get here

    return 0;

}

       至于netlink socket的原理不做过多解释, 我们现在更关心的是我们的电池驱动如何发送出uevent。

2.1.1.2.    uevent

          关于linux设备模型请参考《LDD3》的第14章。

          如果对节点的动态动态创建过程有了解的话, 那么对uevent一定不会陌生。 当发现设备调用device_add函数时, device_add便会调用kobject_uevent函数发出一个uevent。 应用层的udev(当然android是没有udev的, 动态节点创建时由init进程做的)就会监听这个uevent来创建设备节点。 当然当设备移除时也会调用device_remove也会发出一个uevent,udev收到这个消息就删除设备节点。

          每个uevent都包含一些属性, 所有uevent都有如下属性, 下面只列出几个重要的:

l         ACTION: add, remove或change等。

l         SUBSYSTEM:“devices”,“input”,“power_supply”等。

l         DEVPATH:发出uevent的kobject的sys对应路径。

          当然不同的子系统中发出的uevent又会可能有相应的属性发出, 还记得每个kset对应一个子系统吧, 比如/sys/devices, /sys/block, /sys/class/input, /sys/class/power_supply, 当添加一个kset时会为这个kset注册一个kset_uevent_ops。 当一个kobject发出一个uevent时, 就会去寻找这个kobject所属的kset, 然后调用这个kset里面的kset_uevent_ops来添加子系统相关的一些属性。比如power_supply子系统里面发出的uevent都会有POWER_SUPPLY_NAME这个属性, 值可以是“ac”, “usb”或者“battery”。

          当然驱动也是可以为uevent添加属性的, 调用kobject_uevent_env函数发uevent即可,kobject_uevent_env声明如下:

       int kobject_uevent_env(struct kobject *kobj, enum kobject_action action,char *envp_ext[])

       发出去uevent, 应用层就可以通过netlink socket的方式来接收。

2.1.2.          状态读取

       update()函数里会调用nativeUpdate()通过进入c++层, nativeUpdate在com_android_server_BatteryService.cpp中。 Android的Linux 内核中的电池驱动会提供如下sysfs接口给

FrameWork:

/sys/class/power_supply/ac/online
/sys/class/power_supply/ac/type
 
/sys/class/power_supply/usb/online
/sys/class/power_supply/usb/type
 
/sys/class/power_supply/battery/status
/sys/class/power_supply/battery/health
/sys/class/power_supply/battery/present
/sys/class/power_supply/battery/capacity
/sys/class/power_supply/battery/batt_vol
/sys/class/power_supply/battery/batt_temp
/sys/class/power_supply/battery/technology
/sys/class/power_supply/battery/type

       当监听到power_supply变化的消息后, nativeUpdate函数就会重新读取以上sysfs文件获得当前状态。这里应该注意的是读到的capacity为0-100之间的数值, 100代表满电。

2.1.3.          更新显示

       当发现有变化时就会发出BroadCast, 关心电池变化事件的模块就会定义BroadCastReceiver来响应, 比如状态栏。有如下intent会在相应情况下通过BroadCast发出:

l         Intent.ACTION_BATTERY_CHANGED:不仅是电量变换, 包括connected和disconnected等所有变化, 都会发出这个broadcast。

l         Intent.ACTION_POWER_CONNECTED

l         Intent.ACTION_POWER_DISCONNECTED

l         Intent.ACTION_BATTERY_LOW

l         Intent.ACTION_BATTERY_OKAY

       通过在Eclipse中进行search, 找到关心Intent.ACTION_BATTERY_CHANGED的地方有:

l         NotificationManagerService:更新电源灯的更亮度和让通知灯闪几下。

l         PowerManagerService: 代码如下

    private final class BatteryReceiver extends BroadcastReceiver {

        @Override

        public void onReceive(Context context, Intent intent) {

            synchronized (mLocks) {

                boolean wasPowered = mIsPowered;

                mIsPowered = mBatteryService.isPowered();

                if (mIsPowered != wasPowered) {

                    // update mStayOnWhilePluggedIn wake lock

                    updateWakeLockLocked();

                    // treat plugging and unplugging the devices as a user activity.

                    // users find it disconcerting when they unplug the device

                    // and it shuts off right away.

                    // to avoid turning on the screen when unplugging, we only trigger

                    // user activity when screen was already on.

                    // temporarily set mUserActivityAllowed to true so this will work

                    // even when the keyguard is on.

                    synchronized (mLocks) {

                        if (!wasPowered || (mPowerState & SCREEN_ON_BIT) != 0) {

                            forceUserActivityLocked();

                        }

                    }

                }

            }

        }

    }

l         UiModeManagerService: 会申请一个FULL_WAKE_LOCK, keep screen on when charging and in car mode。

l         WifiService:注释如下

 

l         com.android.server.Connectivity.Tethering: 代码如下,还不清楚什么用。

if (action.equals(Intent.ACTION_BATTERY_CHANGED)) {

    mUsbConnected = (intent.getIntExtra(BatteryManager.EXTRA_PLUGGED, -1)

                        == BatteryManager.BATTERY_PLUGGED_USB);

    Tethering.this.updateUsbStatus();

}

l         packages.SystemUI.src.com.android.systemui.statusbar: 更新状态栏电池量的显示。

l         KeyguardUpdateMonitor:代码如下, 还不清楚作用。

private void handleBatteryUpdate(int pluggedInStatus,int batteryLevel) {

        if (DEBUG) Log.d(TAG, "handleBatteryUpdate");

        final boolean pluggedIn = isPluggedIn(pluggedInStatus);

        if (isBatteryUpdateInteresting(pluggedIn, batteryLevel)) {

            mBatteryLevel = batteryLevel;

            mDevicePluggedIn = pluggedIn;

            for (int i = 0; i < mInfoCallbacks.size(); i++) {

                mInfoCallbacks.get(i).onRefreshBatteryInfo(

                        shouldShowBatteryInfo(), pluggedIn, batteryLevel);

            }

       }

}

关心Intent.ACTION_POWER_CONNECTED的地方有:

l         packages.SystemUI.src.com.android.systemui.statusbar: 更新状态栏电池量的显示。

关心Intent.ACTION_POWER_DISCONNECTED的地方有:

关心Intent.ACTION_BATTERY_LOW的地方有:

l         packages.SystemUI.src.com.android.systemui.statusbar: 更新状态栏电池量的显示, 弹出dialog并发出声音提示用户低电量。

关心Intent.ACTION_BATTERY_OKAY的地方有:

l         packages.SystemUI.src.com.android.systemui.statusbar: 更新状态栏电池量的显示。

2.2 驱动

关于驱动实现遵循linux kernel power-supply子系统规范即可, 可参考示例代码较多。

             

3.       用电统计

      

       上图是显示Android系统用电统计结果的界面, 这个界面在Settings->About Phone->Battery use里。

用电统计是对Android电源管理系统缺点的弥补, Android电源管理系统的缺点是只有所有程序设计良好电源管理系统才能工作的出色, 一坏就会坏一锅粥。 当你发现你的系统莫名其妙很快没电了时, 你就会可以通过用电统计找到原因, 以便禁掉某些功能或卸载某些程序。

       com.android.server.BatteryStatsService负责统计, 其它模块比如WakeLock和PowerManagerService会向BatteryStatsService喂数据。BatteryStatsService在ActivityManagerService中创建, 如下所示:

mBatteryStatsService = new BatteryStatsService(new File(systemDir, "batterystats.bin").toString());

可见统计的数据保存在batterystats.bin里面。

Settings程序也是通过BatteryStatsService获得统计数据, 然后把统计数据传入BatteryStatsImpl来分析。

这部分细节比较复杂就先点到为止, 看以后是不是有深入的需要。

4.      实际问题

当由充电状态转到电池供电时,读到电池电压会有0。2v左右跳降。当由电池供电转到充电状态时,读到电池电压会有0。2v左右跳升。这就需要驱动或hal层维护两张电压电量对应表。

1.       介绍

       这是一篇关于Android电源管理的文章, 适合应用程序开发人员和驱动开发人员和FrameWork开发人员阅读。

2.       前言

     Android系统出于节电的需要, 一般应用在用户一段时间无操作的情况下屏幕变暗, 然后进后休眠状态, 此时cpu处于挂起状态, 屏幕处于关闭状态。 用户只能在”设置->声音和显示”中设置所有应用默认的屏幕亮度和系统无操作多久进入睡眠的时间。

     当然这种管理模式并不适合于所有的应用, 比如当看电影的时候总不希望系统隔一段时间就进入睡眠吧。所以Android系统为这种有特殊需求的应用提供了可以不遵守规矩的Api, 通过使用这种Api可以打破默认情况下的电源管理模式。

3.       应用程序开发相关

3.1.      Api的使用

    只需要熟悉android.os.PowerManager和android.os.PowerManager.WakeLock这两个类的Api就足够了, 当需要用到这些Api时, 需要在Manifest.xml添加如下权限:

     <uses-permission android:name="android.permission.WAKE_LOCK" />

     <uses-permission android:name="android.permission.DEVICE_POWER" />

     很重要的一个概念就是WakeLock, 系统中定义了六种WakeLock, 常用的有4个:

 

cpu

Screen

keyboard light

PARTIAL_WAKE_LOCK

on

off

off

SCREEN_DIM_WAKE_LOCK

on

dim

off

SCREEN_BRIGHT_WAKE_LOCK

on

on

off

FULL_WAKE_LOCK

on

on

on

    对WakeLock有两种操作:获取和释放。获取后当系统需要睡眠时就会进入所获取的WakeLock代表的状态。 实例代码如下:

 

PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
PowerManager.WakeLock wl = pm.newWakeLock(PowerManager.SCREEN_DIM_WAKE_LOCK, "My Tag");
wl.acquire();

   ……
   ..cpu on, screen dim, keyboard light off
wl.release();

    如上面代码所示, 如果系统在获取锁后一段时间无操作就会进入SCREEN_DIM_WAKE_LOCK锁所对应的状态。

    另外还有两个比较特殊的锁:

l          ACQUIRE_CAUSES_WAKEUP:一旦有请求锁时强制打开Screen和keyboard light

l          ON_AFTER_RELEASE:当lock被释放后,通过reset user activity timer使屏幕多亮一会儿

关于api更加详细的内容请参考官方文档。

3.24.  注意事项

l        使用WakeLock会增加系统功耗,所以原则上是只有必要的时候才用

l         所有的锁必须成对的使用, 如申请了PARTIAL_WAKE_LOCK, 而没有及时释放,那系统就永远进不了Sleep模式, 这会严重影响手持设备的待机时间。

4.       驱动程序开发相关

    值得注意的是, 写Android的linux驱动时也要记得系统随时都可能suspend。有些驱动需要关心early suspend和later resume, 比如early suspend时framebuffer驱动的dma应该可以休息了。 有些驱动则需要在某个过程中系统不能睡眠, 比如usb gadget driver向pc传文件的过程中系统就不能睡眠, 睡了就传不完了, 这就需要在这个过程前后获得而后释放锁。 有些驱动向某个设备发送命令过程中如果发生suspend, 等到唤醒的时候再接着发可能外设早已timeout, 这时也需要锁。

     驱动相关的wakelock接口如下, 在inclulde/linux/wakelock.h中定义:

 

void wake_lock_init(struct wake_lock *lock, int type, const char *name);

void wake_lock_destroy(struct wake_lock *lock);

void wake_lock(struct wake_lock *lock);

void wake_lock_timeout(struct wake_lock *lock, long timeout);

void wake_unlock(struct wake_lock *lock);

int wake_lock_active(struct wake_lock *lock);

long has_wake_lock(int type);

     示例代码如下:

 

#include <linux/earlysuspend.h>

#include <linux/module.h>

struct wake_lock my_wake_lock;

static int __init my_init(void)

{

wake_lock_init(&my_wake_lock, WAKE_LOCK_SUSPEND, "mylock");

wake_lock(&my_wake_lock);

      //we do not want to suspend here!

wake_unlock(&my_wake_lock);

       return 0;

}

static void  __exit my_exit(void)

{

wake_lock_destroy(&my_wake_lock)

}

module_init(my_init);

module_exit(my_exit);

     early suspend和later resume是Android对linux内核的扩展机制, 当系统持有wakelock时,如果一段时间无用户操作, 就会进入early suspend状态, 所有驱动注册的early suspend回调函数会被调用。当用户开始操作设备时,所有驱动注册的later resume函数会被调用。 当系统中无wakelock时, 驱动注册的early suspend会先于suspend函数调用, 唤醒时later resume会再resume函数后调用。 early suspend机制给系统提供了一种折中的省电模式,让系统在不能睡眠的情况下各个驱动模块也能有机会调整工作模式。 驱动注册early suspend和later resume回调的代码如下:

 

#include <linux/earlysuspend.h>

#include <linux/module.h>

static my_early_suspend(struct early_suspend *h)

{

}

static void my_late_resume(struct early_suspend *h)

{

}

static struct early_suspend my_early_suspend_desc = {

    .suspend = my_early_suspend,

    .resume = my_late_resume,

};

static int __init my_init(void)

{

       register_early_suspend(&my_early_suspend_desc);

       return 0;

}

static void  __exit my_exit(void)

{

    unregister_early_suspend(&my_ early_suspend_desc);

}

module_init(my_init);

module_exit(my_exit);

另外,请慎重在驱动的suspend和resume函数中和内核线程(workqueue或tasklet)中操作wakelock。

5.       FrameWork相关

5.1.      Linux内核的电源管理

    Linux内核的电源管理有两个阶段suspend和resume, 分别在设备睡眠和唤醒的时候进行,需要关心电源管理的驱动程序可以注册suspend和resume函数在睡眠和唤醒时执行。Linux内核提供给应用层的接口是/sys/power/state, 有如下命令:

l        echo mem>/sys/power/state:睡眠,状态保存进ram

l        echo on>/sys/power/state:唤醒

l        echo standby>/sys/power/state

l        echo disk>/sys/power/state:睡眠, 状态保存进disk

还有一些其他命令请查阅网上资料, 有的命令不一定支持。

5.2.      Android对linux内核的修改

    Android linux内核的电源管理系统是基于Linux内核的电源管理系统的扩展,添加了锁的概念, 多出了两个阶段early suspend和later resume,在有锁的情况下进入的睡眠是partial lock状态, 只会执行注册的early suspend函数, 唤醒时只会执行注册的later resume函数。只有在无锁时进入睡眠才会真正suspend,注册的early suspend函数会在suspend函数前执行,注册的later resume函数会在resume函数后执行 。 关心电源管理的android驱动程序可以注册early suspend和later resume和suspend和resume函数在相应阶段执行。 Android linux内核提供给应用层得接口如下:

l        /sys/power/state:只支持mem(睡眠)和on(唤醒)两个参数

l        /sys/power/power_lock: 写入锁的名字即获得锁

l        /sys/power/power_unlock:写入锁的名字即解锁

    状态转换图如下:


图1

    值得注意的是, 其实并不能把Android linux内核的电源管理和Android电源管理分开, Android linux内核的电源管理完全是根据上层需要对linux内核做的修改, 单独并不能工作。 请看情景分析 , later resume过程是在Android FrameWork层收到电源键按下的事件后“echo on>/sys/power/state“进行的。

    TODO:分析Android Linux内核的电源管理系统源码

   

5.3.      Android Power Management

    Android系统的电源管理是基于Android linux内核的电源管理系统, 在锁的基础上又添加了SCREEN_DIM_WAKE_LOCK,SCREEN_BRIGHT_WAKE_LOCK和SCREEN_FULL_WAKE_LOCK。为了尽可能节电, 系统中有一个定时器,每当定时器溢出时就会根据当前持有锁得情况进行判断进入相应的状态。 定时器溢出时间在settings里面可以设置。

    电源管理模块的结构图如下。


       WatchDog负责监测PowerManagerService, 防止PowerManagerService出现死锁。

    PowerManagerService通过LightsService操作屏幕和键盘背光。

        下图是状态转换图, 比较复杂所以没有画出状态转换条件。


图2

    与图1对应的是Suspend和PARTIAL_WAKE_LOCK状态,而另外三个状态都是处于图1中的awake状态之中。 下图把他们统一看做SCREEN_LOCK状态,简化后的状态图如下, 这回从Android api的角度来分析状态的转换的条件。


图3

    从suspend到SCREEN_LOCK的转换是到FULL_SCREEN_WAKE_LOCK, 从PARTIAL_WAKE_LOCK到SCREEN_LOCK的转换则分三种情况:

l        userActivity:到FULL_SCREEN_WAKE_LOCK状态

l        获得FULL_SCREEN_WAKE_LOCK 进入FULL_SCREEN_WAKE_LOCK状态

l        获得DIM_SCREEN_WAKE_LOCK: 进入DIM_SCREEN_WAKE_LOCK状态

l        获得BRIGHT_SCREEN_WAKE_LOCK: 进入BRIGHT_SCREEN_WAKE_LOCK状态

    当然SCREEN_DIM_WAKE_LOCK, SCREEN_BRIGHT_WAKE_LOCK和SCREEN_FULL_WAKE_LOCK之间也是会相互转换的。

  

6.       情景分析

    系统开机后一段时间无用户输入进入睡眠, 过一段时间用户按下电源键将设备唤醒。

过程如下:

l        PowerManagerService定时器溢出

l        由于无锁, 会利用LightsService关闭屏幕背光和键盘背光

l        echo mem>/sys/power/state进入内核态

l        调用所有驱动注册的early suspend函数

l        由于无锁, 会调用所有驱动注册的suspend函数真正进入睡眠

l        按下电源键设备被唤醒, 所有驱动的resume函数被调用

l        WindowManagerService收到/event/input0的按键事件, 调用PowerManagerService.userActivity函数, 随后PowerManagerService会利用LightsService打开屏幕和键盘背光, 并且“echo on>/sys/power/state“进入内核态。

l        内核调用所有驱动的later resume函数,至此唤醒过程结束。

注:在linux最小系统调试电源管理时, echo mem >/sys/power/state进入睡眠, 按电源键唤醒后再echo mem >/sys/power/state将无法进入睡眠。 需手动echo on>/sys/power/state来完成唤醒过程才能再次echo mem >/sys/power/state。

7.       LightsService

     http://wenku.baidu.com/view/1d5b627202768e9951e73852.html

8.       WatchDog

     http://www.limodev.cn/blog/archives/1566

9.       优缺点

优点:节电

缺点:每一个开发者(无论应用和驱动)都需要关心电源管理, 一个程序锁使用不当会影  响整个系统。

  

    Android 对linux内核PowerManager部分的修改没有被linux内核所接收引发了一场大讨论,至于当中缘由和是非,有兴趣请看下面的文章和讨论。

l         http://lwn.net/Articles/388131/

l         http://lwn.net/Articles/318611/

l         \\srv-srd\SHARES\FW\协同开发\Android预研\经验分享\framework\张岱岩\Android2.2电源管理\ suspend_blockers.pdf

下面是自己对一些有见解的讨论的摘录

1.all that suspend blockers do is to tell the system that while suspend is blocked, don't run your normal heuristics.

When suspend is not blocked, the system can run the heuristics to decide if it should suspend or not.

suspend blockers may allow yo to set the system heuristics to be more aggressive, but if you have any apps on the system that do not invoke suspend blockers, you run the risk of suspending too aggressively when those apps are running.

And if an app claims that you should not suspend (by setting a suspend block), then the system will never go to sleep.

this is very similar to cooperative multitasking, when all apps are well written it can work _very_ well (and can be far more efficient than premptive multitasking), but it only takes one badly written app to cripple the system.

There's a reason that there are basically no commonly used cooperative multitasking systems, in the real world they just aren't reliable enough. The only place you can really use them is in embedded situations where you control everything that's running on the system. That no longer includes phones as people can download apps to run on them.

2. Android's implementation of suspend blockers allows it to suspend the system aggressively without worrying about interrupting anything important (such as a download). And for many apps it achieves this without any risk that the app will block suspend no matter how buggy it is, because only some apps have access to the suspend blocker API.

Before an app is installed the user is shown a list of access rights the app is requesting. One access right an app can require is to "prevent the phone from sleeping". As many apps don't need this functionality they don't ask for access to the suspend blocker API, and so can't flatten your battery no matter how buggy they are.

Apps that do require access to the suspend blocker API would need to to exhibit buggy behavior *while holding a lock* in order to drain your battery. Android can also show you which apps are using the most power if you are concerned about your battery life.

3. My understanding is that in the Android phone world what happens is this:

• User A downloads "cow bouncer" and is impressed that now their phone constantly has bouncing cows. However an hour later their phone bleeps to warn the battery is low. The "low battery" screen shows a "Why so soon?" button or link, which implicates "cow bouncer" as the reason. User A chooses to uninstall the "cow bouncer" app because it's a waste of battery life.

• User B downloads "John the ripper Android edition" and sets it to work cracking password hashes. An hour later the phone bleeps due to exhausted battery. User B plugs it into the wall, he doesn't ask why because hey, he was running a password cracker on his phone, stands to reason it will exhaust the battery.

By obligating ordinary developers to ask for this functionality if they need it and then auditing how it is used, Android make them responsible to their users - if you need to block suspend for long periods, you will need to educate your users about why that is, and persuade them that the app functionality is worth the reduced battery life, otherwise they're going to throw your app away and probably warn off other potential customers

10.   PowerManagerService阅读笔记

获得或释放锁时更新mWakeLockState

用户输入或timeout时更新mUserState

setPowerState时根据mWakeLockState|mUserState更新状态

用户输入通过PowerManagerService.userActivity和gotoSleep系列函数注入, 包括电源键

需要注意的是, InputManager和PowerManagerService的接口层com_android_service_PowerManagerService的状态需要通过nativeSetPowerState正确维护

11.  如何充分利用xxpmu的功能

Android的省电模式只有suspend和early suspend。 early suspend是假睡眠, cpu仍然工作, 并没有通过pmu。 xxpmu有4中状态, runtime, 待机, 关机和掉电, 关机状态下系统可以被唤醒, 当电池耗光时进入掉电状态。 其中省电模式有待机和关机。

待机时状态保存在ram, 关机时状态保存在flash, 都可以被唤醒, 关机状态更省电但恢复时间更长。

       根据xx spec,我们只需要在系统进入待机状态之前设置RTC即可, 如果进入待机一段时间内没有本唤醒, RTC会发出RTC_close_D信号使GL5201进入关机状态。

       接下来的问题是, 我们将设置RTC的代码放在哪里?

       下图使android linux内核电源管理的状态转换图, FrameWork部分没有权利直接让系统睡眠, 而是通过sys文件系统向内核发送请求, 内核再根据锁的情况来决定要不要睡眠。 所以只需要修改内核代码, 在睡眠前设置RTC即可。

,

       Android电源管理也是分平台相关部分和平台无关部分, 平台无关部分代码位于kernel/power目录下。 平台相关代码一般放在arch/pm.c(当然文件名不必须是pm.c)文件中。 在pm.c的工作就是实现并在启动时调用suspend_set_ops函数向内核注册一个platform_suspend_ops结构体,platform_suspend_ops结构体里是一些回调函数, 在这些函数里来设置pmu的寄存器。 platform_suspend_ops结构体如下:

 

struct platform_suspend_ops {

       int (*valid)(suspend_state_t state);

       int (*begin)(suspend_state_t state);

       int (*prepare)(void);

       int (*prepare_late)(void);

       int (*enter)(suspend_state_t state);

       void (*wake)(void);

       void (*finish)(void);

       void (*end)(void);

       void (*recover)(void);

};

       这里只有enter函数是必须的, 其它都可以根据实际情况选择性的实现。 我们可以这样实现platform_suspend_ops的enter函数:

 

int platform_enter(suspend_state_t state)

{

set_rtc();

platform_suspend();

clear_rtc();

}

       RTC的超时时间(也就是待机状态向关机状态转换的时间)可以(不必须)由用户设置, 可以通过添加sysfs接口的方式将用户的设置传到内核空间,这样就需要改动Settings程序代码, 在用户设置时和系统启动时调用sysfs接口设置超时时间。

       关于Android linux内核休眠机制的分析请参考《Android linux 内核休眠与唤醒》(http://www.360doc.com/content/10/0722/08/496343_40576726.shtml

       关于嵌入式系统的电源管理方式, 请参考《嵌入式系统动态电源管理技术》(http://blog.csdn.net/colorant/article/details/2827812

     

12.   调试过程中产生的问题

l         一段时间无用户操作, 系统不会进入suspend模式。 但在命令行里echo mem>/sys/power/state可以进入suspend模式。

原因:没有将wakelock编译进内核,导致hardware/libhardware_legacy/power/power.c初始化失败(因为找不到/sys/power/wake_lock和/sys/power/wake_unlock),acquire_wake_lock, set_screen_state等函数全部直接返回error。

l         调试过程中出现了一系列问题, 比如唤醒后马上又进入睡眠, 都是由于唤醒时没有向上报power的input event导致的, 没input event上报导致系统没持有任何wakelock, 而且later resume过程也无法执行到。 EventHub中有一段注释:

 

// Poll for events.  Mind the wake lock dance!

        // We hold a wake lock at all times except during poll().  This works due to some

        // subtle choreography.  When a device driver has pending (unread) events, it acquires

        // a kernel wake lock.  However, once the last pending event has been read, the device

        // driver will release the kernel wake lock.  To prevent the system from going to sleep

        // when this happens, the EventHub holds onto its own user wake lock while the client

        // is processing events.  Thus the system can only sleep if there are no events

        // pending or currently being processed.

        release_wake_lock(WAKE_LOCK_ID);

        int pollResult = poll(mFDs, mFDCount, -1);

        acquire_wake_lock(PARTIAL_WAKE_LOCK, WAKE_LOCK_ID);

l         在驱动suspend函数中申请锁会导致睡眠失败, 系统resume。

l         wakeup wakelock, 也就是唤醒后的第一个锁, 必须是向input子系统发送power事件时获得的锁,  在其它驱动resume函数中申请锁, 如果此锁成为wakeup wakelock,会导致唤醒失败, 系统重新进入睡眠。

l         内核线程在默认情况下是不被冷冻的, 也就是在打印的freezing tasks和restart tasks之间仍然活跃, 而被冷冻的用户进程则在restart tasks之后才开始调度。 所以在内核线程要慎重申请wakelock, 必要时需要在驱动suspend时挂起内核线程, resume时再恢复。

l         如下内核oop:

[  220.980000] CPU 0 Unable to handle kernel paging request at virtual address 0063002e, epc == 801d2df8, ra == 801d4f0c

[  220.980000] Oops[#1]:

[  220.980000] Cpu 0

[  220.980000] $ 0   : 00000000 00000001 00000000 00000000

[  220.980000] $ 4   : 0063002e ffffffff 0063002e 00000000

[  220.980000] $ 8   : 00000022 00000003 00000001 00000002

[  220.980000] $12   : 00000005 fffffffe 00000002 c4653601

[  220.980000] $16   : 80710190 80711000 ffffffff 860fbd94

[  220.980000] $20   : 8071018f 8035f44c 00000e71 80360000

[  220.980000] $24   : 00000000 00000002                 

[  220.980000] $28   : 860fa000 860fbce8 8035f494 801d4f0c

[  220.980000] Hi    : 00000005

[  220.980000] Lo    : 5ae82600

[  220.980000] epc   : 801d2df8 strnlen+0x8/0x4c

[  220.980000]     Not tainted

[  220.980000] ra    : 801d4f0c string.clone.1+0x48/0x10c

[  220.980000] Status: 31009c02    KERNEL EXL

[  220.980000] Cause : 00800008

[  220.980000] BadVA : 0063002e

[  220.980000] PrId  : 00019750 (MIPS 74Kc)

[  220.980000] Modules linked in: gl5201_camera ov2643 sndrv atc260x_audio aotg_monitor g_switch mmc_block atv5201_mmc mmc_core galcore atc260x_pm atc260x_hwmon atc260x_switch_ldo atc260x_dcdc atc260x_ldo atc260x_power atc260x_gpio atc260x_rtc atc260x_onoff atc260x_tp atc260x_core asoc_spi blk2drv blk1drv atv5201_matrixkey atv5201_vout atv5201_fb atv5201_de atv5201_tvout atv5201_lcd atv5201_display atv5201_backlight [last unloaded: aotg_udc]

[  220.980000] Process UEventObserver (pid: 431, threadinfo=860fa000, task=860ef468, tls=40869f00)

[  220.980000] Stack : 803d5ada 803d5ada 80711000 860fbdc8 0063002e 00000000 803d5ab7 803d5ab7

[  220.980000]         80711000 801d6574 803d5ab6 00000000 86227740 86227a00 ffffffff ffffffff

[  220.980000]         ffffffff ffffffff ff0a0004 ffffffff 00000001 80710190 86227b00 86227b00

[  220.980000]         ffffffff 00000000 006f0074 ffffffff 00002719 ffffffff 0000000a 800f8d54

[  220.980000]         80444230 8403ed00 8642c018 86227740 860fbd90 80129ef8 c46535ff 80073460

[  220.980000]         ...

[  220.980000] Call Trace:

[  220.980000] [<801d2df8>] strnlen+0x8/0x4c

[  220.980000] [<801d4f0c>] string.clone.1+0x48/0x10c

[  220.980000] [<801d6574>] vsnprintf+0x270/0x498

[  220.980000] [<800f8d54>] seq_printf+0x44/0x8c

[  220.980000] [<80073460>] print_lock_stat+0x14c/0x364

[  220.980000] [<800736e0>] wakelock_stats_show+0x68/0x138

[  220.980000] [<800f9100>] seq_read+0x1e0/0x4d0

[  220.980000] [<8012a740>] proc_reg_read+0x98/0x108

[  220.980000] [<800d704c>] vfs_read+0xb8/0x148

[  220.980000] [<800d7128>] sys_read+0x4c/0xa4

[  220.980000] [<80012aa4>] stack_done+0x20/0x3c

[  220.980000]

[  220.980000]

[  220.980000] Code: 00000000  10a00010  00001021 <80820000> 1040000b  00852821  08074b86  00801021  80430000

[  220.980000] Disabling lock debugging due to kernel taint

[  221.748000] gl5201_matixkey_drv: key 139 :pressed

[  221.764000] Fatal exception: panic in 5 seconds

[  221.788000] BUG: scheduling while atomic: UEventObserver/431/0x00000002

[  221.808000] Modules linked in: gl5201_camera ov2643 sndrv atc260x_audio aotg_monitor g_switch mmc_block atv5201_mmc mmc_core galcore atc260x_pm atc260x_hwmon atc260x_switch_ldo atc260x_dcdc atc260x_ldo atc260x_power atc260x_gpio atc260x_rtc atc260x_onoff atc260x_tp atc260x_core asoc_spi blk2drv blk1drv atv5201_matrixkey atv5201_vout atv5201_fb atv5201_de atv5201_tvout atv5201_lcd atv5201_display atv5201_backlight [last unloaded: aotg_udc]

[  221.980000] Call Trace:

[  221.988000] [<8031dcac>] dump_stack+0x8/0x34

[  222.000000] [<8031e388>] schedule+0x4b0/0x754

[  222.028000] [<8031efac>] schedule_timeout+0x188/0x340

[  222.044000] [<80044490>] msleep+0x24/0x34

[  222.068000] [<8000fcdc>] die+0x138/0x150

[  222.080000] [<800163b4>] do_page_fault+0x2a4/0x320

[  222.108000] [<80009444>] ret_from_exception+0x0/0x10

[  222.124000] [<801d2df8>] strnlen+0x8/0x4c

[  222.148000] [<801d4f0c>] string.clone.1+0x48/0x10c

[  222.164000] [<801d6574>] vsnprintf+0x270/0x498

[  222.188000] [<800f8d54>] seq_printf+0x44/0x8c

[  222.200000] [<80073460>] print_lock_stat+0x14c/0x364

[  222.228000] [<800736e0>] wakelock_stats_show+0x68/0x138

[  222.244000] [<800f9100>] seq_read+0x1e0/0x4d0

[  222.268000] [<8012a740>] proc_reg_read+0x98/0x108

[  222.284000] [<800d704c>] vfs_read+0xb8/0x148

[  222.308000] [<800d7128>] sys_read+0x4c/0xa4

[  222.320000] [<80012aa4>] stack_done+0x20/0x3c

[  222.348000]

[  222.356000] gl5201_matixkey_drv: [in asoc_keymx_report_released]key 139 :release

[  226.792000] Kernel panic - not syncing: Fatal exception

[  226.820000] Rebooting in 5 seconds..

很可能是驱动对wake_lock api的错误使用导致:

Wake lock在使用前要调用wake_lock_init。

Wake lock释放前要调用Wake_lock_destroy。

wake lock错误使用的例子:

(1)     wakelock作为函数局部变量, 函数退出前没调用wake_lock_destroy

如果某某驱动在释放wakelock的内存前没调Wake_lock_destroy, kernel的wakelock管理系统仍然会持有指向已经释放的wakelock的指针, 从而导致类似这种错误。

用户态操作wakelock通过/systemfs接口, 内存由kernel的wakelock管理系统分配, 不存在此类担心。

所以用到wakelock驱动, 还应确定对wakelock的使用是否正确。

 

 

总体上来说Android的电源管理还是比较简单的, 主要就是通过锁和定时器来切换系统的状态,使系统的功耗降至最低,整个系统的电源管理架构图如下: (注该图来自Steve Guo)

Android power management block diagram

接下来我们从Java应用层面, Android framework层面, Linux内核层面分别进行详细的讨论:

应用层的使用:

Android提供了现成android.os.PowerManager类,该类用于控制设备的电源状态的切换.

该类对外有三个接口函数:

         void goToSleep(long time); //强制设备进入Sleep状态

         Note:

尝试在应用层调用该函数,却不能成功,出现的错误好象是权限不够, 但在Framework下面的Service里调用是可以的.

         newWakeLock(int flags, String tag);//取得相应层次的锁

flags参数说明:

PARTIAL_WAKE_LOCK: Screen off, keyboard light off

SCREEN_DIM_WAKE_LOCK: screen dim, keyboard light off

SCREEN_BRIGHT_WAKE_LOCK: screen bright, keyboard light off

FULL_WAKE_LOCK: screen bright, keyboard bright

ACQUIRE_CAUSES_WAKEUP: 一旦有请求锁时强制打开Screen和keyboard light

ON_AFTER_RELEASE: 在释放锁时reset activity timer

Note:

如果申请了partial wakelock,那么即使按Power键,系统也不会进Sleep,如Music播放时

如果申请了其它的wakelocks,按Power键,系统还是会进Sleep

         void userActivity(long when, boolean noChangeLights);//User activity事件发生,设备会被切换到Full on的状态,同时Reset Screen off timer.

Sample code:

         PowerManager pm = (PowerManager)getSystemService(Context.POWER_SERVICE);

PowerManager.WakeLock wl = pm.newWakeLock (PowerManager.SCREEN_DIM_WAKE_LOCK, “My Tag”);

         wl.acquire();

         …….

         wl.release();

Note:

1. 在使用以上函数的应用程序中,必须在其Manifest.xml文件中加入下面的权限:

    <uses-permission android:name="android.permission.WAKE_LOCK" />

<uses-permission android:name="android.permission.DEVICE_POWER" />

2. 所有的锁必须成对的使用,如果申请了而没有及时释放会造成系统故障.如申请了partial wakelock,而没有及时释放,那系统就永远进不了Sleep模式.


Android Framework层面:

其主要代码文件如下:

frameworks/base/core/java/android/os/PowerManager.java

frameworks/base/services/java/com/android/server/PowerManagerService.java

frameworks/base/core/java/android/os/Power.java

frameworks/base/core/jni/android_os_power.cpp

hardware/libhardware/power/power.c

其中PowerManagerService.java是核心, Power.java提供底层的函数接口,与JNI层进行交互, JNI层的代码主要在文件android_os_Power.cpp中,与Linux kernel交互是通过Power.c来实现的, Andriod跟Kernel的交互主要是通过sys文件的方式来实现的,具体请参考Kernel层的介绍.

这一层的功能相对比较复杂,比如系统状态的切换,背光的调节及开关,Wake Lock的申请和释放等等,但这一层跟硬件平台无关,而且由Google负责维护,问题相对会少一些,有兴趣的朋友可以自己查看相关的代码.


Kernel层:

其主要代码在下列位置:

drivers/android/power.c

其对Kernel提供的接口函数有

EXPORT_SYMBOL(android_init_suspend_lock); //初始化Suspend lock,在使用前必须做初始化

EXPORT_SYMBOL(android_uninit_suspend_lock); //释放suspend lock相关的资源

EXPORT_SYMBOL(android_lock_suspend); //申请lock,必须调用相应的unlock来释放它

EXPORT_SYMBOL(android_lock_suspend_auto_expire);//申请partial wakelock, 定时时间到后会自动释放

EXPORT_SYMBOL(android_unlock_suspend); //释放lock

EXPORT_SYMBOL(android_power_wakeup); //唤醒系统到on

EXPORT_SYMBOL(android_register_early_suspend); //注册early suspend的驱动

EXPORT_SYMBOL(android_unregister_early_suspend); //取消已经注册的early suspend的驱动

提供给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" //指示当前系统的状态

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的队列,等待下次的调用.

在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释放资源.

系统的状态:

         USER_AWAKE, //Full on status

         USER_NOTIFICATION, //Early suspended driver but CPU keep on

         USER_SLEEP // CPU enter sleep mode

其状态切换示意图如下:

system state machine

系统正常开机后进入到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一起使用,而不是单独的使用一个队列来进行管理.

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值