wakelock实现

5010阅读 0评论2017-08-08 wibnmo
分类:Android平台

wakelock申请流程:


kernel/power/
drivers/base/power/
drivers/hisi/hi3xxx/hisi_lpregs.c
drivers/hisi/hi3xxx/pm.c


frameworks/base/core/java/android/os/PowerManager.java
frameworks/base/services/core/java/com/android/server/power/PowerManagerService.java
frameworks/base/services/core/jni/com_android_server_power_PowerManagerService.cpp
hardware/libhardware_legacy/power/power.c




PowerManager.java
public void acquire()
public void acquire(long timeout)
对外提供以上两个接口


两个acquire都会调用到:
acquireLocked
mService.acquireWakeLock(mToken, mFlags, mTag, mPackageName, mWorkSource,
                            mHistoryTag); //通过binder调用到acquireWakeLock@PowerManagerService.java


PowerManagerService.java
acquireWakeLock-->acquireWakeLockInternal-->updatePowerStateLocked-->updateSuspendBlockerLocked
updateSuspendBlockerLocked
根据当前系统状态,判断需要调用
mWakeLockSuspendBlocker.acquire()
还是
mWakeLockSuspendBlocker.release()


在PowerManagerService的构造函数里:
mWakeLockSuspendBlocker = createSuspendBlockerLocked("PowerManagerService.WakeLocks");
mDisplaySuspendBlocker = createSuspendBlockerLocked("PowerManagerService.Display");


    private SuspendBlocker createSuspendBlockerLocked(String name) {
        SuspendBlocker suspendBlocker = new SuspendBlockerImpl(name);
        mSuspendBlockers.add(suspendBlocker);
        return suspendBlocker;
    }


private final class SuspendBlockerImpl implements SuspendBlocker {
        @Override
        public void acquire() {
nativeAcquireSuspendBlocker(mName); //mName是在SuspendBlockerImpl构造函数里传入的,即createSuspendBlockerLocked时指定的名字
}
}
 
nativeAcquireSuspendBlocker(mName); //com_android_server_power_PowerManagerService.cpp
acquire_wake_lock(PARTIAL_WAKE_LOCK, name.c_str()); // 如果按以上流程下来,这里的name就是"PowerManagerService.WakeLocks"


acquire_wake_lock //hardware/libhardware_legacy/power/power.c
write(fd, id, strlen(id)) //fd是open("/sys/power/wake_lock", O_RDWR | O_CLOEXEC)返回的文件描述符

wake_lock_store //kernel/power/main.c
pm_wake_lock(buf);
wakelock_lookup_add(buf, len, true);


wakelock_lookup_add //kernel/power/wakelock.c
先从wakelocks_tree里按名字查找要添加的wakelock是否已经存在,存在就return找到的wakelock,不存在再往下执行
wakeup_source_add(&wl->ws) //内核的wakelock其实只做记录,记录系统中所有申请的wakelock,真正阻止休眠的是wakeup source框架
rb_insert_color(&wl->node, &wakelocks_tree) //wakelocks_tree记录系统所有wakelock
wakelocks_lru_add(wl) //用于wakelock回收




class WakeLock implements IBinder.DeathRecipient
PMS的WakeLock实现了DeathRecipient接口。根据Binder系统的知识可知,当Binder服务端死亡后,Binder系统会向注册了讣告接收的Binder客户端发送讣告通知,因此客户端可以做一些资源清理工作。
在本例中,PM.WakeLock是Binder服务端,而PMS.WakeLock是Binder客户端。假如PM.WakeLock所在进程在release唤醒锁(即WakeLock)之前死亡,PMS.WakeLock的binderDied函数则会被调用,这样,PMS也
能及时进行释放(release)工作。对于系统的重要资源来说,采用这种安全保护措施尤其必要。


applogcat中的wakelock日志:
PowerManagerService: acquireWakeLockInternal: lock=165206168, flags=0x1000001a, packageName=lte.trunk.terminal.tmophone, ws=null, uid=1001, pid=1667
日志中flags=0x1000001a是指持有的wakelock类型,对于PARTIAL_WAKE_LOCK类型的wakelock不会打印日志
public static final int FULL_WAKE_LOCK = 0x0000001a; //PowerManager.java
--------------------------------------------------------------------------------
wakelock日志周期修改:


