目录
[1、Kernel wakelocks在电源管理中的位置](#1、Kernel wakelocks在电源管理中的位置)
[二、wakelocks 内核源码分析](#二、wakelocks 内核源码分析)
[1、创建 /sys/power/wake_lock 和 /sys/power/wake_unlock](#1、创建 /sys/power/wake_lock 和 /sys/power/wake_unlock)
[2、pm_wake_lock() 接口](#2、pm_wake_lock() 接口)
[3、pm_wake_unlock() 接口](#3、pm_wake_unlock() 接口)
简介:
Wakelocks 框架是基于Wakeup Source实现的为Android系统上层提供投票机制,以阻止系统进入休眠。
该模块的支持受宏CONFIG_PM_WAKELOCKS控制。在使能该宏的情况下,PM Core初始化过程中会在sysfs下创建两个属性节点:
- /sys/power/wake_lock:用户程序可以向其写入一个字符串来创建一个wakelock,该字符创即为wakelock的名字,该wakelock可阻止系统进入低功耗模式
- /sys/power/wake_unlock:用户程序向其写入相同的字符串,即可注销该wakelock
配置宏CONFIG_PM_WAKELOCKS_LIMIT可以限制系统所能创建的wakelock的数量。
使能宏CONFIG_PM_WAKELOCKS_GC能打开wakelock的回收机制,使得wakelock在积累一定的数量后再去清除(释放空间),从而不需要在每次释放wakelock时都去清除。
一、wakelocks
wakelocks提供的功能包括:
1)/sys/power/wake_lock:用户程序向文件写入一个字符串,即可创建一个wakelock,该字符串就是wakelock的名字。该wakelock可以阻止系统进入低功耗模式
2)/sys/power/wake_unlock:用户程序向文件写入相同的字符串,即可注销一个wakelock
3)当系统中所有的wakelock都注销后,系统可以自动进入低功耗状态:由autosleep实现
4)向内核其它driver也提供了wakelock的创建和注销接口,允许driver创建wakelock以阻止睡眠、注销wakelock以允许睡眠。并允许用户空间访问
有关Android wakelocks更为详细的描述,可以参考:http://elinux.org/Android_Power_Management
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。

二、wakelocks 内核源码分析
该模块的支持受宏 CONFIG_PM_WAKELOCKS 控制。在使能该宏的情况下,PM Core初始化过程中会在sysfs下创建两个属性节点:
- /sys/power/wake_lock:用户程序可以向其写入一个字符串来创建一个wakelock,该字符创即为wakelock的名字,该wakelock可阻止系统进入低功耗模式
- **/sys/power/wake_unlock:**用户程序向其写入相同的字符串,即可注销该wakelock
1、创建 /sys/power/wake_lock 和 /sys/power/wake_unlock
pm_init() 调用 kobject_create_and_add 和 sysfs_create_groups 函数,创建一系列的属性文件,其中 wake_lock_attr、wake_unlock_attr 分别会生成 /sys/power/wake_lock 和 /sys/power/wake_unlock
cpp
/* kernel/power/main.c */
static struct attribute * g[] = {
&state_attr.attr,
#ifdef CONFIG_PM_TRACE
&pm_trace_attr.attr,
&pm_trace_dev_match_attr.attr,
#endif
#ifdef CONFIG_PM_SLEEP
&pm_async_attr.attr,
&wakeup_count_attr.attr,
#ifdef CONFIG_SUSPEND
&mem_sleep_attr.attr,
&sync_on_suspend_attr.attr,
#endif
#ifdef CONFIG_PM_AUTOSLEEP
&autosleep_attr.attr,
#endif
#ifdef CONFIG_PM_WAKELOCKS
&wake_lock_attr.attr, //wake_lock
&wake_unlock_attr.attr, //wake_unlock
#endif
#ifdef CONFIG_PM_SLEEP_DEBUG
&pm_test_attr.attr,
&pm_print_times_attr.attr,
&pm_wakeup_irq_attr.attr,
&pm_debug_messages_attr.attr,
#endif
#endif
#ifdef CONFIG_FREEZER
&pm_freeze_timeout_attr.attr,
#endif
NULL,
};
static const struct attribute_group attr_group = {
.attrs = g,
};
static const struct attribute_group *attr_groups[] = {
&attr_group,
#ifdef CONFIG_PM_SLEEP
&suspend_attr_group,
#endif
NULL,
};
struct workqueue_struct *pm_wq;
EXPORT_SYMBOL_GPL(pm_wq);
static int __init pm_start_workqueue(void)
{
pm_wq = alloc_workqueue("pm", WQ_FREEZABLE, 0);
return pm_wq ? 0 : -ENOMEM;
}
static int __init pm_init(void)
{
int error = pm_start_workqueue();
if (error)
return error;
hibernate_image_size_init();
hibernate_reserved_size_init();
pm_states_init();
power_kobj = kobject_create_and_add("power", NULL);
if (!power_kobj)
return -ENOMEM;
error = sysfs_create_groups(power_kobj, attr_groups);
if (error)
return error;
pm_print_times_init();
return pm_autosleep_init();
}
core_initcall(pm_init);
wake_lock_attr 和 wake_unlock_attr 的 .show、.store 定义如下
cpp
#define power_attr(_name) \
static struct kobj_attribute _name##_attr = { \
.attr = { \
.name = __stringify(_name), \
.mode = 0644, \
}, \
.show = _name##_show, \
.store = _name##_store, \
}
#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写数据最终调用 pm_wake_lock
用户空间往/sys/power/wake_unlock写数据最终调用 pm_wake_unlock
2、pm_wake_lock() 接口
向 /sys/power/wake_lock 写如字符串时调用 pm_wake_lock()
pm_wake_lock() 主要实现功能:
- 查找同名 wakelock,找不到时创建 wakelock,并持(超时)锁
- 配置 CONFIG_PM_WAKELOCKS_LIMIT > 0 的情况下,对wakelock数量计数并限制
- 将该wakelock移到回收链表前端,以防被优先回收
cpp
/* kernel/power/wakelock.c*/
int pm_wake_lock(const char *buf)
{
const char *str = buf;
struct wakelock *wl;
u64 timeout_ns = 0;
size_t len;
int ret = 0;
if (!capable(CAP_BLOCK_SUSPEND))
return -EPERM;
//解析传入的字符串,第一个参数为wakelock名称,第二个参数(可选)则是wakelock超时时间
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;
}
mutex_lock(&wakelocks_lock);
//查找wakelock,找不到时创建
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); //将该wakelock移到回收链表前端,使得回收机制触发时靠后处理
out:
mutex_unlock(&wakelocks_lock);
return ret;
}
wakelock_lookup_add() 查找wakelock,找不到时创建
cpp
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;
//根据名称在红黑树上查找是否已经存在该wakelock
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; //找到同名wakelock,返回
}
if (diff < 0)
node = &(*node)->rb_left;
else
node = &(*node)->rb_right;
}
if (!add_if_not_found)
return ERR_PTR(-EINVAL);
//配置CONFIG_PM_WAKELOCKS_LIMIT>0的情况下,会检测已创建的wakelock数量是否已经超过该配置
if (wakelocks_limit_exceeded())
return ERR_PTR(-ENOSPC);
//未找到同名wakelock的情况下,开始创建wakelock
/* 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);
}
//本质wakelock是通过wakeup_source机制实现的
wl->ws = wakeup_source_register(NULL, wl->name);
if (!wl->ws) {
kfree(wl->name);
kfree(wl);
return ERR_PTR(-ENOMEM);
}
wl->ws->last_time = ktime_get();
//将该wakelock挂到红黑树上
rb_link_node(&wl->node, parent, node);
rb_insert_color(&wl->node, &wakelocks_tree);
wakelocks_lru_add(wl); //添加到回收链表
increment_wakelocks_number(); //wakelock数量+1
return wl;
}
3、pm_wake_unlock() 接口
向 /sys/power/wake_unlock 写入字符串时调用 pm_wake_unlock()
- 查找同名wakelock,找不到时返回错误
- 释放锁
- 配置 CONFIG_PM_WAKELOCKS_GC 开启回收机制的情况下,对wakelock数量计数并在超过上限时触发回收处理work
cpp
/* kernel/power/wakelock.c */
int pm_wake_unlock(const char *buf)
{
struct wakelock *wl;
size_t len;
int ret = 0;
if (!capable(CAP_BLOCK_SUSPEND))
return -EPERM;
len = strlen(buf);
if (!len)
return -EINVAL;
if (buf[len-1] == '\n')
len--;
if (!len)
return -EINVAL;
mutex_lock(&wakelocks_lock);
//查找wakelock,找不到时直接返回错误
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); //将该wakelock移到回收链表前端,使得回收机制触发时靠后处理
wakelocks_gc(); //已解锁的wakelock加1,并判断是否超过上限,触发回收处理work
out:
mutex_unlock(&wakelocks_lock);
return ret;
}
4、__wakelocks_gc()回收处理work
该接口在已解锁的wakelock数量超过上限 WL_GC_COUNT_MAX(100) 时调用,用于处理回收已创建的wakelock,释放空间。
cpp
static void __wakelocks_gc(struct work_struct *work)
{
struct wakelock *wl, *aux;
ktime_t now;
mutex_lock(&wakelocks_lock);
now = ktime_get();
//从回收链表尾部开始倒序遍历(越靠近链表头部的wakelock,越是最近才操作的wakelock)
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)) //如果锁空闲时间小于300s,则不再继续回收
break;
//如果锁已经失活,则注销该锁,从红黑树中移除,并移除出回收链表,释放空间,wakelock数量-1
if (!active) {
wakeup_source_unregister(wl->ws);
rb_erase(&wl->node, &wakelocks_tree);
list_del(&wl->lru);
kfree(wl->name);
kfree(wl);
decrement_wakelocks_number();
}
}
wakelocks_gc_count = 0; //重置回收锁计数
mutex_unlock(&wakelocks_lock);
}
回收机制的好处:
- 上层频繁操作wakelock时,不用每次unlock时都耗时去释放资源;
- 如果频繁操作的是同一个wakelock,也不用反复创建/释放资源。
三、工作时序
wakelock的工作时序如下:
- 应用程序在处理数据前不希望系统进入休眠状态,通过向/sys/power/wake_lock写入一个字符串作为wakelock名字,此时pm_wake_lock()被调用
- 在pm_wake_lock()里,会查找是否已存在同名wakelock,已存在则持锁,不存在则创建锁并持锁
- 应用程序在处理完数据后允许系统进入休眠状态时,通过向/sys/power/wake_unlock写入已持锁的wakelock名字,此时pm_wake_unlock()被调用
- 在pm_wake_unlock()里,会查找是否已存在同名wakelock,并释放该锁,同时判断此时是否要触发wakelock的回收机制
- 当wakelock回收链表里的wakelock数量达到上限后,触发wakelock的回收机制,将长时间未使用且已经解锁的wakelock注销,释放资源

