Linux电源管理(10)_autosleep
作者:wowo 发布于:2014-9-18 23:42 分类:电源管理子系统
1. 前言
Autosleep也是从Android wakelocks补丁集中演化而来的(Linux电源管理(9)_wakelocks),用于取代Android wakelocks中的自动休眠功能。它基于wakeup source实现,从代码逻辑上讲,autosleep是一个简单的功能,但背后却埋藏着一个值得深思的话题:
计算机的休眠(通常是STR、Standby、Hibernate等suspend操作),应当在什么时候、由谁触发?
蜗蜗在“Linux电源管理(2)_Generic PM之基本概念和软件架构”中有提过,在传统的操作场景下,如PC、笔记本电脑,这个问题很好回答:由用户、在其不想或不再使用时。
但在移动互联时代,用户随时随地都可能使用设备,上面的回答就不再成立,怎么办?这时,Android提出了“Opportunistic suspend(这个词汇太传神了,很难用简洁的中文去翻译,就不翻译了)”的理论,通俗的讲,就是“逮到机会就睡”。而autosleep功能,无论是基于Android wakelocks的autosleep,还是基于wakeup source的autosleep,都是为了实现“Opportunistic suspend”。
相比较“对多样的系统组件单独控制”的电源管理方案(如Linux kernel的Dynamic PM),“Opportunistic suspend”是非常简单的,只要检测到系统没有事情在做(逮到机会),就suspend整个系统。这对系统的开发人员(特别是driver开发者)来说,很容易实现,几乎不需要特别处理。
但困难的是,“系统没有事情在做”的判断依据是什么?能判断准确吗?会不会浪费过多的资源在"susend->resume-supsend…”的无聊动作上?如果只有一个设备在做事情,其它设备岂不是也得陪着耗电?等等…
所以,实现“Opportunistic suspend”机制的autosleep功能,是充满争议的。说实话,也是不优雅的。但它可以解燃眉之急,因而虽然受非议,却在Android设备中广泛使用。
其实Android中很多机制都是这样的(如wakelocks,如binder,等等),可以这样比方:Android是设计中的现实主义,Linux kernel是设计中的理想主义,当理想和现实冲突时,怎么调和?不只是Linux kernel,其它的诸如设计、工作和生活,都会遇到类似的冲突,怎么对待?没有答案,但有一个原则:不要偏执,不要试图追求非黑即白的真理!
我们应该庆幸有Android这样的开源软件,让我们可以对比,可以思考。偏题有点远,言归正传吧,去看看autosleep的实现。
2. 功能总结和实现原理
经过前言的瞎聊,Autosleep的功能很已经很直白了,“系统没有事情在做”的时候,就将系统切换到低功耗状态。
根据使用场景,低功耗状态可以是Freeze、Standby、Suspend to RAM(STR)和Suspend to disk(STD)中的任意一种。而怎么判断系统没有事情在做呢?依赖wakeup events framework。只要系统没有正在处理和新增的wakeup events,就尝试suspend,如果suspend的过程中有events产生,再resume就是了。
由于suspend/resume的操作如此频繁,解决同步问题就越发重要,这也要依赖wakeup events framework及其wakeup count功能。
3. 在电源管理中的位置
autosleep的实现位于kernel/power/autosleep.c中,基于wakeup count和suspend & hibernate功能,并通过PM core的main模块向用户空间提供sysfs文件(/sys/power/autosleep)
注1:我们在“Linux电源管理(8)_Wakeup count功能”中,讨论过wakeup count功能,本文的autosleep,就是使用wakeup count的一个实例。
4. 代码分析
4.1 /sys/power/autosleep
/sys/power/autosleep是在kernel/power/main.c中实现的,如下:
1: #ifdef CONFIG_PM_AUTOSLEEP
2: static ssize_t autosleep_show(struct kobject *kobj,
3: struct kobj_attribute *attr,
4: char *buf)
5: {
6: suspend_state_t state = pm_autosleep_state();
7:
8: if (state == PM_SUSPEND_ON)
9: return sprintf(buf, "off\n");
10:
11: #ifdef CONFIG_SUSPEND
12: if (state < PM_SUSPEND_MAX)
13: return sprintf(buf, "%s\n", valid_state(state) ?
14: pm_states[state] : "error");
15: #endif
16: #ifdef CONFIG_HIBERNATION
17: return sprintf(buf, "disk\n");
18: #else
19: return sprintf(buf, "error");
20: #endif
21: }
22:
23: static ssize_t autosleep_store(struct kobject *kobj,
24: struct kobj_attribute *attr,
25: const char *buf, size_t n)
26: {
27: suspend_state_t state = decode_state(buf, n);
28: int error;
29:
30: if (state == PM_SUSPEND_ON
31: && strcmp(buf, "off") && strcmp(buf, "off\n"))
32: return -EINVAL;
33:
34: error = pm_autosleep_set_state(state);
35: return error ? error : n;
36: }
37:
38: power_attr(autosleep);
39: #endif /* CONFIG_PM_AUTOSLEEP */
a)autosleep不是一个必须的功能,可以通过CONFIG_PM_AUTOSLEEP打开或关闭该功能。
b)autosleep文件和state文件类似:
读取,返回“freeze”,“standby”,“mem”,“disk”, “off”,“error”等6个字符串中的一个,表示当前autosleep的状态,分别是auto freeze、auto standby、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中。
4.2 pm_autosleep_init
开始之前,先介绍一下autosleep的初始化函数,该函数在kernel PM初始化时(.\kernel\power\main.c:pm_init)被调用,负责初始化autosleep所需的2个全局参数:
1)一个名称为“autosleep”的wakeup source(autosleep_ws),在autosleep执行关键操作时,阻止系统休眠(我们可以从中理解wakeup source的应用场景和使用方法)。
2)一个名称为“autosleep”的有序workqueue,用于触发实际的休眠动作(休眠应由经常或者线程触发)。这里我们要提出2个问题:什么是有序workqueue?为什么要使用有序workqueue?后面分析代码时会有答案。
如下:
1: int __init pm_autosleep_init(void)
2: {
3: autosleep_ws = wakeup_source_register("autosleep");
4: if (!autosleep_ws)
5: return -ENOMEM;
6:
7: autosleep_wq = alloc_ordered_workqueue("autosleep", 0);
8: if (autosleep_wq)
9: return 0;
10:
11: wakeup_source_unregister(autosleep_ws);
12: return -ENOMEM;
13: }
4.3 pm_autosleep_set_state
pm_autosleep_set_state负责设置autosleep的状态,autosleep状态和“Linux电源管理(5)_Hibernate和Sleep功能介绍”所描述的电源管理状态一致,共有freeze、standby、STR、STD和off五种(具体依赖于系统实际支持的电源管理状态)。具体如下:
1: int pm_autosleep_set_state(suspend_state_t state)
2: {
3:
4: #ifndef CONFIG_HIBERNATION
5: if (state >= PM_SUSPEND_MAX)
6: return -EINVAL;
7: #endif
8:
9: __pm_stay_awake(autosleep_ws);
10:
11: mutex_lock(&autosleep_lock);
12:
13: autosleep_state = state;
14:
15: __pm_relax(autosleep_ws);
16:
17: if (state > PM_SUSPEND_ON) {
18: pm_wakep_autosleep_enabled(true);
19: queue_up_suspend_work();
20: } else {
21: pm_wakep_autosleep_enabled(false);
22: }
23:
24: mutex_unlock(&autosleep_lock);
25: return 0;
26: }
a)判断state是否合法。
b)调用__pm_stay_awake,确保系统不会休眠。
c)将state保存在一个全局变量中(autosleep_state)。
d)调用__pm_relax,允许系统休眠。
e)根据state的状态off还是其它,调用wakeup events framework提供的接口pm_wakep_autosleep_enabled,使能或者禁止autosleep功能。
f)如果是使能状态,调用内部接口queue_up_suspend_work,将suspend work挂到autosleep workqueue中。
注2:由这里的实例可以看出,此时wakeup source不再是wakeup events的载体,而更像一个lock(呵呵,Android wakelocks的影子)。
注3:
该接口并没有对autosleep state的当前值做判断,也就意味着用户程序可以不停的调用该接口,设置autosleep state,如写“mem”,写“freeze”,写“disk”等等。那么suspend work将会多次queue到wrokqueue上。
而在多核CPU上,普通的workqueue是可以在多个CPU上并行执行多个work的。这恰恰是autosleep所不能接受的,因此autosleep workqueue就必须是orderd workqueue。所谓ordered workqueue,就是统一时刻最多执行一个work的worqueue(具体可参考include\linux\workqueue.h中的注释)。
那我们再问,为什么不判断一下状态内?首先,orderd workqueue可以节省资源。其次,这样已经够了,何必多费心思呢?简洁就是美。
pm_wakep_autosleep_enabled主要用于更新wakeup source中和autosleep有关的信息,代码和执行逻辑如下:
1: #ifdef CONFIG_PM_AUTOSLEEP
2: /**
3: * pm_wakep_autosleep_enabled - Modify autosleep_enabled for all wakeup sources.
4: * @enabled: Whether to set or to clear the autosleep_enabled flags.
5: */
6: void pm_wakep_autosleep_enabled(bool set)
7: {
8: struct wakeup_source *ws;
9: ktime_t now = ktime_get();
10:
11: rcu_read_lock();
12: list_for_each_entry_rcu(ws, &wakeup_sources, entry) {
13: spin_lock_irq(&ws->lock);
14: if (ws->autosleep_enabled != set) {
15: ws->autosleep_enabled = set;
16: if (ws->active) {
17: if (set)
18: ws->start_prevent_time = now;
19: else
20: update_prevent_sleep_time(ws, now);
21: }
22: }
23: spin_unlock_irq(&ws->lock);
24: }
25: rcu_read_unlock();
26: }
27: #endif /* CONFIG_PM_AUTOSLEEP */
a)更新系统所有wakeup souce的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。
queue_up_suspend_work比较简单,就是把suspend_work挂到workqueue,等待被执行。而suspend_work的处理函数为try_to_suspend,如下:
1: static DECLARE_WORK(suspend_work, try_to_suspend);
2:
3: void queue_up_suspend_work(void)
4: {
5: if (autosleep_state > PM_SUSPEND_ON)
6: queue_work(autosleep_wq, &suspend_work);
7: }
4.4 try_to_suspend
try_to_suspend是suspend的实际触发者,代码如下:
1: static void try_to_suspend(struct work_struct *work)
2: {
3: unsigned int initial_count, final_count;
4:
5: if (!pm_get_wakeup_count(&initial_count, true))
6: goto out;
7:
8: mutex_lock(&autosleep_lock);
9:
10: if (!pm_save_wakeup_count(initial_count) ||
11: system_state != SYSTEM_RUNNING) {
12: mutex_unlock(&autosleep_lock);
13: goto out;
14: }
15:
16: if (autosleep_state == PM_SUSPEND_ON) {
17: mutex_unlock(&autosleep_lock);
18: return;
19: }
20: if (autosleep_state >= PM_SUSPEND_MAX)
21: hibernate();
22: else
23: pm_suspend(autosleep_state);
24:
25: mutex_unlock(&autosleep_lock);
26:
27: if (!pm_get_wakeup_count(&final_count, false))
28: goto out;
29:
30: /*
31: * If the wakeup occured for an unknown reason, wait to prevent the
32: * system from trying to suspend and waking up in a tight loop.
33: */
34: if (final_count == initial_count)
35: schedule_timeout_uninterruptible(HZ / 2);
36:
37: out:
38: queue_up_suspend_work();
39: }
该接口是wakeup count的一个例子,根据我们在“Linux电源管理(8)_Wakeup count功能”的分析,就是read wakeup count,write wakeup count,suspend,具体为:
a)调用pm_get_wakeup_count(block为true),获取wakeup count,保存在initial_count中。如果有wakeup events正在处理,阻塞等待。
b)将读取的count,写入。如果成功,且当前系统状态为running,根据autosleep状态,调用hibernate或者pm_suspend,suspend系统。
d)如果写count失败,说明读写的过程有events产生,退出,进行下一次尝试。
e)如果suspend的过程中,或者是suspend之后,产生了events,醒来,再读一次wakeup count(此时不再阻塞),保存在final_count中。
f)如果final_count和initial_count相同,发生怪事了,没有产生events,竟然醒了。可能有异常,不能再立即启动autosleep(恐怕陷入sleep->wakeup->sleep->wakeup的快速loop中),等待0.5s,再尝试autosleep。
4.5 pm_autosleep_state
该接口比较简单,获取autosleep_state的值返回即可。
原创文章,转发请注明出处。蜗窝科技,www.wowotech.net。