wakelock日志记录源码:vendor/huawei/chipset_common/modules/logs/sleeplogcat/sleeplogcat.c
启动:service sleeplogcat /system/bin/sleeplogcat -t 2 -p /data/android_logs/sleeplog/ -f /system/etc/pwrlog.cfg      这行代码在文件vendor/huawei/extra/rc_files/init.platform.rc
-t是时间,单位是秒
wakelock是20s记录一次,这个时间太长,查问题有时候需要修改记录时间,比方改为1s。
改动方法:
sleeplogcat.c文件有一个函数:
cat_node() {
/*
        int index;
        for (index=0;index         }
*/
--> sleep(1);
}
g_timedelay在main初始化,是上面service命令传过来的值-t 2,即2s。
--------------------------------------------------------------------------------
wakelock日志解读与wakeup source阻止系统休眠的实现:


name active_count event_count wakeup_count expire_count active_since total_time max_time last_change
PowerManagerService.WakeLocks 79 79 8 0 1015 53959 15018 871206
PowerManagerService.Display 7 7 1 0 708 91250 28823 871513
Mains       1 1 0 0 0 0 0 1213
USB         1 1 0 0 0 0 0 1213
Battery     27 28 0 0 0 34 6 829790
usb_wake_lock 1 1 1 0 0 660630 660630 829790
autosleep   0 0 0 0 0 0 0 180




active_count
当一个wakeup event被处理且产生该wakeup event的wakeup source处于inactive状态时,计数增加


event_count
当一个wakeup event被处理时,计数增加


wakeup_count
在休眠最后一步suspend_enter函数被调用时,会通过pm_wakeup_pending函数检查系统中是否存在active wakeup source,如果有,那么suspend失败,计数增加


expire_count
pm_wakeup_event/wake_lock_timeout调用会设置一个timeout值,timeout时间到计数增加


active_since
在wakeup框架对应active_time,inactive时为0,active时计算方法为,每次打印日志时取当前时间t,然后减去last_time,这个差就是active_time


total_time
在active时更新,保存着active_time的累加值


max_time
在active时才有机会更新,每次打印日志时,判断active_time的时间,如果这次active_time比上次active_time大,那么更新max_time,如果小不更新


last_change
在wakeup框架对应last_time,active/inactive时会取当时时间,然后赋给last_time






对active_count/event_count的说明:
通俗讲,就是调用wakeup source激活函数时,wakeup source会变成active状态,然后active_count/event_count计数有增加的机会
调用wakeup source吊销函数时wakeup source会变成inactive状态


有时候会看到event_count的计数比active_count多,是因为比方调用了一次wake_lock,但还未调用wake_unlock,wakeup source还处于active状态,
此时又调用了一次wake_lock,那么只增加event_count,active_count不变






wakeup source激活函数:wake_lock/wake_lock_timeout/pm_wakeup_event/pm_stay_awake
wakeup source吊销函数:wake_unlock/pm_relax


这些函数在什么时候使用呢?


wake_lock/wake_lock_timeout
驱动中我们很常用


pm_wakeup_event
和wake_lock_timeout相同,都会设置timeout时间,pm_wakeup_event例子可参考gpio_keys驱动


pm_stay_awake/__pm_stay_awake/__pm_wakeup_event
power_supply/wifi驱动会调用pm_stay_awake
用户空间申请wakelock最终会往sys/power/wake_lock写值,到内核空间是wake_lock_store@kernel/power/main.c,wake_lock_store调用了pm_wake_lock@wakelock.c,
pm_wake_lock又调用到了__pm_stay_awake/__pm_wakeup_event




struct device {
struct dev_pm_info  power;
};
struct dev_pm_info {
struct wakeup_source    *wakeup;
};


从以上结构体可以看出,内核里的设备就是一个wakeup source,
用户空间通过PowerManagerService获取wake lock,间接调用到内核wakeup框架,所以用户空间进程也可以是一个wakeup source,
wakeup source产生一个wakeup event。


“wakeup event被处理”具体做什么处理呢?其实就是更新wakeup相关的计数,具体是更新event_count,wakeup_count,active_count,last_time,combined_event_count这些变量


wake_lock我们通常理解为持有一个锁,如果不想系统休眠那么调用该函数。其实从内核角度讲,看到的是计数。休眠时会判断计数,满足条件才休眠。


