android power-wakelock kernel

来源:互联网 发布:网站模板php 卖房网 编辑:程序博客网 时间:2024/05/16 08:21

前面讲到HAL层通过acquire_wake_lock和release_wake_lock往"/sys/power/wake_lock"与"/sys/power/wake_unlock"写入wakelock名称来acquire或者release wakelock。

写入这个文件节点时会调用到kernel里面"/sys/power/wake_lock"与"/sys/power/wake_unlock"文件节点的store函数,位于/kernel-3.10/kernel/power/main.c:

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);

wake_lock的写接口,直接调用pm_wake_lock;wake_unlock的写接口,直接调用pm_wake_unlock;它们的读接口,直接调用pm_show_wakelocks接口(参数不同)。这三个接口均在kernel/power/wakelock.c中实现。

pm_wake_lock位于kernel\power\wakelock.c中,用于上报一个wakeup event(从另一个角度,就是阻止系统suspend),代码如下:

int pm_wake_lock(const char *buf){const char *str = buf;struct wakelock *wl;u64 timeout_ns = 0;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;while (*str && !isspace(*str))str++;len = str - buf;if (!len)return -EINVAL;if (*str && *str != '\n') {/* Find out if there's a valid timeout string appended. */ret = kstrtou64(skip_spaces(str), 10, &timeout_ns);if (ret)return -EINVAL;}//<20130327> <marc.huang> add wakelock dubug logwakelock_log("%s\n", buf);mutex_lock(&wakelocks_lock);wl = wakelock_lookup_add(buf, len, true);if (IS_ERR(wl)) {ret = PTR_ERR(wl);goto out;}if (timeout_ns) {u64 timeout_ms = timeout_ns + NSEC_PER_MSEC - 1;do_div(timeout_ms, NSEC_PER_MSEC);__pm_wakeup_event(&wl->ws, timeout_ms);} else {__pm_stay_awake(&wl->ws);}wakelocks_lru_most_recent(wl); out:mutex_unlock(&wakelocks_lock);return ret;}

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”。

wakelock_lookup_add是内部接口,代码如下:

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;elsereturn wl;}if (diff < 0)node = &(*node)->rb_left;elsenode = &(*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;}

在wakelock.c中,维护一个名称为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的垃圾回收机制有关,后面会单独描述)。

再看一下struct wakelock结构:

struct wakelock {char*name;struct rb_nodenode;struct wakeup_sourcews;#ifdef CONFIG_PM_WAKELOCKS_GCstruct list_headlru;#endif};

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


pm_wake_unlock和pm_wake_lock类似,如下:

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 logwakelock_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的垃圾回收动作。

pm_show_wakelocks该接口很简单,查询红黑树,返回处于acvtive或者deactive状态的wakelock,如下:

ssize_t pm_show_wakelocks(char *buf, bool show_active){struct rb_node *node;struct wakelock *wl;char *str = buf;char *end = buf + PAGE_SIZE;mutex_lock(&wakelocks_lock);for (node = rb_first(&wakelocks_tree); node; node = rb_next(node)) {wl = rb_entry(node, struct wakelock, node);if (wl->ws.active == show_active)str += scnprintf(str, end - str, "%s ", wl->name);}if (str > buf)str--;str += scnprintf(str, end - str, "\n");mutex_unlock(&wakelocks_lock);return (str - buf);}

1)遍历红黑树,拿到wakelock指针,判断其中的wakeup source的active变量,如果和输入变量(show_active)相符,将该wakelock的名字添加在buf中。

2)调整buf的长度和结束符,返回长度值。

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指针,如下:

static LIST_HEAD(wakelocks_lru_list);static unsigned int wakelocks_gc_count;

2)在wakelock结构中,嵌入一个list head(lru),用于挂入wakelocks_lru_list。

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

a)wakelock创建时,调用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关于wakelock的就这么多了。下面将继续将android suspend流程。

以上部分内容直接截自蜗窝科技博客,写的很好,就直接借鉴了,地址是:

http://www.wowotech.net/pm_subsystem/wakelocks.html

0 0