1. 前言
wakelocks最初出现在Android为linux kernel打的一个补丁集上,该补丁集实现了一个名称为“wakelocks”的系统调用,该系统调用允许调用者阻止系统进入低功耗模式(如idle、suspend等)。同时,该补丁集更改了Linux kernel原生的电源管理执行过程(kernel/power/main.c中的state_show和state_store),转而执行自定义的state_show、state_store。
这种做法是相当不规范的,它是典型的只求实现功能,不择手段。就像国内很多的Linux开发团队,要实现某个功能,都不去弄清楚kernel现有的机制、框架,牛逼哄哄的猛干一番。最后功能是实现了,可都不知道重复造了多少轮子,浪费了多少资源。到此打住,Android的开发者不会这么草率,他们推出wakelocks机制一定有一些苦衷,我们就不评论了。
但是,虽然有苦衷,kernel的开发者可是有原则的,死活不让这种机制合并到kernel分支(换谁也不让啊),直到kernel自身的wakeup events framework成熟后,这种僵局才被打破。因为Android开发者想到了一个坏点子:不让合并就不让合并呗,我用你的机制(wakeup source),再实现一个就是了。至此,全新的wakelocks出现了。
所以wakelocks有两个,早期Android版本的wakelocks几乎已经销声匿迹了,不仔细找还真找不到它的source code(这里有一个链接,但愿读者看到时还有效,drivers/android/power.c)。本文不打算翻那本旧黄历,所以就focus在新的wakelocks上(kernel/power/wakelock.c,较新的kernel都支持)。
2. Android wakelocks
虽说不翻旧黄历了,还是要提一下Android wakelocks的功能,这样才能知道kernel wakelocks要做什么。总的来说,Android wakelocks提供的功能包括:
1)一个sysfs文件:/sys/power/wake_lock,用户程序向文件写入一个字符串,即可创建一个wakelock,该字符串就是wakelock的名字。该wakelock可以阻止系统进入低功耗模式。
2)一个sysfs文件::/sys/power/wake_unlock,用户程序向文件写入相同的字符串,即可注销一个wakelock。
3)当系统中所有的wakelock都注销后,系统可以自动进入低功耗状态。
4)向内核其它driver也提供了wakelock的创建和注销接口,允许driver创建wakelock以阻止睡眠、注销wakelock以允许睡眠。
有关Android wakelocks更为详细的描述,可以参考下面的一个链接:
http://elinux.org/Android_Power_Management
3. Kernel wakelocks
3.1 Kernel wakelocks的功能
对比Android wakelocks要实现的功能,Linux kernel的方案是:
1)允许driver创建wakelock以阻止睡眠、注销wakelock以允许睡眠:已经由“Linux电源管理(7)_Wakeup events framework”所描述的wakeup source取代。
2)当系统中所有的wakelock都注销后,系统可以自动进入低功耗状态:由autosleep实现(下一篇文章会分析)。
3)wake_lock和wake_unlock功能:由本文所描述的kernel wakelocks实现,其本质就是将wakeup source开发到用户空间访问。
3.2 Kernel wakelocks在电源管理中的位置
相比Android wakelocks,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。
3.3 /sys/power/wake_lock & /sys/power/wake_unlock
从字面意思上,新版的wake_lock和wake_unlock和旧版的一样,都是用于创建和注销wakelock。从应用开发者的角度,确实可以这样理解。但从底层实现的角度,却完全不是一回事。
Android的wakelock,真是一个lock,用户程序创建一个wakelock,就是在系统suspend的路径上加了一把锁,注销就是解开这把锁。直到suspend路径上所有的锁都解开时,系统才可以suspend。
而Kernel的wakelock,是基于wakeup source实现的,因此创建wakelock的本质是在指定的wakeup source上activate一个wakeup event,注销wakelock的本质是deactivate wakeup event。因此,/sys/power/wake_lock和/sys/power/wake_unlock两个sysfs文件的的功能就是:
写wake_lock(以wakelock name和timeout时间<可选>为参数),相当于以wakeup source为参数调用__pm_stay_awake(或者__pm_wakeup_event),即activate wakeup event;
写wake_unlock(以wakelock name为参数),相当于以wakeup source为参数,调用__pm_relax;
读wake_lock,获取系统中所有的处于active状态的wakelock列表(也即wakeup source列表)
读wake_unlock,返回系统中所有的处于非active状态的wakelock信息(也即wakeup source列表)
注1:上面有关wakeup source的操作接口,可参考“Linux电源管理(7)_Wakeup events framework”。
这两个sysfs文件在kernel/power/main.c中实现,如下:
#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 */
1)wakelocks功能不是linux kernel的必选功能,可以通过CONFIG_PM_WAKELOCKS开关。
2)wake_lock的写接口,直接调用pm_wake_lock;wake_unlock的写接口,直接调用pm_wake_unlock;它们的读接口,直接调用pm_show_wakelocks接口(参数不同)。这三个接口均在kernel/power/wakelock.c中实现。
3.4 pm_wake_lock
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;
// 检查当前进程是否具有CAP_BLOCK_SUSPEND权限,如果没有则返回EPERM错误
if (!capable(CAP_BLOCK_SUSPEND))
return -EPERM;
// 查找buf中的第一个空格或者换行符
while (*str && !isspace(*str))
str++;
// 计算buf的长度
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;
}
// 加锁以保证对wakelocks数据结构的访问是互斥的
mutex_lock(&wakelocks_lock);
// 在wakelocks数据结构中查找或添加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);
}
// 更新最近使用的wakelock的LRU链表位置
wakelocks_lru_most_recent(wl);
out:
// 解锁wakelocks数据结构
mutex_unlock(&wakelocks_lock);
return ret;
}
wakelock_lookup_add是内部接口,代码如下:
/**
* wakelock_lookup_add - 查找或添加wakelock
* @name: wakelock的名称
* @len: wakelock名称的长度
* @add_if_not_found: 如果找不到是否添加新的wakelock
*
* 该函数用于查找已存在的wakelock,如果找不到且add_if_not_found为true,则会添加一个新的wakelock。
* 这里使用红黑树来维护wakelock的有序集合,根据名称进行查找和插入操作。
*
* 返回值:
* - 成功查找到现有的wakelock时,返回指向该wakelock的指针。
* - 找不到现有wakelock并且add_if_not_found为true时,返回新添加的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;
// 在红黑树中查找或添加wakelock节点
while (*node) {
int diff;
parent = *node;
wl = rb_entry(*node, struct wakelock, node);
diff = strncmp(name, wl->name, len);
if (diff == 0) {
// 名称匹配,找到现有的wakelock
if (wl->name[len])
diff = -1;
else
return wl;
}
if (diff < 0)
node = &(*node)->rb_left;
else
node = &(*node)->rb_right;
}
// 找不到现有的wakelock,如果需要添加新的wakelock
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. */
// 分配新的wakelock节点
wl = kzalloc(sizeof(*wl), GFP_KERNEL);
if (!wl)
return ERR_PTR(-ENOMEM);
// 复制wakelock的名称
wl->name = kstrndup(name, len, GFP_KERNEL);
if (!wl->name) {
kfree(wl);
return ERR_PTR(-ENOMEM);
}
// 注册wakeup_source
wl->ws = wakeup_source_register(NULL, wl->name);
if (!wl->ws) {
kfree(wl->name);
kfree(wl);
return ERR_PTR(-ENOMEM);
}
// 记录wakelock的最后使用时间
wl->ws->last_time = ktime_get();
// 将新的wakelock节点插入红黑树
rb_link_node(&wl->node, parent, node);
rb_insert_color(&wl->node, &wakelocks_tree);
// 将新的wakelock添加到LRU链表
wakelocks_lru_add(wl);
// 增加wakelock的数量计数
increment_wakelocks_number();
// 返回新添加的wakelock节点指针
return wl;
}
该函数维护一个红黑树来保存wakelock,根据名称进行查找和添加操作。如果找到现有的wakelock,则返回该wakelock的指针;如果找不到现有的wakelock且add_if_not_found为true,则会分配并添加新的wakelock,并返回新添加的wakelock的指针。在添加新的wakelock时,会进行一系列的初始化操作,包括注册wakeup_source,并记录wakelock的最后使用时间。此外,每次添加新的wakelock时,都会将其添加到LRU链表,用于管理最近使用的wakelock。
/**
* wakeup_source_register - 创建wakeup_source并将其添加到列表中
* @dev: 与该wakeup_source关联的设备(如果为虚拟设备则为NULL)
* @name: 要注册的wakeup_source的名称
*
* 该函数用于创建wakeup_source并将其添加到wakeup_source列表中。wakeup_source是Linux内核中用于追踪系统唤醒源的机制,
* 它允许标识哪些设备或驱动程序可能导致系统从低功耗状态中唤醒。通过追踪唤醒源,内核可以更有效地管理系统的
* 低功耗模式和唤醒过程。
*
* 参数:
* @dev: 与wakeup_source关联的设备,如果是虚拟设备则为NULL。
* @name: 要注册的wakeup_source的名称。
*
* 返回值:
* - 成功创建wakeup_source并添加到列表时,返回指向该wakeup_source的指针。
* - 创建或添加失败时,返回NULL。
*/
struct wakeup_source *wakeup_source_register(struct device *dev,
const char *name)
{
struct wakeup_source *ws;
int ret;
// 创建一个新的wakeup_source
ws = wakeup_source_create(name);
if (ws) {
// 如果设备已经注册,将wakeup_source添加到sysfs中
if (!dev || device_is_registered(dev)) {
ret = wakeup_source_sysfs_add(dev, ws);
if (ret) {
// 添加失败,释放wakeup_source并返回NULL
wakeup_source_free(ws);
return NULL;
}
}
// 将wakeup_source添加到wakeup_source链表中
wakeup_source_add(ws);
}
// 返回wakeup_source指针
return ws;
}
EXPORT_SYMBOL_GPL(wakeup_source_register);
该函数用于创建wakeup_source并将其添加到wakeup_source列表中。wakeup_source是Linux内核中用于追踪系统唤醒源的机制,它允许标识哪些设备或驱动程序可能导致系统从低功耗状态中唤醒。通过追踪唤醒源,内核可以更有效地管理系统的低功耗模式和唤醒过程。
函数首先调用wakeup_source_create(name)创建一个新的wakeup_source,如果创建成功,则根据传入的设备指针dev判断是否将wakeup_source添加到sysfs中。如果dev为NULL或者设备已经注册,则通过调用wakeup_source_sysfs_add(dev, ws)将wakeup_source添加到sysfs中。最后,通过调用wakeup_source_add(ws)将wakeup_source添加到wakeup_source链表中。
返回值为指向创建的wakeup_source的指针,如果创建或添加失败,则返回NULL。
再看一下struct wakelock结构:
struct wakelock {
char *name; // 唤醒锁的名称
struct rb_node node; // 用于将唤醒锁添加到红黑树中
struct wakeup_source *ws; // 指向唤醒源结构体的指针
#ifdef CONFIG_PM_WAKELOCKS_GC
struct list_head lru; // 用于将唤醒锁添加到链表中(可选,依赖于配置选项)
#endif
};
/sys/class/wakeup目录下能查到注册到kernel中的wakelock
3.5 pm_wake_unlock
pm_wake_unlock和pm_wake_lock类似,如下:
/**
* pm_wake_unlock - 解锁并允许系统进入睡眠状态
* @buf: 要解锁的唤醒锁的名称
*
* 该函数用于解锁之前由调用者使用"pm_wake_lock"函数获取的唤醒锁。唤醒锁是系统用于阻止设备进入睡眠状态的机制,用于保证在某些关键任务或进程运行时设备保持唤醒状态。
*
* 参数:
* - @buf: 要解锁的唤醒锁的名称。该名称对应的唤醒锁将被解锁,如果没有其他活动的唤醒锁,则允许系统进入睡眠状态。
*
* 返回值:
* - 返回0表示成功。失败时返回以下错误代码之一:
* - -EPERM:调用者没有解锁唤醒锁的必要权限(CAP_BLOCK_SUSPEND)。
* - -EINVAL:参数无效,如空名称或以换行符结尾的名称。
* - 其他错误代码:如果添加唤醒锁时出现错误,则返回"wakelock_lookup_add"函数的错误代码。
*
* 用例示例:
* - pm_wake_unlock("example_wakelock");
*/
int pm_wake_unlock(const char *buf)
{
struct wakelock *wl;
size_t len;
int ret = 0;
// 检查调用者是否具有解锁唤醒锁的权限(CAP_BLOCK_SUSPEND)。
if (!capable(CAP_BLOCK_SUSPEND))
return -EPERM;
// 计算唤醒锁名称的长度,并检查其是否是有效的非空字符串。如果名称为空或仅包含换行符,则返回错误代码(-EINVAL)。
len = strlen(buf);
if (!len)
return -EINVAL;
// 如果唤醒锁名称以换行符结尾,则去除换行符以确保正确处理字符串。
if (buf[len-1] == '\n')
len--;
// 如果去除换行符后唤醒锁名称为空,则返回错误代码(-EINVAL)。
if (!len)
return -EINVAL;
// 获取wakelocks_lock互斥锁,以避免对唤醒锁列表的并发访问。
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);
// 将解锁的唤醒锁移动到LRU(Least Recently Used)列表中的最近位置,以优先进行垃圾回收。
wakelocks_lru_most_recent(wl);
// 执行垃圾回收(wakelocks_gc),从列表中删除任何过期或不必要的唤醒锁。
wakelocks_gc();
out:
// 释放wakelocks_lock互斥锁。
mutex_unlock(&wakelocks_lock);
// 返回操作结果(成功或错误代码)。
return ret;
}
pm_wake_unlock函数用于解锁之前由调用者使用pm_wake_lock函数获取的唤醒锁。唤醒锁是系统用于阻止设备进入睡眠状态的机制,用于保证在某些关键任务或进程运行时设备保持唤醒状态。该函数接收唤醒锁的名称作为参数,并解锁它,如果没有其他活动的唤醒锁,则允许系统进入睡眠状态。
3.6 pm_show_wakelocks
/**
* pm_show_wakelocks - 显示唤醒锁列表
* @buf: 存储唤醒锁列表信息的缓冲区
* @show_active: 是否只显示活动的唤醒锁(true表示只显示活动的唤醒锁,false表示显示所有唤醒锁)
*
* 该函数用于在指定缓冲区中显示唤醒锁列表的信息。唤醒锁是系统用于阻止设备进入睡眠状态的机制,用于保证在某些关键任务或进程运行时设备保持唤醒状态。
*
* 参数:
* - @buf: 存储唤醒锁列表信息的缓冲区。该缓冲区将用于输出唤醒锁列表的名称。
* - @show_active: 是否只显示活动的唤醒锁。如果该参数为true,则只显示活动的唤醒锁;如果为false,则显示所有唤醒锁。
*
* 返回值:
* - 返回成功输出的唤醒锁列表的长度(以字节为单位)。如果参数@buf为NULL或其他错误情况,则返回负值。
*
* 用例示例:
* - char buf[256];
* - ssize_t len = pm_show_wakelocks(buf, true);
* - if (len >= 0) {
* - // 处理唤醒锁列表的信息(存储在buf中)
* - } else {
* - // 处理错误情况
* - }
*/
ssize_t pm_show_wakelocks(char *buf, bool show_active)
{
struct rb_node *node;
struct wakelock *wl;
int len = 0;
// 获取wakelocks_lock互斥锁,以避免对唤醒锁列表的并发访问。
mutex_lock(&wakelocks_lock);
// 遍历唤醒锁列表,显示唤醒锁名称到指定缓冲区中。
for (node = rb_first(&wakelocks_tree); node; node = rb_next(node)) {
wl = rb_entry(node, struct wakelock, node);
// 只显示活动的唤醒锁,如果唤醒锁是活动的(show_active为true),则输出唤醒锁名称到缓冲区。
// 如果show_active为false,则显示所有唤醒锁。
if (wl->ws->active == show_active)
len += sysfs_emit_at(buf, len, "%s ", wl->name);
}
// 在缓冲区末尾添加换行符,以表示唤醒锁列表的结束。
len += sysfs_emit_at(buf, len, "\n");
// 释放wakelocks_lock互斥锁。
mutex_unlock(&wakelocks_lock);
// 返回成功输出的唤醒锁列表的长度(以字节为单位)。
return len;
}
pm_show_wakelocks函数用于在指定缓冲区中显示唤醒锁列表的信息。唤醒锁是系统用于阻止设备进入睡眠状态的机制,用于保证在某些关键任务或进程运行时设备保持唤醒状态。函数接收一个缓冲区和一个布尔参数,用于控制显示活动的唤醒锁还是所有唤醒锁。
3.7 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。
(Android GKI中CONFIG_PM_WAKELOCKS_GC没有使能)
参考文章:http://www.wowotech.net/pm_subsystem/wakelocks.html