Linux电源管理8(基于Linux6.6)---autosleep介绍
一、概述
Autosleep(自动休眠)是 Linux 操作系统中的一种电源管理机制,用于自动将系统进入低功耗状态(如睡眠模式),以延长设备的电池寿命,尤其适用于笔记本电脑、移动设备等对电源消耗有较高要求的场景。该机制依赖于硬件和内核的协作,能够在用户不再使用设备或系统空闲一段时间后自动进入休眠或睡眠状态,减少能源消耗。
1.1、功能
Linux 系统的 autosleep 功能旨在实现自动化的低功耗管理,具体包括以下功能:
-
自动进入睡眠模式:当系统在一段时间内没有用户活动(如按键、鼠标操作等),系统会自动进入休眠模式,关闭不必要的硬件组件(如显示器、硬盘等)以节省电能。
-
节能:通过进入低功耗状态(如 suspend 或 hibernation),系统能够在空闲时显著减少能源消耗,从而延长设备的续航时间。
-
恢复快速:系统从睡眠状态中恢复时能够快速响应,恢复到用户上次的工作状态,提供良好的用户体验。
-
支持多种睡眠模式:Linux 提供了不同级别的睡眠模式(如 suspend-to-RAM、suspend-to-disk、hibernate 等),系统可以根据电源状态、用户偏好以及硬件支持情况自动选择合适的模式。
-
硬件管理:通过与硬件的紧密配合,自动休眠功能不仅限于 CPU,还能够控制其他硬件组件,如屏幕、Wi-Fi 模块、蓝牙等,以最大化节省电能。
1.2、实现原理
Linux 中的 autosleep 机制通常通过 内核的电源管理子系统(Power Management Subsystem)来管理。该子系统在内核中提供了多种用于管理设备电源状态的 API 和功能。这些功能的实现通常依赖于以下几个组件和机制:
休眠模式(Sleep Modes)
Linux 内核支持多种低功耗模式,常见的包括:
- Suspend-to-RAM(Suspend):系统将大部分硬件设备置于低功耗模式,但保留内存中的数据(RAM)。CPU 和其他设备大多数被关闭,但 RAM 中的状态被保留,系统可以在较短时间内恢复。
- Suspend-to-disk(Hibernate):系统将内存中的数据保存到硬盘上,关闭所有硬件设备并完全断电。系统恢复时从硬盘读取内存数据,恢复到之前的状态。这种方式恢复时间较长,但节能效果更好。
- Idle(空闲)状态:在空闲状态下,CPU 和其他设备会进入较低的功耗状态,但不完全关闭。
内核的电源管理机制
-
ACPI(Advanced Configuration and Power Interface):ACPI 是 Linux 系统中广泛使用的硬件电源管理接口,它允许操作系统直接控制硬件的电源状态。内核通过 ACPI 来检测硬件的空闲状态,并控制硬件进入低功耗模式。ACPI 提供了对 CPU、显示器、硬盘等设备的电源管理支持。
-
CPUIDLE:这是 Linux 内核中的 CPU 空闲管理机制,它根据 CPU 的空闲程度选择合适的低功耗状态(如 C-states)。当 CPU 空闲时,它可以进入较低的功耗状态,从而节省电能。
-
Runtime PM(运行时电源管理):这是 Linux 内核用于管理外部设备(如硬盘、网络接口、显卡等)的电源状态的机制。设备会根据活动状态动态调整电源,空闲时进入低功耗模式。
-
PM QoS(Power Management Quality of Service):Linux 提供了一些机制,如 PM QoS 来保证系统中的电源管理不会影响到系统的性能。通过设置优先级,内核可以在系统需要休眠时平衡电源节省和性能需求。
自动休眠逻辑
自动休眠的实现一般会依赖于内核中的时间管理和空闲检测机制:
-
空闲检测:当系统在一段时间内没有用户输入(例如鼠标或键盘活动),内核会通过 空闲进程 或 空闲检测机制(idle detection)来判断是否可以进入休眠状态。
-
定时器:内核设置了一个空闲时间阈值(例如 5 分钟),当系统空闲时间达到这个阈值后,自动进入休眠模式。这个阈值和不同的休眠模式(如 suspend、hibernate)可以根据不同的硬件配置和用户需求进行调节。
-
用户空间控制:除了内核本身的自动休眠控制外,用户还可以通过一些工具(如
systemd
,pm-utils
,upower
等)来定制休眠行为。例如,用户可以手动设置系统何时自动休眠,或者配置在电池模式下系统自动进入睡眠。
外部事件与唤醒
系统可以根据外部事件来决定是否唤醒。例如:
- 定时唤醒:如果系统正在等待某些定时事件(如定时器),它可能会进入低功耗模式,直到事件触发时自动恢复。
- 硬件唤醒:某些硬件事件(如网络接口接收到数据包、USB 设备插入等)也可以触发系统的唤醒。
电池管理
对于笔记本等移动设备,电池电量管理非常重要。内核电源管理会监控电池电量,并根据电池状态来决定是否触发休眠。例如,当电池电量过低时,系统会自动进入低功耗模式(如 suspend)以延长电池寿命。
1.3、用户空间的管理工具
-
systemd:在现代 Linux 系统中,
systemd
提供了电源管理服务,它会定期检查系统是否处于空闲状态并决定是否进行休眠。systemd
还提供了与电源管理相关的服务,如systemd-sleep
,用于控制挂起、恢复等操作。 -
upower:
upower
是一个电源管理服务,它提供了一个 D-Bus 接口,供用户空间应用程序查询电池状态、设置休眠参数等。 -
pm-utils:这个工具集在旧版的 Linux 系统中较为常见,它提供了一组脚本和工具,用于管理电源状态,如
pm-suspend
和pm-hibernate
,可以用来控制休眠、挂起等操作。
二、实现框图
autosleep的实现位于kernel/power/autosleep.c中,基于wakeup count和suspend & hibernate功能,并通过PM core的main模块向用户空间提供sysfs文件(/sys/power/autosleep)
三、代码分析
3.1、/sys/power/autosleep
/sys/power/autosleep是在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;
if (state == PM_SUSPEND_MEM)
state = mem_sleep_current;
error = pm_autosleep_set_state(state);
return error ? error : n;
}
power_attr(autosleep);
#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中。
3.2、pm_autosleep_init
开始之前,先介绍一下autosleep的初始化函数,该函数在kernel PM初始化时(.\kernel\power\main.c:pm_init)被调用,负责初始化autosleep所需的2个全局参数:
1)一个名称为“autosleep”的wakeup source(autosleep_ws),在autosleep执行关键操作时,阻止系统休眠。
2)一个名称为“autosleep”的有序workqueue,用于触发实际的休眠动作(休眠应由经常或者线程触发)。
kernel/power/autosleep.c
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;
}
3.3、pm_autosleep_set_state
pm_autosleep_set_state负责设置autosleep的状态,autosleep状态所描述的电源管理状态一致,共有freeze、standby、STR、STD和off五种(具体依赖于系统实际支持的电源管理状态)。具体如下:
kernel/power/autosleep.c
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;
}
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中。
pm_wakep_autosleep_enabled主要用于更新wakeup source中和autosleep有关的信息,代码和执行逻辑如下:
drivers/base/power/wakeup.c
#ifdef CONFIG_PM_AUTOSLEEP
/**
* pm_wakep_autosleep_enabled - Modify autosleep_enabled for all wakeup sources.
* @enabled: Whether to set or to clear the autosleep_enabled flags.
*/
void pm_wakep_autosleep_enabled(bool set)
{
struct wakeup_source *ws;
ktime_t now = ktime_get();
rcu_read_lock();
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);
}
rcu_read_unlock();
}
#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,如下:
kernel/power/autosleep.c
static DECLARE_WORK(suspend_work, try_to_suspend);
void queue_up_suspend_work(void)
{
if (autosleep_state > PM_SUSPEND_ON)
queue_work(autosleep_wq, &suspend_work);
}
3.4、try_to_suspend
try_to_suspend是suspend的实际触发者,代码如下:
kernel/power/autosleep.c
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();
}
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。
3.5、pm_autosleep_state
该接口比较简单,获取autosleep_state的值返回即可。
suspend_state_t pm_autosleep_state(void)
{
return autosleep_state;
}