Linux电源管理(9)_wakelocks

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值