当前位置:   article > 正文

android 休眠唤醒机制分析(一)_android休眠唤醒机制

android休眠唤醒机制

refer to  :  http://www.wowotech.net/linux_kenrel/wakelocks.html 

      http://slightsnow.blog.chinaunix.net/uid-29269256-id-4093367.html


Android的休眠唤醒主要基于wake_lock机制,只要系统中存在任一有效的wake_lock,系统就不能进入深度休眠,但可以进行设备的浅度休眠操作。wake_lock一般在关闭lcd、tp但系统仍然需要正常运行的情况下使用,比如听歌、传输很大的文件等。本文主要分析driver层wake_lock的实现。

一、wake_lock 定义和接口

struct wakelock {
char *
name;
struct rb_nodenode;
struct
wakeup_sourcews;
#ifdef CONFIG_PM_WAKELOCKS_GC

struct list_headlru;
#endif
};

一个name指针,保存wakelock的名称;一个rb node节点,用于组成红黑树;一个wakeup source变量;如果开启了wakelocks垃圾回收功能,一个用于GC的list head。


/**
 * struct wakeup_source - Representation of wakeup sources
 *
 * @total_time: Total time this wakeup source has been active.
 * @max_time: Maximum time this wakeup source has been continuously active.
 * @last_time: Monotonic clock when the wakeup source's was touched last time.
 * @prevent_sleep_time: Total time this source has been preventing autosleep.
 * @event_count: Number of signaled wakeup events.
 * @active_count: Number of times the wakeup sorce was activated.
 * @relax_count: Number of times the wakeup sorce was deactivated.
 * @expire_count: Number of times the wakeup source's timeout has expired.
 * @wakeup_count: Number of times the wakeup source might abort suspend.
 * @active: Status of the wakeup source.
 * @has_timeout: The wakeup source has been activated with a timeout.
 */
struct wakeup_source {
const char *name;
struct list_headentry;
spinlock_t lock;
struct timer_listtimer;
unsigned long timer_expires;
ktime_t total_time;
ktime_t max_time;
ktime_t last_time;
ktime_t start_prevent_time;
ktime_t prevent_sleep_time;
unsigned long event_count;
unsigned long active_count;
unsigned long relax_count;
unsigned long expire_count;
unsigned long wakeup_count;
bool active:1;
bool autosleep_enabled:1;
};


Kernel wakelocks的实现非常简单(简单的才是最好的),就是在PM core中增加一个wakelock模块(kernel/power/wakelock.c),该模块依赖wakeup events framework提供的wakeup source机制,实现用户空间的wakeup source(就是wakelocks),并通过PM core main模块,向用户空间提供两个同名的sysfs文件,wake_lock和wake_unlock。kernel内部直接 使用wakelock模块提供的接口。




二、用户空间接口

kernel/kernel/power/main.c

wake_lock向用户空间提供了两个文件节点用于申请锁和解锁:

#ifdef CONFIG_PM_WAKELOCKS
static ssize_t wake_lock_show(struct kobject *kobj,
     struct kobj_attribute *attr,
     char *buf)
{
return pm_show_wakelocks(buf, true);
}


static ssize_t wake_lock_store(struct kobject *kobj,
      struct kobj_attribute *attr,
      const char *buf, size_t n)
{
int error = pm_wake_lock(buf);
return error ? error : n;
}


power_attr(wake_lock);


static ssize_t wake_unlock_show(struct kobject *kobj,
struct kobj_attribute *attr,
char *buf)
{
return pm_show_wakelocks(buf, false);
}


static ssize_t wake_unlock_store(struct kobject *kobj,
struct kobj_attribute *attr,
const char *buf, size_t n)
{
int error = pm_wake_unlock(buf);
return error ? error : n;
}

power_attr(wake_unlock);

#endif /* CONFIG_PM_WAKELOCKS */

这两个文件节点分别为"/sys/power/wake_lock"和"/sys/power/wake_unlock"应用程序可以根据HAL层的接口读写这两个节点。

