linux suspend 机制
一共就四种模式
include/linux/suspend.h
#define PM_SUSPEND_ON ((__force suspend_state_t) 0)
#define PM_SUSPEND_TO_IDLE ((__force suspend_state_t) 1)
#define PM_SUSPEND_STANDBY ((__force suspend_state_t) 2)
#define PM_SUSPEND_MEM ((__force suspend_state_t) 3)
#define PM_SUSPEND_MIN PM_SUSPEND_TO_IDLE
#define PM_SUSPEND_MAX ((__force suspend_state_t) 4)
可写入的四种状态(对应内核 enum suspend_state,定义在 include/linux/suspend.h): ┌─────────┬─────────────────────────────┬─────────────────────────────────────────────────────┬────────┐ │ 状态 │ 内核枚举 │ 含义 │ ACPI │ ├─────────┼─────────────────────────────┼─────────────────────────────────────────────────────┼────────┤ │ freeze │ PM_SUSPEND_TO_IDLE │ 仅冻结用户进程,CPU 进入 idle,设备进入各自低功耗态 │ s2idle │ ├─────────┼─────────────────────────────┼─────────────────────────────────────────────────────┼────────┤ │ standby │ PM_SUSPEND_STANDBY │ 待机(Power-on suspend),CPU 停止执行但保持上下文 │ S1 │ ├─────────┼─────────────────────────────┼─────────────────────────────────────────────────────┼────────┤ │ mem │ PM_SUSPEND_MEM │ 挂起到内存(Suspend-to-RAM) │ S3 │ ├─────────┼─────────────────────────────┼─────────────────────────────────────────────────────┼────────┤ │ disk │ PM_SUSPEND_MAX(hibernation) │ 挂起到磁盘(Hibernation) │ S4 │ └─────────┴─────────────────────────────┴─────────────────────────────────────────────────────┴────────┘
功耗/恢复速度 浅 ──────────────────────────────► 深
freeze(s2idle) standby(S1) mem/deep(S3) disk/hibernate(S4)
▲ ▲ ▲ ▲
│ │ │ └─ 内存内容写入磁盘,整机断电
│ │ └─ 上下文保存在 RAM,仅 RAM 保持供电
│ └─ CPU 停止但保持供电,上下文在 CPU 内
└─ 进程冻结,CPU/设备低功耗 idle,不涉及平台睡眠流程
由于接口的历史兼容性,大致是这样的:
写入 /sys/power/state 的内容 |
mem_sleep 当前选择 |
请求进入的模式 |
|---|---|---|
freeze |
无论选了哪个 | Suspend-to-idle |
standby |
无论选了哪个 | Standby |
mem |
s2idle |
Suspend-to-idle |
mem |
shallow |
Standby |
mem |
deep |
Suspend-to-RAM |
disk |
首先进入独立的 hibernation 流程 | 休眠到持久存储;后续动作由 /sys/power/disk 配置 |
关于 s2idle cpuidle_idle_call 中注释:
/*
* Suspend-to-idle ("s2idle") is a system state in which all user space
* has been frozen, all I/O devices have been suspended and the only
* activity happens here and in interrupts (if any). In that case bypass
* the cpuidle governor and go straight for the deepest idle state
* available. Possibly also suspend the local tick and the entire
* timekeeping to prevent timer interrupts from kicking us out of idle
* until a proper wakeup interrupt happens.
*/
只有中断才可以导致出来。
sysfs 接口
cd /sys/power
🧀 grep . *
state:freeze mem disk
disk:[platform] shutdown reboot suspend test_resume
mem_sleep:s2idle [deep]
freeze_filesystems:0
hibernate_compression_threads:3
image_size:3296653312
pm_async:1
pm_debug_messages:0
pm_freeze_timeout:20000
pm_print_times:0
pm_test:[none] core processors platform devices freezer
pm_trace:0
pm_trace_dev_match:tty
pm_trace_dev_match:tty
reserved_size:1048576
resume:0:0
resume_offset:0
sync_on_suspend:1
wakeup_count:0
接口的含义
- /sys/power/state : 触发 freeze mem disk
const char * const pm_labels[] = { [PM_SUSPEND_TO_IDLE] = "freeze", [PM_SUSPEND_STANDBY] = "standby", [PM_SUSPEND_MEM] = "mem", }; - /sys/power/mem_sleep : 用于选择 mem 下的子模式
- s2idle(suspend-to-idle / freeze)— 最浅,依赖设备自身的运行时电源管理
- shallow(standby / S1)— 平台提供浅睡眠
- deep(suspend-to-RAM / S3)— 最深的传统挂起,RAM 之外全部断电
static const char * const mem_sleep_labels[] = { [PM_SUSPEND_TO_IDLE] = "s2idle", [PM_SUSPEND_STANDBY] = "shallow", [PM_SUSPEND_MEM] = "deep", }; - /sys/power/disk : 和 mem_sleep 类似
static const char * const hibernation_modes[] = {
[HIBERNATION_PLATFORM] = "platform",
[HIBERNATION_SHUTDOWN] = "shutdown",
[HIBERNATION_REBOOT] = "reboot",
#ifdef CONFIG_SUSPEND
[HIBERNATION_SUSPEND] = "suspend",
#endif
[HIBERNATION_TEST_RESUME] = "test_resume",
};
- platform — 由平台固件完成(ACPI S4)
- shutdown — 直接关机,内核引导时恢复(resume=kernel 参数)
- reboot — 重启后恢复
- suspend — 混合挂起(hybrid suspend):先写内存(快速恢复),同时写磁盘(防断电丢数据)
-
还有调试用的 test / test_resume
- stat 一共四个模式
- freeze
- standby
- mem
- disk
/sys/power/mem_sleep 和 /sys/power/disk 都是子模式的扩展
mem_sleep 是"mem 这个令牌的解释器" state 是"要睡哪个状态"
───────────────────────────────── ─────────────────────────
echo deep > /sys/power/mem_sleep echo mem > /sys/power/state
实操上的对应关系
- echo mem + mem_sleep=deep : 直接睡到 S3
- echo mem + mem_sleep=s2idle : echo freeze
- echo standby 与 echo shallow(经 mem 间接)指向同一状态
都是直接指向的结果:
static ssize_t state_store(struct kobject *kobj, struct kobj_attribute *attr,
const char *buf, size_t n)
{
suspend_state_t state;
int error;
error = pm_autosleep_lock();
if (error)
return error;
if (pm_autosleep_state() > PM_SUSPEND_ON) {
error = -EBUSY;
goto out;
}
state = decode_state(buf, n);
if (state < PM_SUSPEND_MAX) {
if (state == PM_SUSPEND_MEM)
state = mem_sleep_current;
error = pm_suspend(state);
} else if (state == PM_SUSPEND_MAX) {
error = hibernate();
} else {
error = -EINVAL;
}
out:
pm_autosleep_unlock();
return error ? error : n;
}
/sys/power/disk
/sys/power/disk 决定:休眠镜像写入磁盘后,系统接下来做什么。 写它只是修改配置,真正触发休眠需要向 /sys/power/state 写入 disk。
你的输出:
[platform] shutdown reboot suspend test_resume
表示支持这五种模式,当前选择的是 platform。
模式 镜像写入磁盘后的动作 主要用途 ━━━━━━━━━━━━━ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ platform 通过平台固件进入休眠状态,ACPI 平台通常是 S4 正常休眠,可使用平台提供的唤醒机制 ───────────── ────────────────────────────────────────────── ──────────────────────────────────────── shutdown 直接执行关机 正常休眠,下次开机时从磁盘镜像恢复 ───────────── ────────────────────────────────────────────── ──────────────────────────────────────── reboot 重启机器 调试休眠及启动后的恢复流程 ───────────── ────────────────────────────────────────────── ──────────────────────────────────────── suspend 进入 mem_sleep 当前选择的挂起模式 混合睡眠:内存保留现场,磁盘也保存一份 ───────────── ────────────────────────────────────────────── ──────────────────────────────────────── test_resume 不关机、不重启,立即读取刚保存的镜像并恢复 测试镜像保存、读取和恢复流程
内核流程
kernel/power/suspend.c
state_store()
pm_suspend() → enter_state()
同步文件系统(sync_on_suspend_enabled)
suspend_prepare()
PM_SUSPEND_PREPARE notifier
filesystems_freeze(filesystem_freeze_enabled)
冻结用户进程、可冻结内核线程
suspend_devices_and_enter()
console_suspend_all()
dpm_suspend_start():prepare → suspend
suspend_enter()
dpm_suspend_late()
dpm_suspend_noirq():配置 wake IRQ,屏蔽普通设备 IRQ,再执行 noirq 回调
├─ s2idle:s2idle_loop()
└─ deep:下线其他 CPU → 关本地中断 → syscore_suspend()
→ suspend_ops->enter() → 从平台睡眠返回
→ syscore_resume() → 开本地中断 → 上线其他 CPU
dpm_resume_noirq() → dpm_resume_early()
dpm_resume_end():resume → complete
console_resume_all()
suspend_finish()
解冻任务 → filesystems_thaw() → PM_POST_SUSPEND notifier
子系统的支持
tty
hibernation 也是会有 tty 哦 https://news.ycombinator.com/item?id=41483789
console 和 Suspend 也是有关系的 printk: Suspending console(s) (use no_console_suspend to debug)
kvm 也是有关系的
kvm_arch_suspend_notifier -> kvm_set_guest_paused
fs
syspend 和文件系统还有关系啊 https://mp.weixin.qq.com/s/pRcEyXmEZ3G1Gzjlur2TJw
而且还和 tty 有关。
virtio
static struct virtio_driver virtio_blk = {
.feature_table = features,
.feature_table_size = ARRAY_SIZE(features),
.feature_table_legacy = features_legacy,
.feature_table_size_legacy = ARRAY_SIZE(features_legacy),
.driver.name = KBUILD_MODNAME,
.driver.owner = THIS_MODULE,
.id_table = id_table,
.probe = virtblk_probe,
.remove = virtblk_remove,
.config_changed = virtblk_config_changed,
#ifdef CONFIG_PM_SLEEP
.freeze = virtblk_freeze,
.restore = virtblk_restore,
#endif
};
- 这里的 virtblk_freeze 是做什么的?
- 直接让 host 将 qemu suspend 暂停和这种 guest os 内部的有区别吗?
更加上层的
- systemctl hibernate
- systemctl suspend
bogon login: [ 6526.000858] PM: suspend entry (deep)
[ 6526.004766] Filesystems sync: 0.003 seconds
[ 6526.016356] Freezing user space processes
[ 6526.017305] Freezing user space processes completed (elapsed 0.000 seconds)
[ 6526.017547] OOM killer disabled.
[ 6526.017660] Freezing remaining freezable tasks
[ 6526.018847] Freezing remaining freezable tasks completed (elapsed 0.001 seconds)
[ 6526.019101] printk: Suspending console(s) (use no_console_suspend to debug)
qmp 命令 : system_wakeup 就可以唤醒了。
arm 的环境中,system_wakeup 无法正常工作,不过无所谓了,到时候看看
(qemu) system_wakeup
Error: wake-up from suspend is not supported by this guest
对应的启动日志观察
acpi_sleep_init
[ 0.179357] ACPI: PM: (supports S0 S3 S4 S5)
INTEL(R) XEON(R) GOLD 6530
[ 5.921766] ACPI: PM: (supports S0 S5)
gnome 为什么设置我的机箱按钮到底是 poweroff 还是 reboot
GNOME 设置 power button 关机/挂起这件事,本质上是三层协作:内核只上报按键事件、systemd-logind 执行策略、GNOME 通过 D-Bus 在运行时改写 logind 的策略。没 有一层是在”内核里改配置”。
- 内核层:只报事件,不决策
drivers/acpi/button.c 里,acpi_button_probe() 把 power button 注册成一个普通 input 设备,能力只有按键:
case ACPI_BUTTON_TYPE_POWER:
input_set_capability(input, EV_KEY, KEY_POWER);
input_set_capability(input, EV_KEY, KEY_WAKEUP);
break;
按下时,ACPI 触发 notify,进 acpi_button_notify(),它只做两件事:
input_report_key(input, keycode, 1);
input_sync(input);
input_report_key(input, keycode, 0);
input_sync(input);
acpi_bus_generate_netlink_event(...); // 同时发一个 uevent 给 udev
注意内核根本没有”power button = 关机还是挂起”这个判断。它只是把 KEY_POWER 扔给 input 子系统(顺带发个 netlink 事件)。关机/挂起的决策完全在用户态。(唯 一例外:长按 4 秒以上由 EC/固件直接硬断电,不经 OS。)
- systemd-logind:真正的决策执行者
logind 打开并抓取这个 power button 输入设备,收到 KEY_POWER 后查 logind.conf 里的配置项:
/etc/systemd/logind.conf
HandlePowerKey=poweroff # 默认就是 poweroff
可选值: poweroff / reboot / halt / kexec / suspend / hibernate / hybrid-sleep / suspend-then-hibernate / lock / ignore。
关键点:logind 把这个配置通过 D-Bus 暴露成运行时可改的接口:
org.freedesktop.login1.Manager.SetHandlePowerKey(string)
(同族还有 SetHandleSuspendKey / SetHandleHibernateKey / SetHandleLidSwitch。)
- GNOME:写 gsettings → 调 D-Bus
用户在图里改的是这个 gsettings 键:
org.gnome.settings-daemon.plugins.power
power-button-action # 取值: nothing / suspend / hibernate / interactive
gnome-settings-daemon 的 power 插件(gsd-power,源码里 logind_set_power_button_action() 这类函数)监听这个键,把字符串映射成 logind 的动作,然后调用 SetHandlePowerKey() 把结果推给 logind。
所以整条链是:
gnome-control-center 下拉框
→ gsettings set org.gnome.settings-daemon.plugins.power power-button-action "suspend"
→ gsd-power 监听到变化
→ D-Bus: logind.SetHandlePowerKey("suspend")
→ logind 运行时覆盖 logind.conf 的默认值
→ 下次 KEY_POWER 到来,logind 执行 suspend
注意两个细节:
- GNOME 是运行时覆盖,它不会去改写 /etc/systemd/logind.conf 文件,只通过 D-Bus 改 logind 内存里的值,session 结束后失效,下次登录重新设置。
- 下拉框里的 “Interactive”(弹对话框问你)走的是另一套机制:gnome-shell 向 logind 注册一个 delay 类型的 inhibitor,logind 要关机前会广播 PrepareForShutdown 信号并等待 inhibitor 释放,gnome-shell 就趁这个空档弹出关机/重启/取消对话框。
内核 = 传感器,logind = 策略引擎(默认关机),GNOME = 用 D-Bus 在 session 内把策略改成”挂起”。
2026-10-19 : 也就是这个按钮被暴露出来,当这个按钮被触发的时候,是用户态的服务 来决定发送什么行为,也就是写入什么内容到 /sy/power/state 中
参考
- https://askubuntu.com/questions/792166/how-does-exactly-suspend-works-in-ubuntu
- https://docs.kernel.org/admin-guide/pm/suspend-flows.html :更加详细
- Documentation/ABI/testing/sysfs-power : 最直接对应:state、mem_sleep、pm_async、wakeup_count、autosleep、挂起统计等接口的语义
- Documentation/admin-guide/pm/sleep-states.rst : 系统睡眠状态,以及通过 state、mem_sleep 选择和触发睡眠的方式
- Documentation/driver-api/pm/notifiers.rst : register_pm_notifier() 等通知链接口与事件
- Documentation/power/basic-pm-debugging.rst : pm_test 等挂起、休眠调试机制
附录
- /sys/power/state 是 2.6 时代的老接口,mem 当年就指”挂起到内存”(S3)。工具链、systemd 都写 mem。
- 后来引入了更浅的 suspend-to-idle 和 standby,但不能改变 mem 的既有语义(否则老脚本 echo mem 行为就变了)。
- 于是新增 /sys/power/mem_sleep 文件,专门用来选择 mem 具体落到哪一档。新名字 s2idle/shallow/deep 是更精确、更”现代”的命名。
这些说法在内核源码和 git 历史里都有直接证据:
- /sys/power/state 确实古老:
state_attr首次出现于1da177e4c3f4 Linux-2.6.12-rc2(内核初始 import,2.6 时代)。 - “mem 语义不能改”有两条 commit 佐证,都出自 PM 维护者 Rafael J. Wysocki:
0399d4db3edf(2014):已经发现有些平台只支持 freeze,但用户态框架只认 mem/standby 标签、没法改用户态代码,于是先打了个补丁式 workaround —— 加relative_sleep_states内核参数去重新解释 mem 标签。406e79385f32(2016,”System sleep state selection interface rework”):提交说明原文 “but preserve backwards compatibiliby”,新增 /sys/power/mem_sleep,”in analogy with what /sys/power/disk does for hibernation”。
- 代码层面,kernel/power/suspend.c 里并存两套标签数组:
pm_labels[]= freeze/standby/mem(老的 state 接口名字)mem_sleep_labels[]= s2idle/shallow/deep(新的 mem_sleep 名字)
- 替换逻辑在 kernel/power/main.c 的
state_store():写 “mem” 时if (state == PM_SUSPEND_MEM) state = mem_sleep_current;,即 mem 被重新翻译成 mem_sleep 当前档位后再执行。 pm_states_init()的注释/* "mem" and "freeze" are always present in /sys/power/state. */说明这两个老名字被强制保留。- 默认 deep:
suspend_set_ops()里valid_state(PM_SUSPEND_MEM)时mem_sleep_current = PM_SUSPEND_MEM。 - 另:/sys/power/disk 之于 hibernation,与 /sys/power/mem_sleep 之于 suspend,是同一套设计模式(state 是触发入口,另一个文件是配置解释器),commit 说明里已点明这个类比。
本站所有文章转发 CSDN 将按侵权追究法律责任,其它情况随意。