把 /sys/power/wake_lock 和 /sys/power/wake_unlock 当成普通文件读写即可,参考代码如下:
cpp
static void do_wake_lock(char *module)
{
char wake_lock_path[] = "/sys/power/wake_lock";
int wake_lock_fd = -1;
int ret = -1;
wake_lock_fd= open(wake_lock_path, O_WRONLY | O_APPEND);
if (wake_lock_fd < 0) {
printf("%s Failed to open wakelock file - %s", __func__, strerror(errno));
}
ret = write(wake_lock_fd, module, strlen(module));
if (ret != (int)strlen(module)) {
printf("%s write to wakelock file failed %d - %s", __func__, ret, strerror(errno));
}
else
printf("module:%s write to wake_lock file success",module);
close(wake_lock_fd);
}
static void do_wake_unlock(char *module)
{
char wake_unlock_path[] = "/sys/power/wake_unlock";
int wake_unlock_fd = -1;
int ret = -1;
wake_unlock_fd= open(wake_unlock_path, O_WRONLY | O_APPEND);
if (wake_unlock_fd < 0) {
printf("%s Failed to open wakelock file - %s", __func__, strerror(errno));
}
ret = write(wake_unlock_fd, module, strlen(module));
if (ret != (int)strlen(module)) {
printf("%s write to wakelock file failed %d - %s", __func__, ret, strerror(errno));
}
else
printf("module:%s write to wake_unlock file success",module);
close(wake_unlock_fd);
}