我们继续看下pm_wake_lock函数,

kernel/kernel/power/wakelock.c
183 intpm_wake_lock(const char *buf)
184 {
185         const char *str = buf;
186         struct wakelock *wl;
187         u64 timeout_ns = 0;
188         size_t len;
189         int ret = 0;
190 
191         while (*str && !isspace(*str))
192                 str++;
193 
194         len = str - buf;
195         if (!len)
196                 return -EINVAL;
197 
198         if (*str && *str != '\n') {
199                 /* Find out if there's a valid timeout string appended. */
200                 ret = kstrtou64(skip_spaces(str), 10, &timeout_ns);
201                 if (ret)
202                         return -EINVAL;
203         }
204 
205         mutex_lock(&wakelocks_lock);
206 
207         wl =wakelock_lookup_add(buf, len, true);
208         if (IS_ERR(wl)) {
209                 ret = PTR_ERR(wl);
210                 goto out;
211         }
212         if (timeout_ns) {                      
213                 u64 timeout_ms = timeout_ns + NSEC_PER_MSEC - 1;
214 
215                 do_div(timeout_ms, NSEC_PER_MSEC);
216                __pm_wakeup_event(&wl->ws, timeout_ms);  // timeout wakelock , control by a Timer
217         } else {                                                                        
218                __pm_stay_awake(&wl->ws);        //
219         }
220 
221        wakelocks_lru_most_recent(wl);
222 
223  out:
224         mutex_unlock(&wakelocks_lock);
225         return ret;
226 }

a)输入参数为一个字符串,如"wake_lock_test 1000”,该字符串指定上报wakeup event的wakelock name,可以在name后用空格隔开,添加一个时间值(单位为ns),表示该event的timeout值。

b)调用capable,检查当前进程是否具备阻止系统suspend的权限。 
注2:capable是Linux security子系统提供的一个接口,用于权限判断。我们说过,power是系统的核心资源,理应由OS全权管理,但wakelock违反了这一原则,将阻止系统睡眠的权利给了用户空间。这样一来,用户空间程序将可以随心所欲的占用power资源,特别是用户态的程序员,天生对资源占用不敏感(这是对的),就导致该接口有被滥用的风险。不过还好,通过系统的权限管理机制,可以改善这种状态(其实不是改善,而是矛盾转移,很有可能把最终的裁决权交给用户,太糟糕了!)。

c)解析字符串,将timeout值(有的话)保存在timeout_ns中,解析name长度(len),并将name保存在原来的buf中。

d)调用wakelock_lookup_add接口,查找是否有相同name的wakelock。如果有,直接返回wakelock的指针;如果没有,分配一个wakelock,同时调用wakeup events framework提供的接口,创建该wakelock对应的wakeup source结构。

e)如果指定timeout值,以wakelock的wakeup source指针为参数,调用__pm_wakeup_event接口,上报一个具有时限的wakeup events;否则,调用__pm_stay_awake,上报一个没有时限的wakeup event。有关这两个接口的详细说明,可参考“Linux电源管理(7)_Wakeup events framework”。 

418 void __pm_stay_awake(struct wakeup_source *ws)
419 {
420         unsigned long flags;
421 
422         if (!ws)
423                 return;
424 
425         spin_lock_irqsave(&ws->lock, flags);
426 
427        wakeup_source_report_event(ws);
428         del_timer(&ws->timer);
429         ws->timer_expires = 0;
430 
431         spin_unlock_irqrestore(&ws->lock, flags);
432 }  

这个函数主要干的就是调用wakeup_source_report_evet这个函数

