autosleep
autosleep也是从android wakelocks补丁集中演化而来的,用于取代wakelock中的自动休眠功能。它基于wakeup source实现。从代码逻辑上讲, autosleep是一个简单的功能,但是背后却埋藏这一个令人深思的问题。
计算机的休眠(通常是STR, Standby, Hibernate等suspend操作), 应当在什么时候,由谁触发?
对于pc, 笔记本来说: 当用户在其不想使用或不再使用时
对于移动设备: 逮到机会就睡,困难的点在于如何判定系统没有事情可做。
功能总结和实现原理
根据使用场景,低功耗状态可以是Freeze, Standby, Suspend to RAM和suspend to disk中的任意一种。 而怎么判断系统没有事情在做呢? 依赖wakeup events framework。 只要系统没有正在处理和新增的wakeup events, 就尝试suspend, 如果suspend过程中有events产生,再resume就是了。
由于suspend/resume的操作如此频繁,解决同步问题就越发重要,这也要依赖wakeup events framework及其wakeup count功能。
在电源管理中的位置
autosleep的实现位于kernel/power/autosleep.c中,基于wakeup count & hibernate功能, 并通过PM core的main模块向用户空间提供sysfs文件(sys/power/autosleep)
注1: 我们之前讨论过wakeup count功能,这个就是使用wakeup count的一个实例。
代码分析
/sys/power/autosleep
/sys/power/autosleep是在kernel/power/main.c中实现的, 如下:
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;
}
a) autosleep不是一个必须的功能, 可以通过CONFIG_PM_AUTOSLEEP打开或关闭该功能
b) autosleep文件和state类似
读取, 返回”freeze", "standby", "mem", "disk", "off", "error"等6个字符串中的一个, 表示当前autosleep的状态, 分别是auto freeze, autostandby, auto STR, auto STD, autosleep功能关闭和当前系统不支持该autosleep的错误指示;
写入“freeze", "standby", "mem", "disk", "off"等5个字符串中的一个, 代表将autosleep切换到指定状态。
c) autosleep的读取, 由pm_autosleep_state实现,autosleep的写入, 由pm_autosleep_set_state实现。 这两个接口为autosleep模块提供的核心接口,位于kernel/power/autosleep.c中。
pm_autosleep_init
开始之前,先介绍一下autosleep的全局初始化函数, 该函数在kernel PM初始化时(/kernel/power/main.c:pm_init)被调用,负责初始化autosleep所需的两个全局参数:
1) 一个名称为”autosleep“的wakeup source(autosleep_ws), 在autosleep执行关键操作时, 阻止系统休眠(我们可以从中理解wakeup source的应用场景和使用方法)。
2) 一个名称为autosleep的有序workqueue, 用于触发实际的休眠动作(休眠应由进程或者线程触发)。 这里我们要提出两个问题,1, 什么是有序workqueue?为什么要使用有序workqueue
int __init pm_autosleep_init(void)
{
autosleep_ws = wakeup_source_register("autosleep");
if (!autosleep_ws)
return -ENOMEM;
autosleep_wq = alloc_ordered_workqueue("autosleep", 0);
if (autosleep_wq)
return 0;
wakeup_source_unregister(autosleep_ws);
return -ENOMEM;
}
pm_autosleep_set_state
pm_autosleep_set_state负责设置autosleep的状态, autosleep状态有freeze, standby, STR, STD和off5种(具体依赖于系统支持的电源管理状态)。
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;
}
1) 判断state是否合法
2) 调用__pm_stay_awake,确保系统不会休眠
3) 调用__Pm_relax,允许系统休眠
4) 根据state的状态是off还是其他,调用wakeup events framework提供的接口
pm_wakeup_autosleep_enabled, 使能或者禁止autosleep功能。
5)如果是使能状态,调用内部接口queue_up_suspend_work, 将suspend work挂到autosleep workqueue中
注2: 由这里的实例可以看出,此时wakeup source不再是wakeup events的载体, 而更像一个lock(android wakelock的影子)
注3: 该接口并没有对autosleep state的当前值做判断,也就意味着用户程序可以不停的调用该接口,设置auto sleep state, 如写"mem", "freeze", 写disk等等,那么suspend work将会多次queue到workqueue上。
而在多核cpu上, 普通的workqueue是可以在多个cpu上并行执行多个work的。这恰恰是autosleep所不能接受的, 因此autosleep workqueue就必须是ordered workqueue. 所谓ordered workqueue, 就是同一时刻最多执行一个work的workqueue(具体可以参考include\linux\workqueue.h的注释)。
那我们再问,为什么不判断一下状态呢?首先,ordered workqueue可以节省资源。 其次,这样已经够了,何必多费心思
pm_wakep_autosleep_enabled主要用于更新wakeup source中和auto sleep有关的信息,代码和执行逻辑如下:
void pm_wakep_autosleep_enabled(bool set)
{
struct wakeup_source *ws;
ktime_t now = ktime_get();
int srcuidx;
srcuidx = srcu_read_lock(&wakeup_srcu);
list_for_each_entry_rcu(ws, &wakeup_sources, entry) {
spin_lock_irq(&ws->lock);
if (ws->autosleep_enabled != set) {
ws->autosleep_enabled = set;
if (ws->active) {
if (set)
ws->start_prevent_time = now;
else
update_prevent_sleep_time(ws, now);
}
}
spin_unlock_irq(&ws->lock);
}
srcu_read_unlock(&wakeup_srcu, srcuidx);
}
#endif /* CONFIG_PM_AUTOSLEEP */
a) 更新系统所有的wakeup source的autosleep_enabled标志
b) 如果wakeup source处于active状态,意味着他会阻止autosleep, 切当前autosleep为enable, 将start_prevent_time设置为当前时间(开始阻止)
c) 如果wakeup source处于active状态,切autosleep是disable(说明这个wakeup source一直坚持到autosleep被禁止), 调用update_prevent_sleep_time接口, 更新wakeup source的prevent_sleep_time.