状态机心智模型

任何 sleep / lock / logout / hibernate 操作, 本质都是在调 4 个开关的不同组合; 判断"这个操作干了什么"只要看这 4 件事:

  1. 用户进程还在不在? (Chrome, IDE, SSH 连接是否还活着);
  2. CPU 还在不在执行指令?;
  3. RAM 还通不通电?;
  4. 磁盘上有没有内存镜像? (用来还原的);

对照表 (从轻到重)

操作 用户进程 CPU RAM 磁盘镜像 唤醒/恢复 功耗 典型场景
熄屏 (DPMS) 通电 瞬间 (动鼠标) 省 ~10W (只关显示器) 短暂离开几分钟
锁屏 (lock) 通电 输密码 同平时 离开座位防别人乱碰
Suspend (S3 / s2idle) 冻结 自刷新通电 1-3s 电池 1-3%/小时 合盖, 临时离开
Hibernate (S4) 冻结 断电 整块写到 swap 10-30s 0 过夜, 长时间不用
Hybrid sleep 冻结 通电 同时写盘 RAM 没掉就秒开, 掉了从盘恢复 1-3%/小时 + 一次写入 台式机怕断电
Logout 全部杀掉 通电 重新登录, 所有 app 从头开 同平时 换用户用机器
Shutdown (S5) 全杀 断电 完整冷启动 30s+ 0 不用了 / 要搬动

容易混的几对

Suspend vs Hibernate — 同是"睡眠", 但断电后果完全不同:

1
2
3
4
Suspend  := "暂停按钮", RAM 保持通电, 状态在内存里;
断电就完蛋, 但秒级唤醒;
Hibernate := "存档退出", RAM 写到磁盘, 真的关机;
断电不丢, 但唤醒要重新装回内存, 慢;

锁屏 vs Suspend — 完全是两层东西:

  • 锁屏: 纯 UI, 进程没停, 只是盖一层"输密码才能交互"的窗; 编译, 下载, 视频会议照常跑;
  • Suspend: 物理层面让 CPU 停了; 所有进程冻结, 编译暂停, SSH 断线, 视频会议掉;

所以远程开会时锁屏没事 (会议还在跑); 合盖让它 suspend — 会议直接掉;

Logout vs Shutdown:

  • Logout: 杀掉你这个用户的所有进程, 但系统服务, 内核, 其他用户的 session 全在; Linux 服务器你 SSH 退出就是 logout — 服务器还在跑别的;
  • Shutdown: 杀光所有进程, 内核关, 硬件断电;

合上盖子 = 做了什么?

取决于配置, 不是固定动作; Linux 看 /etc/systemd/logind.confHandleLidSwitch; 绝大多数发行版的默认是:

1
合盖 → suspend (S3 或 s2idle)

也就是上表第三行: 进程冻结, CPU 停, RAM 通电, 1-3 秒能唤醒;

Linux 上可改成:

  • ignore — 啥也不做 (合盖照跑, 配 dock 当桌面机);
  • lock — 只锁屏, 进程继续跑;
  • suspend-then-hibernate — 先 suspend, N 分钟没开转 hibernate (推荐, 兼顾速度和过夜安全);
  • poweroff — 真关机 (没人这么用);

[!macOS 合盖逻辑的区别]

macOS 没有 Linux 那么多 lid action 选项可调, 但有一个自动行为: 接外接显示器 + 电源 + 鼠标/键盘 三件套齐全时, 合盖会自动进入 clamshell mode (等同 Linux 的 ignore), 笔记本继续跑, 屏幕只走外接; 三个条件少一个就 suspend; 想"没外接键鼠也合盖继续跑"得装第三方 (NoSleep, Amphetamine), 或用 caffeinate -i 阻止 idle sleep — 但合盖动作本身没法直接关掉;

