Linux休眠唤醒之wakelock机制

Android wakelocks

Android wakelocks提供的功能包括:

1) 一个sysfs文件: /sys/power/wake_lock, 用户程序向文件写入一个字符串,即可创建一个wake lock, 该字符串就是wakelock的名字。 该wake lock可以阻止系统进入低功耗模式。

2) 一个sysfs文件: /sys/power/wake_unlock, 用户程序向文件写入相同的字符串, 即可注销一个wakelock。

3) 当系统中所有的wakelock都注销后, i系统可以自动进入低功耗状态

4) 向内核其他driver也提供了wakelock的创建和注销接口, 允许driver创建wakelock以阻止休眠, 注销wakelock以允许睡眠。

有关Android wakelocks更为详细的描述,参考下面一个链接:

http://elinux.org/Android_Power_Management

kernel wakelocks的功能

对比Android wakelocks要实现的功能, Linux kernel的方案是:

1) 允许driver创建wakelock以阻止睡眠, 注销wakelock以允许睡眠:已经由wakeup source取代

2) 当系统中所有的wakelock都注销后,系统可以自动进入低功耗模式:由autosleep实现

3) wake_lock和wake_unlock功能,由本文所描述的kernel wakelocks实现,其本质就是将wakeup source开发到用户空间访问。

kernel wakelocks在电源管理中的位置

相比Android wakelocks, Kernel wakelocks的实现非常简单,就是PM core中增加一个wakelock模块(kernel/power/wakelock.c), 该模块依赖wake events framework提供的wakeup source机制,实现用户空间的wakeup source(就是wakelocks), 并通过PM core main模块, 向用户空间提供两个同名的sysfs文件, wakelock和wake_unlock.

在这里插入图片描述

/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的本质是在指定的wakup source上activate一个wakeup events, 注销wakelock的本质是deactivate wakeup event。 因此, /sys/power/wake_lock和/sys/power/wake_unlock两个sysfs文件的功能就是:

写wake_lock(以wakelock name和timeout时间<可选>为参数), 相当于以wakeup source为参数调用__pm_stay_awake(或者__pm_wake_event), 即activate wakeup event;

写wake_unlocks(以wakelock name为参数), 相当于以wakeup source为参数, 调用__pm_relax;

读wake_lock, 获取系统中所有的处于active状态的wakelock列表(也即wakeup source列表)

写wake_unlock, 返回系统中所有的处于非active状态的wakelock信息(也即wakeup source列表)。

注1: 上面有关wakeup source的操作接口,

这两个sysfs文件在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;
}

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中实现。
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;

	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;
}
a) 输入参数为一个字符串, 如“wake_lock_test 100", 该字符串指定上报wakeup event的wakelock name, 可以在name后用空格隔开, 添加一个时间值(单位ns), 表示该event的timeout值
b) 调用capable, 查看当前进程是否具备阻止系统suspend的权限
注2: capable是Linux security子系统的一个接口,用于权限判断。 我们说过,power是系统的核心资源, 理应由OS全权掌握, 但wakelock违反了这一个原则, 将阻止系统睡眠的权力给了用户空间。 这样一来, 用户空间程序将可以随心所欲的占用power资源, 特别是用户态的程序员, 天生对资源占用不敏感的(这是对的), 就导致该接口有被滥用的风险, 不过还好,通过系统的权限管理机制,可以改善这种状态(其实不是改善,而是矛盾转移,很有可能吧最终的裁决权给到用户,太糟糕了);
c)解析字符串,将timeout值保存在timeout_ns中, 解析name长度,并将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 event; 否则调用__pm_stay_awake,上报一个没有时限的wakeup event。
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;
}
在wakelock.c中,维护一个名称为wakelocks_tree的红黑树所有的wakelock都保存在该tree上。因此该接口的动作是:
a) 查找红黑树,如果找到name相同的wakelock, 就返回wakelock指针
b) 如果没找到,切add_if_not_fount为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 {
	char			*name;
	struct rb_node		node;
	struct wakeup_source	ws;
#ifdef CONFIG_PM_WAKELOCKS_GC
	struct list_head	lru;
#endif
};
非常简单:一个name指针,保存wakelock的名称;一个rb node节点,用于组成红黑树;一个wakeup source变量;如果开启了wakelocks垃圾回收功能,一个用于GC的list head。
pm_wake_unlock
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;
}
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的active周期内,因此如果它的wakeup source状态为deactive, 应该销毁该wakelock。 但销毁后,如果又产生wakeup events, 就得重新建立。 如果这种建立->销毁->建立的过程太频繁, 效率就会降低。

因此,最好不销毁, 保留系统所有的wakelocks(同时可以完整的保留wakelock信息), 但如果wakelocks太多(特别是不活动的),将会占用很多内存,也不合理。

折衷方案: 保留一些非active状态的wakelock, 到一定时机时再销毁,这就是wakelocks的垃圾回收机制。

wakelocks GC功能可以开关(由CONFIG_PM_WAKELOCK_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位置,这是由三种操作保证的

a)wakelock创建时(见3.4小节),调用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的垃圾回收动作。
  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值