Linux电源管理-wakelock

2020-03-24 17:17:38 浏览数 (1)

前言

之前说过Google为了在user space阻止系统suspend,为Android设计出一套新的电源管理: wakelocks, early_suspend等。此机制修改了Linux原生的susupend流程,定义子自己的休眠接口。起初Android为了合入此patch和Linux内核开发者有一段时间的讨论。比如此地址:http://lwn.net/Articles/318611/

但是在Linux合入wakeup event framework,提出了wakeup source概念,同时解决suspend和wakeup event之间的同步问题之后。Android也随之抛弃了自己的wakelocks机制,重新利用Linux中wakeup source,设计了全新的wakelock。其实也就将kernel中的wakeup source开放到用户空间。

从wakelock.c上面的注释可以证明这一点。

代码语言:javascript复制
/*
 * kernel/power/wakelock.c
 *
 * User space wakeup sources support.
 *
 * Copyright (C) 2012 Rafael J. Wysocki <rjw@sisk.pl>
 *
 * This code is based on the analogous interface allowing user space to
 * manipulate wakelocks on Android.
 */

wakelock对比

user space

kernel

旧wakelock

往/sys/power/wake_lock写入字符串阻止系统进入suspend。 往/sys/power/wake_unlock写入字符串系统可以进入suspend。

wakelock在suspend的流程上加把锁,阻止suspend。 wakeunlock就是去掉这把锁。

新wakelock

基于wakeup event framework机制。 wakelock就是在kernel space激活一个wakeup event。 wakeunlock就是deactive一个wakeup event。

对user space来说,wakelock还是以前的wakelock,使用方法没变,API也没有变化。

对kernel space来说,变化是相当大,具体变化如上表。

数据结构

代码语言:javascript复制
struct wakelock {
	char			*name;
	struct rb_node		node;
	struct wakeup_source	ws;
#ifdef CONFIG_PM_WAKELOCKS_GC
	struct list_head	lru;
#endif
};

.name: 该wakelock对应的name。

.node: 红黑树节点,用于存储该wakelock。

.ws: 该wakelock对应的wakeup source。因为wakelock就是一个user space的wakeup source。

.lru: 用于wakelock的回收机制。

代码分析

上面也说了,往/sys/power/wake_lock写入字符串就阻止系统suspend下去,我们就带者这个思路一直探索下去。:)

代码语言:javascript复制
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;
}

此函数直接调用了pm_wake_lock函数。

代码语言:javascript复制
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;

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

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

1. 判断当前task是否有suspend系统的权限。

2. 解析传入进来的字符串,如果传入的字符串为"123 1000",则123就是wakelock存入到buf中,1000为定时器超时时间存入到timeout_ms中。

3. 调用wakelock_lookup_add函数,查找是否有相同的name的wakelock,如果有直接返回。如果没有,重新创建wakelock,然后将此wakelock加入到wakelocks_tree中,同时创建该wakelock对应的wakeup source。

4. 如果该wakelock有超时时间,则调用__pm_wakeup_event函数上报一个timeout_ns的wakeup events。否则调用__pm_stay_awake函数上报一个没有超时的wakeup events。

代码语言:javascript复制
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;
}

1. 获取红黑树的root节点,通过while循环,在红黑树中根据wakelock的name查找是否有相同的,如果查找到,返回该wakelock。

2. 如果没有找到,判断当前的wakelock的数目是否超过系统的上限。

代码语言:javascript复制
static inline bool wakelocks_limit_exceeded(void)
{
	return number_of_wakelocks > CONFIG_PM_WAKELOCKS_LIMIT;
}

通常CONFIG_PM_WAKELOCKS_LIMIT的数目为100。

3. 重新分配一个新的wakelock,设置wakelock, wakeup source的name,调用wakeup_source_add接口将此wakeup source加入到系统中。

4. 插入此wakelock到红黑树中。

5. 调用wakelocks_lru_add函数将此wakelock加入到wakelocks_lru_list表头,用于回收使用。

代码语言:javascript复制
static inline void wakelocks_lru_add(struct wakelock *wl)
{
	list_add(&wl->lru, &wakelocks_lru_list);
}

6. 增加系统wakelock的数量。

这时候系统持有一个wakelock,kernel层面就是有wakeup event正在处理中。当系统需要susupend的时候,就会调用pending接口检查到有wakeup event事件在处理,就需要abort suspend。只有当user space通过在wake_unlock设置字符串后,系统就可以进入低功耗模式。所以接下来分析deactive wakeup event过程。

代码语言:javascript复制
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;
}

此函数最终调用pm_wake_unlock接口。

代码语言:javascript复制
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);

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

1. 依旧需要判断当前task是否有suspend系统的权限。

2. 解析传入的字符串。

3. 依旧调用wakelock_lookup_add函数查找是否有相同name,如果有返回wakelock,否则返回错误。

4. 调用__pm_relax将wakeup source状态置为deactive。

5. 将wakelock从wakelocks_lru_list链表移除,然后又将其添加到链表头。

6. 调用wakelocks_gc执行wakelock的垃圾回收。

wake_lock和wake_unlock在sys中show函数如下,也就是显示系统中所有的lock和unlock的wakelock

代码语言:javascript复制
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);
}

此函数也就是遍历红黑树,通过show_active变量判断当前是lock或者unlock的。最后show出来即可。

wakelock垃圾回收

为什么需要垃圾回收?

如果一个wakelock需要频繁创建,销毁,效率就比较低。此时就需要将一些频繁使用的wakelock保存起来,再次使用的时候就可以快速获取。但是当一个系统的wakelock超过系统的上限就需要将一些一直不再使用的wakelock回收,这时候就需要wakelock的回收机制。

1. 定义一个wakelock_lru链表用于保存系统中所有的wakelock

代码语言:javascript复制
static LIST_HEAD(wakelocks_lru_list);

2. 定义一个变量,记录系统中wakelock的数量。

代码语言:javascript复制
static unsigned int wakelocks_gc_count;

3. 当创建wakelock的时候调用wakelocks_lru_add函数,将此wakelock添加到wakelock_lru链表head部。

代码语言:javascript复制
static inline void wakelocks_lru_add(struct wakelock *wl)
{
	list_add(&wl->lru, &wakelocks_lru_list);
}

4. 当调用unlock的时候,调用wakelocks_lru_most_recent函数,将wakelock移动到链表的head部,表示此wakelock是最近访问的。

5. 然后调用wakelocks_gc进行wakelock回收。

代码语言:javascript复制
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;
}

a. 如果当前系统wakelock小于系统上限(WL_GC_COUNT_MAX=100),则不用回收。

b. 从wakelocks_lru_list链表的末尾取一个wakelock,因为末尾的都是不经常使用的wakelock。如果此wakelock的idle时间没有超过(WL_GC_TIME_SEC * NSEC_PER_SEC)则不用回收,否则进行回收。

c. 如果此时wakelock的状态是deactive的,则进行回收。

d. 移除该wakelock的wakeup source,同时从红黑树中去除,从wakelock_lru链表去除,释放内存,减少wakelock的数目。

0 人点赞