macOS 的 hibernatemode 数值跟 Linux 的 “suspend / hibernate / hybrid” 概念不是一一对应, 是混合策略的预设:

  • 0 = 纯 suspend (老 Mac 默认, 类似 Linux suspend);
  • 3 = safe sleep (类似 hybrid: RAM 通电 + 写盘备份, 现在笔记本默认);
  • 25 = pure hibernate (RAM 断电, 类似 Linux S4, 移动设备防丢数据用);
1
2
pmset -g | grep hibernatemode        # 看当前
sudo pmset -a hibernatemode 3 # 改全场景

电源栈分层

1
2
3
4
5
6
7
8
9
用户态 GUI/CLI: systemctl, loginctl, swayidle, pmset, caffeinate

系统服务: systemd-logind (lid/key), systemd-sleep (hooks)

内核 sysfs: /sys/power/state, /sys/power/disk, /proc/acpi/wakeup

固件 ACPI: DSDT/FADT, _S3/_S4 method, UEFI 配置

硬件: CPU C-states, 网卡 WoL, RTC, LID GPIO

debug 顺序: 从下往上排除;

ACPI Sleep States

状态 名称 行为 唤醒延迟 功耗
S0 Working 正常运行 0 100%
S0ix Modern Standby (s2idle) 软件 deep idle, 部分组件保电 < 100ms ~5%
S3 Suspend-to-RAM 仅 RAM self-refresh 1-3s 1-3%
S4 Suspend-to-Disk (Hibernate) 内存 dump 到 swap, 全断电 10-30s 0%
S5 Soft Off 关机, 但可 WoL 启动时间 ~0%
1
2
3
cat /sys/power/state           # freeze mem disk  ← 本机支持的
cat /sys/power/mem_sleep # [s2idle] deep ← 当前选择
echo deep | sudo tee /sys/power/mem_sleep # 切到真正的 S3

现代笔记本 (Surface, 部分 Dell/Lenovo) 已不支持 S3, 只能 s2idle; Linux 续航差的最大锅, 因为 s2idle 严重依赖驱动+固件配合; 排查用 turbostat, 关注 Pkg%pc10 (要 > 90%) 和 PkgWatt (要 < 2W);

sysfs 接口

1
2
3
4
5
6
7
/sys/power/state            # echo mem|disk|freeze 触发
/sys/power/disk # platform|shutdown|reboot, hibernate 模式
/sys/power/mem_sleep # s2idle vs deep
/sys/power/wakeup_count # race-free 触发用
/sys/power/pm_test # 调试: 模拟不真睡
/proc/acpi/wakeup # 可唤醒设备列表, echo 设备名 toggle
/sys/class/power_supply/BAT0/{capacity,status}

[!race-free suspend 协议]

直接 echo mem > state 有竞态 — 瞬间来个 wakeup 就立刻醒; 正确做法:

  1. wakeup_count 拿值 N;
  2. 写 N 回 wakeup_count (失败说明有新事件, 放弃);
  3. 成功才写 memstate;

systemd-logind 内部就这么做; 手写脚本务必照搬;

触发睡眠的三种姿势

1
2
3
4
5
6
7
8
9
10
11
12
# 推荐: 走完整 systemd hook
systemctl suspend
systemctl hibernate
systemctl hybrid-sleep # 写盘 + S3 (RAM 没掉就秒开, 掉了从盘恢复)
systemctl suspend-then-hibernate # 先 suspend, N 分钟后转 hibernate

# session 层语义
loginctl lock-session
loginctl suspend

# 调试用 (不走任何 hook)
sudo sh -c 'echo mem > /sys/power/state'

策略文件 /etc/systemd/sleep.conf:

1
2
3
4
[Sleep]
HibernateDelaySec=120min # suspend-then-hibernate 转 hibernate 时间
SuspendState=mem
HibernateMode=platform shutdown

Hibernate 配置三要件

  1. Swap 够大swap >= 物理内存 (用 zswap 可放宽到 0.5x);

  2. 内核命令行有 resume=/etc/default/grub 加:

    1
    GRUB_CMDLINE_LINUX="... resume=UUID=<swap-uuid> [resume_offset=<offset>]"

    分区用 blkid 取 UUID; swapfile 还要 filefrag -v /swapfile 拿 offset;

  3. initramfs 有 resume hook:

    1
    2
    sudo update-initramfs -u            # Debian/Ubuntu
    sudo mkinitcpio -P # Arch (HOOKS 加 resume)