那么休眠时是如何判断计数的呢?
通过两个变量:
原子变量combined_event_count
无符号整型变量saved_count


static atomic_t combined_event_count = ATOMIC_INIT(0);
高16位保存着registered wakeup events,registered表示已经处理过的,当前处于inactive状态,所以registered wakeup events的意思是系统中阻止休眠的wakeup event总数,
用低16位保存着wakeup events in progress,意思是当前正在处理的wakeup event。


atomic_add_return(MAX_IN_PROGRESS, &combined_event_count)
这行代码执行完,registered wakeup events会加1,wakeup events in progress会减1。


atomic_add_return是原子操作,由arm汇编实现,简单可理解为combined_event_count = combined_event_count + MAX_IN_PROGRESS
比如,
combined_event_count = 0x00060001
MAX_IN_PROGRESS 即 0x0000ffff


MAX_IN_PROGRESS定义如下:
#define IN_PROGRESS_BITS    (sizeof(int) * 4)
#define MAX_IN_PROGRESS     ((1 << IN_PROGRESS_BITS) - 1)


那么,
combined_event_count = combined_event_count + MAX_IN_PROGRESS = 0x00060001 + 0x0000ffff = 0x00070000
实现了高16位加1,低16位减1。


combined_event_count值会在两个地方更新,wakeup_source_activate/wakeup_source_deactivate两个函数。


当调用wakeup source激活函数时,会调用到wakeup_source_activate函数,在此函数里会对combined_event_count低16位加1,说明有wakeup event正在处理。
当调用wakeup source吊销函数时,会调用到wakeup_source_deactivate函数,在此函数里会通过atomic_add_return对高16位加1,低16位减1。


saved_count在pm_save_wakeup_count函数中更新,值为combined_event_count的高16位,表示系统中阻止休眠的wakeup event历史总数。
在pm_wakeup_pending函数有两个判断条件,
1. 取一次combined_event_count的高16位和saved_count比较,看是否相等
2. 判断combined_event_count低16位是否为0
两个条件都成立就可以睡眠,否则就不能睡眠。
第一个条件满足表示从调用pm_save_wakeup_count开始到调用pm_wakeup_pending这段时间内,没有人调用wakeup source吊销函数;
第二个条件满足表示没有人调用wakeup source激活函数。
wakeup source吊销函数之前肯定会有wakeup source激活函数,这是成对出现的,wakeup source吊销函数不执行,说明也没有调激活函数,通俗讲就是在休眠最后时刻没有人用过wake lock。
第二个条件说明没有wakeup event在处理。
这里有个疑问,只判断第二个条件,即看有没有wakeup event处理不就行了吗,为什么要判断有没有人用过wake lock,有人用过,但现在已经释放了,还有影响吗?
我的理解是,pm_wakeup_pending在suspend_enter里调用,是休眠流程中最后一项检查,也就是在此之前freeze_processes/device_suspend已经执行过,
说明所有设备已经进入到休眠态,process也已经在冻结态。如果第一个条件不成立,说明有人用wake lock了,可以说有device或process醒来过,虽然现在wake lock已经释放,
但醒来就说不好可能还有其他未完成的事情,这时候就不适合继续休眠下去。
--------------------------------------------------------------------------------
android休眠流程:


android提供了三种休眠路径,earlysuspend、autosleep、wakeup_count。
earlysuspend:kernel里没有提供对应的sys结点,应该是废弃掉了。
autosleep:在适当时机,直接写sys文件触发休眠,echo mem > /sys/power/autosleep。
wakeup_count:结合kernel的wakeup count,在适当时机echo mem > /sys/power/state触发休眠。


适当时机是由PowerManagerService决定:
setHalAutoSuspendModeLocked@frameworks/base/services/core/java/com/android/server/power/PowerManagerService.java
nativeSetAutoSuspend@frameworks/base/services/core/jni/com_android_server_power_PowerManagerService.cpp
autosuspend_enable@system/core/libsuspend/autosuspend.c
autosuspend_ops->enable()