评论:
2015-07-01 19:28
__pm_stay_awake(autosleep_ws);
mutex_lock(&autosleep_lock);
autosleep_state = state;
__pm_relax(autosleep_ws);
----这里为什么先lock住系统,然后又释放掉啊?
谢谢!
2015-07-01 19:40
2015-07-03 10:55
1.加个互斥锁就可以了,为啥还要__pm_stay_awake和__pm_relax?先"确保系统不会休眠",然后“允许系统休眠”。
2.为什么保护这个共享资源(autosleep_state),就可以避免产生同步问题了?
谢谢!
2015-07-03 14:09
/*
* Note: it is only safe to mutex_lock(&autosleep_lock) if a wakeup_source
* is active, otherwise a deadlock with try_to_suspend() is possible.
* Alternatively mutex_lock_interruptible() can be used. This will then fail
* if an auto_sleep cycle tries to freeze processes.
*/
就是说,mutex_lock(&autosleep_lock)的调用,必须在有active状态的wakeup_source才安全,否则在 try_to_suspend()时可能发生死锁,我的理解是:
例如,pm_autosleep_set_state中,需要更改autosleep_state(一个全局变量),因而需要使用mutex_lock保护临界区。如果调用时序如下:
1. 系统具备suspend的条件,调用try_to_suspend试图suspend系统,该接口调用mutex_lock(&autosleep_lock);,持有锁。
2. 发生调度,执行pm_autosleep_set_state所在的进程,在该接口中调用mutex_lock(&autosleep_lock)。因为此时锁已经被try_to_suspend占用,进程sleep,且状态为TASK_UNINTERRUPTIBLE(这个状态会导致task freezing失败)。
3. 发生调度,继续执行try_to_suspend,直到执行freeze操作(suspend_freeze_processes)。由于有进程处于TASK_UNINTERRUPTIBLE状态,freeze失败,suspend失败。
因此,出现了“具备suspend条件,却suspend失败了”,就是上面注释中所说的deadlock问题。所以在mutex_lock(&autosleep_lock)调用前,保持系统不会suspend,就可以解决这种问题。
2015-03-23 19:41
目前我已經用eclipse寫好一個程式,它的動作是,
按下button on後suspend 5 秒,再resume 5 秒,持續三次,
但我想在跑完這三次suspend/resume後,判斷是否有真正susend/resume,
判斷完畢並show出一個log,顯示pass or fail,
但我不清楚應該如何寫出這個判斷是才能斷定是否真正有執行到suspend/resume這些動作,
可否麻煩您提供一些寶貴的意見,
謝謝!
2015-03-23 19:49
如下(注意第一行信息中的success,可能就是您需要的):
cat /sys/kernel/debug/suspend_stats
success: 2
fail: 0
failed_freeze: 0
failed_prepare: 0
failed_suspend: 0
failed_suspend_late: 0
failed_suspend_noirq: 0
failed_resume: 0
failed_resume_early: 0
failed_resume_noirq: 0
failures:
last_failed_dev:
last_failed_errno: 0
0
last_failed_step:
2015-02-07 14:53
不是说看大部头不重要,没个程序员必须啃大部头,但是看你们写的文章,思路更清晰,更容易理解。
尤其你们还能抽时间出来写,更新的那么快,很敬佩你们。
只有经常来看你们的文章,以此支持你们,^_^~~~
2015-02-06 08:54
我的是安卓(STR),我执行echo mem > /sys/power/state之后,屏幕黑了一下马上又亮了,然后我读取cat /sys/kernel/debug/rpm_stats,这里面的count一直为0.
所以说我的系统一直没有sleep。
那么像我这种情况,如何去定位系统为什么没睡眠?比如哪个驱动,哪个硬件导致的?
2015-02-27 15:21
新年好..!!
再请教一个问题,如果我要用一个外围设备(Zigbee),通过UART时不时的收发一些数据。
理论上应该做到这样:1.active的情况下传输数据。2.不传输数据时睡眠。3.有数据进来,使用中断唤醒。
这个看上去简单,但是牵涉到的知识点还是很多,最近一直在做知识储备,就在读你们的电源管理和中断的文章。
而我现在困惑的是,这里面的电源管理的情况。
在这个硬件的工作环境中,电源管理是由UART(tty core)来决定?还是需要在Zigbee的驱动中,来做suspend和resume的编写,来关闭Tx Rx以及Zigbee的clock?
2015-02-27 17:10
中断时指哪个中断呢?ZigBee的中断还是Uart的中断?
唤醒源是ZigBee模块吗?ZigBee接收数据唤醒?如果是,关闭ZigBee的CLock是否影响唤醒。
基本的原则是:Uart driver和ZigBee driver是两个单独的driver,电源管理应该独立,互不干涉,这样模块化才好。基于这样的基本原则,再思考接下来的问题。
2015-02-27 17:48
现在如果只不考虑关闭Zigbee模块,让它一直开着。
那UART驱动这部分的电源管理应该是linux的串口驱动自己autosleep?因为我只配置了几个UART的device tree,基本上的数据收发都能实现,我只用到“open /dev/ttyHSL1”就打开了UART。而在芯片厂商给我的Zigbee驱动模块,都没有suspend和resume的部分。
我不了解UART的电源管理部分,目前正看UART的实现代码,在加上你写的linux电源管理部分,希望慢慢理解。一步一步来。
功能
最新评论
- wangjing
写得太好了 - wangjing
写得太好了! - DRAM
圖面都沒辦法顯示出來好像掛點了。 - Simbr
bus至少是不是还有个subsystem? - troy
@testtest:只要ldrex-modify-strex... - gh
Linux 内核在 sparse 内存模型基础上实现了vme...
文章分类
随机文章
文章存档
- 2025年4月(5)
- 2024年2月(1)
- 2023年5月(1)
- 2022年10月(1)
- 2022年8月(1)
- 2022年6月(1)
- 2022年5月(1)
- 2022年4月(2)
- 2022年2月(2)
- 2021年12月(1)
- 2021年11月(5)
- 2021年7月(1)
- 2021年6月(1)
- 2021年5月(3)
- 2020年3月(3)
- 2020年2月(2)
- 2020年1月(3)
- 2019年12月(3)
- 2019年5月(4)
- 2019年3月(1)
- 2019年1月(3)
- 2018年12月(2)
- 2018年11月(1)
- 2018年10月(2)
- 2018年8月(1)
- 2018年6月(1)
- 2018年5月(1)
- 2018年4月(7)
- 2018年2月(4)
- 2018年1月(5)
- 2017年12月(2)
- 2017年11月(2)
- 2017年10月(1)
- 2017年9月(5)
- 2017年8月(4)
- 2017年7月(4)
- 2017年6月(3)
- 2017年5月(3)
- 2017年4月(1)
- 2017年3月(8)
- 2017年2月(6)
- 2017年1月(5)
- 2016年12月(6)
- 2016年11月(11)
- 2016年10月(9)
- 2016年9月(6)
- 2016年8月(9)
- 2016年7月(5)
- 2016年6月(8)
- 2016年5月(8)
- 2016年4月(7)
- 2016年3月(5)
- 2016年2月(5)
- 2016年1月(6)
- 2015年12月(6)
- 2015年11月(9)
- 2015年10月(9)
- 2015年9月(4)
- 2015年8月(3)
- 2015年7月(7)
- 2015年6月(3)
- 2015年5月(6)
- 2015年4月(9)
- 2015年3月(9)
- 2015年2月(6)
- 2015年1月(6)
- 2014年12月(17)
- 2014年11月(8)
- 2014年10月(9)
- 2014年9月(7)
- 2014年8月(12)
- 2014年7月(6)
- 2014年6月(6)
- 2014年5月(9)
- 2014年4月(9)
- 2014年3月(7)
- 2014年2月(3)
- 2014年1月(4)
2015-09-04 15:28
d)如果写count失败,说明读写的过程有events产生,退出,进行下一次尝试。
=>try_to_suspend函数里面,out:好像无论怎么样都会执行,也就是说kernel会不断尝试睡眠,这样的话(以Android为例),执行一次写一次state就好了呀.kernel就会不断尝试睡眠了
这样理解对吗,感觉怪该的