zswap (压缩 swap, 推荐配合 hibernate): 内核参数 zswap.enabled=1 zswap.compressor=lz4 zswap.max_pool_percent=20;
zram (内存当 swap, 不能 hibernate): 缓解内存压力用;

合盖 / 锁屏 / 熄屏

Linux: systemd-logind 配 /etc/systemd/logind.conf

1
2
3
4
5
6
7
8
[Login]
HandleLidSwitch=suspend-then-hibernate # 拔电
HandleLidSwitchExternalPower=suspend # 插电
HandleLidSwitchDocked=ignore # 外接显示器/dock
HandlePowerKey=suspend
HandleLockKey=lock
IdleAction=lock
IdleActionSec=10min

改完: sudo systemctl restart systemd-logind (会断 GUI session, 在 TTY 做);

熄屏 (Display)

1
2
3
4
5
6
7
8
9
10
11
12
# X11: DPMS
xset dpms force off # 立即熄屏
xset dpms 600 900 1200 # standby/suspend/off 超时秒数

# Wayland: 各家合成器
swaymsg "output * power off" # wlroots
hyprctl dispatch dpms off # Hyprland
# GNOME/KDE 用各自 settings

# Darwin
pmset displaysleepnow
sudo pmset -a displaysleep 10 # 10 分钟

锁屏 (Lock Session)

锁屏是 user session 层概念, 跟硬件无关:

1
2
3
4
5
6
7
8
9
10
11
12
# Linux: DBus → screen locker (i3lock/swaylock/...)
loginctl lock-session

# 自动跟随 sleep: xss-lock (X11) 或 swayidle (Wayland)
swayidle -w \
timeout 300 'swaylock -f' \
timeout 600 'swaymsg "output * power off"' \
before-sleep 'swaylock -f'

# Darwin
defaults write com.apple.screensaver askForPassword -int 1
defaults write com.apple.screensaver askForPasswordDelay -int 0

Inhibitor (防睡眠)

1
2
3
4
5
6
7
8
9
# Linux: 看谁阻止睡眠
systemd-inhibit --list

# 自己声明: rsync 跑完自动释放
systemd-inhibit --what=sleep:idle --why="backup" rsync -a /src /dst

# Darwin
caffeinate -dis -t 3600 # 1 小时防 display+idle+system sleep
caffeinate -i make all # 跟随子进程

防"合盖立刻又醒"

LID GPIO 抖动会让合盖瞬间又收到 open 事件:

1
echo LID0 | sudo tee /proc/acpi/wakeup   # toggle 禁用

远程唤醒

Wake-on-LAN 原理

Magic Packet = [FF×6][目标 MAC × 16], 共 102 字节, 通常 UDP/9 广播; 网卡 PHY 在低功耗下监听, pattern match 后拉 PME# 唤醒主板;

约束:

  • 必须知道目标 MAC (IP 在睡眠时已下线);
  • 默认只能同 broadcast domain (二层);
  • 网卡 + 主板 + BIOS 三处都要开 (“Wake on LAN” / “Power on by PCIe”, 关 “Deep Sleep”/“ErP”);

Linux 配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# 检查支持
sudo ethtool eth0 | grep -i wake
# Supports Wake-on: pumbg ← g = MagicPacket
# Wake-on: g ← 已启用

# 临时启用
sudo ethtool -s eth0 wol g

# 持久化 (任选一种)
# 1. NetworkManager
nmcli con modify "Wired connection 1" 802-3-ethernet.wake-on-lan magic

# 2. systemd service
cat > /etc/systemd/system/wol@.service <<'EOF'
[Unit]
After=network.target
[Service]
Type=oneshot
ExecStart=/usr/sbin/ethtool -s %i wol g
[Install]
WantedBy=multi-user.target
EOF
sudo systemctl enable wol@eth0.service