01 static void wakeup_source_report_event(struct wakeup_source *ws)
402 {
403         ws->event_count++;
404         /* This is racy, but the counter is approximate anyway. */
405         if (events_check_enabled)
406                 ws->wakeup_count++;
407         
408         if (!ws->active)
409                wakeup_source_activate(ws);

381 static void wakeup_source_activate(struct wakeup_source *ws)
382 {
383         unsigned int cec;
384 
385         ws->active = true;
386         ws->active_count++;
387         ws->last_time = ktime_get();
388         if (ws->autosleep_enabled)
389                 ws->start_prevent_time = ws->last_time;
390 
391         /* Increment the counter of events in progress. */
392         cec = atomic_inc_return(&combined_event_count);    // wakeup source 计数加1                                                    
393 
394         trace_wakeup_source_activate(ws->name, cec);
395 }

这两段函数连续看,会发现,对与一些count进行的++,设置了下wake_lock中的wake_source的一些变量,主要是 对于combined_event_count这个变量进行的原子操作++,(这个变量暂时理解胃是记录有多少个wakelock吧),然后就完活了啊,这样就对一个wakelock进行。


static struct wakelock *wakelock_lookup_add(const char *name, size_t len,
   bool add_if_not_found)
{
struct rb_node **node = &wakelocks_tree.rb_node;
struct rb_node *parent = *node;
struct wakelock *wl;


while (*node) {
int diff;


parent = *node;
wl = rb_entry(*node, struct wakelock, node);
diff = strncmp(name, wl->name, len);
if (diff == 0) {
if (wl->name[len])
diff = -1;
else
return wl;
}
if (diff < 0)
node = &(*node)->rb_left;
else
node = &(*node)->rb_right;
}
if (!add_if_not_found)
return ERR_PTR(-EINVAL);


if (wakelocks_limit_exceeded())
return ERR_PTR(-ENOSPC);


/* Not found, we have to add a new one. */
wl = kzalloc(sizeof(*wl), GFP_KERNEL);
if (!wl)
return ERR_PTR(-ENOMEM);


wl->name = kstrndup(name, len, GFP_KERNEL);
if (!wl->name) {
kfree(wl);
return ERR_PTR(-ENOMEM);
}
wl->ws.name = wl->name;
wakeup_source_add(&wl->ws);
rb_link_node(&wl->node, parent, node);
rb_insert_color(&wl->node, &wakelocks_tree);
wakelocks_lru_add(wl);
increment_wakelocks_number();
return wl;
}

维护一个名称为wakelocks_tree的红黑树(红黑树都用上了,可以想象wakelocks曾经使用多么频繁!),所有的wakelock都保存在该tree上。因此该接口的动作是:

a)查找红黑树,如果找到name相同的wakelock,返回wakelock指针。

b)如果没找到,且add_if_not_found为false,返回错误。

c)如果add_if_not_found为true,分配一个struct wakelock变量,并初始化它的名称、它的wakeup source的名称。调用wakeup_source_add接口,将wakeup source添加到wakeup events framework中。

d)将该wakelock添加到红黑树。

e)最后调用wakelocks_lru_add接口,将新分配的wakeup添加到一个名称为wakelocks_lru_list的链表前端(该功能和wakelock的垃圾回收机制有关,后面会单独描述)。


int pm_wake_unlock(const char *buf)
{
struct wakelock *wl;
size_t len;
int ret = 0;

/*
* 20130429 marc.huang
* remove CAP_BLOCK_SUSPEND capability check (rollback to android kernel 3.4)
*/
//if (!capable(CAP_BLOCK_SUSPEND))
//return -EPERM;

len = strlen(buf);
if (!len)
return -EINVAL;

if (buf[len-1] == '\n')
len--;

if (!len)
return -EINVAL;

//<20130327> <marc.huang> add wakelock dubug log
wakelock_log("%s\n", buf);

mutex_lock(&wakelocks_lock);

wl =wakelock_lookup_add(buf, len, false);
if (IS_ERR(wl)) {
ret = PTR_ERR(wl);
goto out;
}
__pm_relax(&wl->ws);

wakelocks_lru_most_recent(wl);
wakelocks_gc();

 out:
mutex_unlock(&wakelocks_lock);
return ret;
}

a)输入参数为一个字符串,如"wake_lock_test”,该字符串指定一个wakelock name。