如果注册的是wakeup_count,那么上述会调用到autosuspend_wakeup_count_enable,wakeup_count休眠路径是这样做的:
suspend_thread_func@system/core/libsuspend/autosuspend_wakeup_count.c {
while(1) {


msleep(100);


read(wakeup_count_fd, wakeup_count,
sizeof(wakeup_count)); //wakeup_count_show-->pm_get_wakeup_count@main.c 如果有正在处理的wakeup event,那么调用schedule休眠,否则返回历史wakeup count总数


sem_wait(&suspend_lockout); //等待合适的休眠时机,autosuspend_wakeup_count_enable函数会调用sem_post(&suspend_lockout);


write(wakeup_count_fd, wakeup_count, wakeup_count_len); //wakeup_count_store-->pm_save_wakeup_count@main.c 其实并没写东西到什么地方,只是判断传入的count值和
//当前从combined_event_count取出的count值是否相等,相等表示从读count到写count这段时间没有新的wakeup event产生,返回正值,否则返回负值


write(state_fd, sleep_state, strlen(sleep_state)); //state_store@main.c echo mem > /sys/power/state 如果上面写count的write调用返回正值,那么调用此write,触发系统休眠
}
}


echo mem > /sys/power/state在kernel里流程是:
state_store@kernel/power/main.c
state = decode_state(buf, n) //解析出来是这三个值 "freeze", "standby", "mem"
pm_suspend(state)@kernel/power/suspend.c //这个函数会打印PM: suspend entry 2016-12-19 08:31:07.846252600 UTC




如果注册的是autosleep,那么会调用到autosuspend_autosleep_enable,这个函数就直接echo mem > /sys/power/autosleep,kernel里的流程是:
autosleep_store-->pm_autosleep_set_state-->queue_up_suspend_work-->try_to_suspend
try_to_suspend
pm_save_wakeup_count
pm_suspend


两种都会调用到pm_suspend
pm_suspend
enter_state
sys_sync //将缓存中的数据写入块设备
suspend_prepare
pm_prepare_console //将当前console切换到一个虚拟console并重定向内核的kmsg
pm_notifier_call_chain(PM_SUSPEND_PREPARE)
suspend_freeze_processes
freeze_processes
__usermodehelper_disable(UMH_FREEZING)
try_to_freeze_tasks(true)
freeze_task
freeze_kernel_threads
try_to_freeze_tasks(false)
freeze_task
suspend_devices_and_enter
suspend_ops->begin(state)
suspend_console
ftrace_stop
dpm_suspend_start
dpm_prepare
device_prepare
dpm_suspend
cpufreq_suspend
__cpufreq_governor
device_suspend
__device_suspend
suspend_enter
dpm_suspend_end
disable_nonboot_cpus
arch_suspend_disable_irqs
syscore_suspend
pm_wakeup_pending
suspend_ops->enter
hisi_pm_enter
cpu_suspend


autosleep_lock@autosleep.c作用:
/*
 * Note: it is only safe to mutex_lock(&autosleep_lock) if a wakeup_source
 * is active, otherwise a deadlock with try_to_suspend() is possible.
 * Alternatively mutex_lock_interruptible() can be used.  This will then fail
 * if an auto_sleep cycle tries to freeze processes.
 */
就是说,mutex_lock(&autosleep_lock)的调用,必须在有active状态的wakeup_source才安全,否则在 try_to_suspend()时可能发生死锁,我的理解是: 
例如,pm_autosleep_set_state中,需要更改autosleep_state(一个全局变量),因而需要使用mutex_lock保护临界区。如果调用时序如下: 
1. 系统具备suspend的条件,调用try_to_suspend试图suspend系统,该接口调用mutex_lock(&autosleep_lock);,持有锁。 
2. 发生调度,执行pm_autosleep_set_state所在的进程,在该接口中调用mutex_lock(&autosleep_lock)。因为此时锁已经被try_to_suspend占用,
进程sleep,且状态为TASK_UNINTERRUPTIBLE(这个状态会导致task freezing失败)。 
3. 发生调度,继续执行try_to_suspend,直到执行freeze操作(suspend_freeze_processes)。由于有进程处于TASK_UNINTERRUPTIBLE状态,freeze失败,suspend失败。 
因此,出现了“具备suspend条件,却suspend失败了”,就是上面注释中所说的deadlock问题。所以在mutex_lock(&autosleep_lock)调用前,保持系统不会suspend,就可以解决这种问题。
上一篇:二叉查找树
下一篇:Accessing physical address from user space