您的位置:首页 > 运维架构 > Linux

Linux wakelock与autosleep

2016-06-21 11:41 330 查看
内核版本: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
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: