Skip to the content.

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

接口的含义

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",
};

/sys/power/mem_sleep 和 /sys/power/disk 都是子模式的扩展

   mem_sleep 是"mem 这个令牌的解释器"     state 是"要睡哪个状态"
   ─────────────────────────────────     ─────────────────────────
   echo deep  > /sys/power/mem_sleep       echo mem > /sys/power/state

实操上的对应关系

都是直接指向的结果:

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
};

更加上层的

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 的策略。没 有一层是在”内核里改配置”。

  1. 内核层:只报事件,不决策

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。)

  1. 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。)

  1. 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

注意两个细节:

内核 = 传感器,logind = 策略引擎(默认关机),GNOME = 用 D-Bus 在 session 内把策略改成”挂起”。

2026-10-19 : 也就是这个按钮被暴露出来,当这个按钮被触发的时候,是用户态的服务 来决定发送什么行为,也就是写入什么内容到 /sy/power/state 中

参考

附录

  1. /sys/power/state 是 2.6 时代的老接口,mem 当年就指”挂起到内存”(S3)。工具链、systemd 都写 mem。
  2. 后来引入了更浅的 suspend-to-idle 和 standby,但不能改变 mem 的既有语义(否则老脚本 echo mem 行为就变了)。
  3. 于是新增 /sys/power/mem_sleep 文件,专门用来选择 mem 具体落到哪一档。新名字 s2idle/shallow/deep 是更精确、更”现代”的命名。

这些说法在内核源码和 git 历史里都有直接证据:

本站所有文章转发 CSDN 将按侵权追究法律责任,其它情况随意。