b)调用capable,检查当前进程是否具备阻止系统suspend的权限。

c)解析字符串

d)调用wakelock_lookup_add接口,查找是否有相同name的wakelock。如果有,直接返回wakelock的指针;如果没有,退出。

e)调用__pm_relax接口,deactive wakelock对应的wakeup source。

f)调用wakelocks_lru_most_recent接口,将盖wakelock移到wakelocks_lru_list链表的前端(表示它是最近一个被访问到的,和GC有关,后面重点描述)。

g)调用wakelocks_gc,执行wakelock的垃圾回收动作。


void __pm_relax(struct wakeup_source *ws)
{
unsigned long flags;

if (!ws)
return;

//<20130327> <marc.huang> add wakeup source dubug log
wakeup_log("ws->name: %s\n", ws->name);

spin_lock_irqsave(&ws->lock, flags);
if (ws->active)
wakeup_source_deactivate(ws);        
spin_unlock_irqrestore(&ws->lock, flags);
}


static void wakeup_source_deactivate(struct wakeup_source *ws)
{
unsigned int cnt, inpr, cec;
ktime_t duration;
ktime_t now;

ws->relax_count++;
/*
* __pm_relax() may be called directly or from a timer function.
* If it is called directly right after the timer function has been
* started, but before the timer function calls __pm_relax(), it is
* possible that __pm_stay_awake() will be called in the meantime and
* will set ws->active.  Then, ws->active may be cleared immediately
* by the __pm_relax() called from the timer function, but in such a
* case ws->relax_count will be different from ws->active_count.
*/
if (ws->relax_count != ws->active_count) {
ws->relax_count--;
return;
}

ws->active = false;

now = ktime_get();
duration = ktime_sub(now, ws->last_time);
ws->total_time = ktime_add(ws->total_time, duration);
if (ktime_to_ns(duration) > ktime_to_ns(ws->max_time))
ws->max_time = duration;

ws->last_time = now;
del_timer(&ws->timer);
ws->timer_expires = 0;

if (ws->autosleep_enabled)
{
//<20130327> <marc.huang> add wakeup source dubug log
wakeup_warn("ws->name: %s\n", ws->name);
update_prevent_sleep_time(ws, now);
}

/*
* Increment the counter of registered wakeup events and decrement the
* couter of wakeup events in progress simultaneously.
*/
    // FIXME: CHECK BUG here ??? if combined_event_count = 0x????0000, then atomic_add_return(...) --> 0x????ffff
    //        , which is not the expected result !!!
cec = atomic_add_return(MAX_IN_PROGRESS, &combined_event_count);
trace_wakeup_source_deactivate(ws->name, cec);
    
split_counters(&cnt, &inpr);      
if (!inpr && waitqueue_active(&wakeup_count_wait_queue))         //  判断wake_locks数,等待队列是否活动 
wake_up(&wakeup_count_wait_queue);     然后唤醒suspend等待队列 , refer to : pm_get_wakeup_count()

       // 这样就将wake lock 与 suspend 关联起来了。
}

 wakelocks的垃圾回收机制

由上面的逻辑可知,一个wakelock的生命周期,应只存在于wakeup event的avtive时期内,因此如果它的wakeup source状态为deactive,应该销毁该wakelock。但销毁后,如果又产生wakeup events,就得重新建立。如果这种建立->销毁->建立的过程太频繁,效率就会降低。

因此,最好不销毁,保留系统所有的wakelocks(同时可以完整的保留wakelock信息),但如果wakelocks太多(特别是不活动的),将会占用很多内存,也不合理。

折衷方案,保留一些非active状态的wakelock,到一定的时机时,再销毁,这就是wakelocks的垃圾回收(GC)机制。

wakelocks GC功能可以开关(由CONFIG_PM_WAKELOCKS_GC控制),如果关闭,系统会保留所有的wakelocks,如果打开,它的处理逻辑也很简单:

1)定义一个list head,保存所有的wakelock指针,如下:

   1: static LIST_HEAD(wakelocks_lru_list);
   2: static unsigned int wakelocks_gc_count;

 

