Linux休眠唤醒之autosleep

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.

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值