Linux wakelock与autosleep

内核版本:Linux-4.5


. wakelock

Linux kernel在3.4及以后的版本中加入了wakelock机制,作用同之前的Android内核一样:
1. 为应用层提供接口来创建、释放wakelock
2. 在driver中,同样也可以创建、释放wakelock
3. 当所有的wakelock都释放掉之后,系统可以自动进入低功耗状态,由autosleep来实现

从应用层角度来看wakelock并没有什么变化,但是从底层实现角度来看,却并不完全一样。早期Android的内核版本中只是在suspend的流程上加了一把锁,只有当所有的锁释放掉之后,系统才能进入suspend状态,而自3.4之后的Linux内核版本中,是基于wakeup source实现的,创建wakelock本质上是active一个wakeup event,释放wakelock本质上是deactive wakeup event。


来看wake_lock、wake_unlock的sys接口函数:

#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 */
lock、unlock的show接口直接调用的pm_show_wakelocks,只是参数不一样。
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);
}
查询红黑树,返回处于active、deactive的wakelock。


再来看lock的写接口,直接调用pm_wake_lock()函数。

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;
}
例如往/sys/power/wake_lock文件写入wake_lock_test 1000,第一个参数为wakelock name,第二个参数为一个时间值,用空格隔开,单位为ns。

首先来判断进程是否具有阻止系统进入suspend的权限。

然后获取传递进来的时间值,调用wakelock_lookup_add()函数。在wakelock_lookup_add()函数中首先查找是否具有相同name的wakelock,如果找到,则直接返回该wakelock,否则需要重新申请一个wakelock。

如果指定了timerout值,那么调用__pm_wakeup_event()去上报一个有时限的wakeup event,否则调用__pm_stay_awake()上报一个没有时限的wakeup event。


最后来看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;
}
例如往/sys/power/wake_unlock文件写入wake_lock_test,解析该字符串,同样调用wakelock_lookup_add()函数,但是注意第三个参数为false,查找是否有这个name的wakelock,如果找到,则返回这个wakelock,否则的话直接返回。然后调用__pm_relax()函数去deactive一个wakeup source。


从上面代码可以看出wakelock的核心是wakeup source,通过active一个wakeup event来阻止系统进入suspend,通过deactive wakeup event来释放wakelock。


. autosleep

autosleep的sys接口还是在kernel/power/main.c中创建的,读写函数如下:
#ifdef CONFIG_PM_AUTOSLEEP
static ssize_t autosleep_show(struct kobject *kobj,
			      struct kobj_attribute *attr,
			      char *buf)
{
	suspend_state_t state = pm_autosleep_state();

	if (state == PM_SUSPEND_ON)
		return sprintf(buf, "off\n");

#ifdef CONFIG_SUSPEND
	if (state < PM_SUSPEND_MAX)
		return sprintf(buf, "%s\n", pm_states[state] ?
					pm_states[state] : "error");
#endif
#ifdef CONFIG_HIBERNATION
	return sprintf(buf, "disk\n");
#else
	return sprintf(buf, "error");
#endif
}

static ssize_t autosleep_store(struct kobject *kobj,
			       struct kobj_attribute *attr,
			       const char *buf, size_t n)
{
	suspend_state_t state = decode_state(buf, n);
	int error;

	if (state == PM_SUSPEND_ON
	    && strcmp(buf, "off") && strcmp(buf, "off\n"))
		return -EINVAL;

	error = pm_autosleep_set_state(state);
	return error ? error : n;
}

power_attr(autosleep);
#endif /* CONFIG_PM_AUTOSLEEP */
在屏亮情况下,查看/sys/power/autosleep文件的值为off,在屏灭情况下去查看该值通常为mem,当我们按下关机按钮之后会往该属性文件写入mem值来触发suspend流程。

在写函数中,直接调用autosleep.c中的pm_autosleep_set_state()函数。
int pm_autosleep_set_state(suspend_state_t state)
{

#ifndef CONFIG_HIBERNATION
	if (state >= PM_SUSPEND_MAX)
		return -EINVAL;
#endif

	__pm_stay_awake(autosleep_ws);

	mutex_lock(&autosleep_lock);

	autosleep_state = state;

	__pm_relax(autosleep_ws);

	if (state > PM_SUSPEND_ON) {
		pm_wakep_autosleep_enabled(true);
		queue_up_suspend_work();
	} else {
		pm_wakep_autosleep_enabled(false);
	}

	mutex_unlock(&autosleep_lock);
	return 0;
}
pm_autosleep_set_state()根据名字来看是设置sleep的一个状态,这个状态可以是on、freeze、standby、mem等,mem就是通常所说的suspend to memory,还有一个更为深度的休眠是suspend to disk。

在pm_autosleep_set_state()函数中,首先调用__pm_stay_awake()函数,保证系统不能休眠,保存sleep这个状态,然后根据这个state去做相应处理。

如果是suspend状态,那么首先调用pm_wakep_autosleep_enabled()去使能autosleep,紧接着调用一个workqueue,也就是调用try_to_suspend()函数。
static void try_to_suspend(struct work_struct *work)
{
	unsigned int initial_count, final_count;

	if (!pm_get_wakeup_count(&initial_count, true))
		goto out;

	mutex_lock(&autosleep_lock);

	if (!pm_save_wakeup_count(initial_count) ||
		system_state != SYSTEM_RUNNING) {
		mutex_unlock(&autosleep_lock);
		goto out;
	}

	if (autosleep_state == PM_SUSPEND_ON) {
		mutex_unlock(&autosleep_lock);
		return;
	}
	if (autosleep_state >= PM_SUSPEND_MAX)
		hibernate();
	else
		pm_suspend(autosleep_state);

	mutex_unlock(&autosleep_lock);

	if (!pm_get_wakeup_count(&final_count, false))
		goto out;

	/*
	 * If the wakeup occured for an unknown reason, wait to prevent the
	 * system from trying to suspend and waking up in a tight loop.
	 */
	if (final_count == initial_count)
		schedule_timeout_uninterruptible(HZ / 2);

 out:
	queue_up_suspend_work();
}
在try_to_suspend()函数中,首先调用pm_get_wakeup_count()函数去获取wakeup count,如果有active的wakeup source,这里会block住,直到所有的wakeup soucre变成deactive状态,如果你的系统不能进入suspend,那么多半是这里的问题。

然后调用pm_save_wakeup_count()保存这个wakeup count,调用hibernate()或pm_suspend()让系统进入suspend。


参考: http://www.wowotech.net/pm_subsystem/wakelocks.html
http://www.wowotech.net/pm_subsystem/autosleep.html

  • 0
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值