2)在wakelock结构中,嵌入一个list head(lru),用于挂入wakelocks_lru_list。可参考3.4小节的描述。

3)wakelocks_lru_list中的wakelock是按访问顺序排列的,最近访问的,靠近head位置。这是由3种操作保证的:

a)wakelock创建时(见3.4小节),调用wakelocks_lru_add接口,将改wakelock挂到wakelocks_lru_list的head处(利用list_add接口),表示它是最近被访问的。

b)pm_wake_lock或者pm_wake_unlock时,调用wakelocks_lru_most_recent接口,将该wakelcok移到链表的head处,表示最近访问。

c)每当pm_wake_unlock时,调用wakelocks_gc,执行wakelock的垃圾回收动作。wakelocks_gc的实现如下:

  
static void wakelocks_gc(void)
{
struct wakelock *wl, *aux;
ktime_t now;


if (++wakelocks_gc_count <= WL_GC_COUNT_MAX)
return;


now = ktime_get();
list_for_each_entry_safe_reverse(wl, aux, &wakelocks_lru_list, lru) {
u64 idle_time_ns;
bool active;


spin_lock_irq(&wl->ws.lock);
idle_time_ns = ktime_to_ns(ktime_sub(now, wl->ws.last_time));
active = wl->ws.active;
spin_unlock_irq(&wl->ws.lock);


if (idle_time_ns < ((u64)WL_GC_TIME_SEC * NSEC_PER_SEC))
break;


if (!active) {
wakeup_source_remove(&wl->ws);
rb_erase(&wl->node, &wakelocks_tree);
list_del(&wl->lru);
kfree(wl->name);
kfree(wl);
decrement_wakelocks_number();
}
}
wakelocks_gc_count = 0;
}


1)如果当前wakelocks的数目小于最大值(由WL_GC_COUNT_MAX配置,当前代码为100),不回收,直接返回。

2)否则,从wakelocks_lru_most_recent的尾部(最不活跃的),依次取出wakelock,判断它的idle时间(通过wakeup source lst_time和当前时间计算)是否超出预设值(由WL_GC_TIME_SEC指定,当前为300s,好长),如果超出且处于deactive状态,调用wakeup_source_remove,注销wakeup source,同时把它从红黑树、GC list中去掉,并释放memory资源。



三、内核空间接口

kernel/kernel/power/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);

其中wake_lock_init()用于初始化一个新锁,type参数指定了锁的类型;wake_lock_destroy()则注销一个锁;wake_lock()和wake_lock_timeout()用于将初始化完成的锁激活,使之成为有效的永久锁或者超时锁;wake_unlock()用于解锁使之成为无效锁。另外还有两个接口:

int wake_lock_active(struct wake_lock *lock);

其中wake_lock_active()用于判断锁当前是否有效,如果有效则返回非0值;


struct wake_lock {
struct wakeup_source ws;
};

// 内核使用wake lock 结构 

static inline void wake_lock_init(struct wake_lock *lock, int type,           // 初始化 wake lock 结构 
 const char *name)
{
wakeup_source_init(&lock->ws, name);
}


static inline void wake_lock_destroy(struct wake_lock *lock)
{
wakeup_source_trash(&lock->ws);
}


static inline void wake_lock(struct wake_lock *lock)     //  获取 wake lock ,直接 调用  __pm_stay_awake,前面讲过
{
__pm_stay_awake(&lock->ws);
}


static inline void wake_lock_timeout(struct wake_lock *lock, long timeout)
{
__pm_wakeup_event(&lock->ws, jiffies_to_msecs(timeout));
}


static inline void wake_unlock(struct wake_lock *lock)
{
__pm_relax(&lock->ws);
}


static inline int wake_lock_active(struct wake_lock *lock)
{
return lock->ws.active;
}



声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/小蓝xlanll/article/detail/301852
推荐阅读
相关标签
  

闽ICP备14008679号