发送 magic packet

1
2
3
4
5
6
wakeonlan aa:bb:cc:dd:ee:ff                      # 默认 255.255.255.255
wakeonlan -i 192.168.1.255 aa:bb:cc:dd:ee:ff # 指定子网广播
sudo etherwake aa:bb:cc:dd:ee:ff # raw L2

# macOS 发送: brew install wakeonlan
# macOS 接收: sudo pmset -a womp 1

WoL over WAN

广播默认不跨路由器, 三种突破:

  1. Directed broadcast — 路由器 DNAT WAN 进来的包到 192.168.1.255 (Smurf 攻击面, 务必限源 IP);
  2. 跳板机 — 树莓派/NAS 常开, ssh pi@home 'wakeonlan MAC', 最干净;
  3. VPN (WireGuard/Tailscale) — 接入 LAN 当本地子网; Tailscale 有原生 “Wake-on-LAN over Tailnet” 文档;

IPMI / BMC — 服务器带外管理

📝 机房标配

WoL 只能在主板有电时听网卡; IPMI 用独立 BMC 微控制器, 跟主电源完全独立 — 主机断电/内核 panic/BIOS 卡死都能控制; Dell 叫 iDRAC, HP 叫 iLO, Supermicro 叫 IPMI;

1
2
3
4
5
6
7
8
export IPMI="-H 10.0.0.100 -U admin -P pw -I lanplus"

ipmitool $IPMI power status # / on / off / soft / cycle
ipmitool $IPMI sol activate # Serial Over LAN, Ctrl+. 退出
ipmitool $IPMI sdr # 传感器 (温度/风扇)

# Redfish (现代 REST API 替代)
curl -k -u admin:pw https://10.0.0.100/redfish/v1/Systems/1 | jq '.PowerState'

安全: BMC 必须放管理网, 改默认密码 (ADMIN/ADMIN), 关 cipher 0; 永远别暴露公网, 用 VPN/跳板;

RTC Wake (定时自醒)

1
2
3
rtcwake -m mem -s 600                            # suspend 600 秒
rtcwake -m disk -t $(date -d 'tomorrow 06:00' +%s)
rtcwake -m no -t $(date -d 'tomorrow 03:00' +%s) # 只设 alarm 不睡

Debug Cheat Sheet

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 上次睡眠为何醒
journalctl -b 0 | grep -iE 'PM:|suspend|resume|wakeup'

# 谁在阻止睡眠
systemd-inhibit --list

# 哪个驱动卡住 suspend (test 模式)
echo devices | sudo tee /sys/power/pm_test
echo mem | sudo tee /sys/power/state
dmesg | grep -i 'PM: late suspend'

# 哪个设备乱唤醒
cat /sys/class/wakeup/*/event_count

# Idle 多久 (X11)
xprintidle

常见症状对应

症状 怀疑方向
合盖立刻醒 LID GPIO 抖动 — 关 LID wakeup
s2idle 续航差 检查 turbostat Pkg%pc10 / PkgWatt, 多半 USB/Wi-Fi/Nvidia 没正确 runtime-suspend
Hibernate 卡在 “Saving image” swap 太慢 (HDD) 或损坏
Resume 后时钟跳几小时 RTC 时区 — timedatectl set-local-rtc 0
Resume 后 GPU 黑屏 (Nvidia) nvidia.NVreg_PreserveVideoMemoryAllocations=1
wol g 重启失效 没持久化, 改用 systemd/NetworkManager/udev
S3 能唤 S5 不能 BIOS “Wake from S5” / 关 ErP/Deep Sleep
Wi-Fi 不响应 WoL 大多数 Wi-Fi 不支持 (WoWLAN 另一回事), iw phy phy0 wowlan
IPMI 连不上 BMC 网络隔离, 默认密码, cipher 0
WoL over WAN 不通 路由器没开 directed broadcast / ARP 老化 (路由器加静态 ARP)