Unix 电源管理
状态机心智模型
任何 sleep / lock / logout / hibernate 操作, 本质都是在调 4 个开关的不同组合; 判断"这个操作干了什么"只要看这 4 件事:
- 用户进程还在不在? (Chrome, IDE, SSH 连接是否还活着);
- CPU 还在不在执行指令?;
- RAM 还通不通电?;
- 磁盘上有没有内存镜像? (用来还原的);
对照表 (从轻到重)
| 操作 | 用户进程 | 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 | Suspend := "暂停按钮", RAM 保持通电, 状态在内存里; |
锁屏 vs Suspend — 完全是两层东西:
- 锁屏: 纯 UI, 进程没停, 只是盖一层"输密码才能交互"的窗; 编译, 下载, 视频会议照常跑;
- Suspend: 物理层面让 CPU 停了; 所有进程冻结, 编译暂停, SSH 断线, 视频会议掉;
所以远程开会时锁屏没事 (会议还在跑); 合盖让它 suspend — 会议直接掉;
Logout vs Shutdown:
- Logout: 杀掉你这个用户的所有进程, 但系统服务, 内核, 其他用户的 session 全在; Linux 服务器你 SSH 退出就是 logout — 服务器还在跑别的;
- Shutdown: 杀光所有进程, 内核关, 硬件断电;
合上盖子 = 做了什么?
取决于配置, 不是固定动作; Linux 看 /etc/systemd/logind.conf 的 HandleLidSwitch; 绝大多数发行版的默认是:
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 | 用户态 GUI/CLI: systemctl, loginctl, swayidle, pmset, caffeinate |
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 | cat /sys/power/state # freeze mem disk ← 本机支持的 |
现代笔记本 (Surface, 部分 Dell/Lenovo) 已不支持 S3, 只能 s2idle; Linux 续航差的最大锅, 因为 s2idle 严重依赖驱动+固件配合; 排查用 turbostat, 关注 Pkg%pc10 (要 > 90%) 和 PkgWatt (要 < 2W);
sysfs 接口
1 | /sys/power/state # echo mem|disk|freeze 触发 |
[!race-free suspend 协议]
直接
echo mem > state有竞态 — 瞬间来个 wakeup 就立刻醒; 正确做法:
- 读
wakeup_count拿值 N;- 写 N 回
wakeup_count(失败说明有新事件, 放弃);- 成功才写
mem到state;systemd-logind 内部就这么做; 手写脚本务必照搬;
触发睡眠的三种姿势
1 | # 推荐: 走完整 systemd hook |
策略文件 /etc/systemd/sleep.conf:
1 | [Sleep] |
Hibernate 配置三要件
-
Swap 够大 —
swap >= 物理内存(用 zswap 可放宽到 0.5x); -
内核命令行有 resume= —
/etc/default/grub加:1
GRUB_CMDLINE_LINUX="... resume=UUID=<swap-uuid> [resume_offset=<offset>]"
分区用
blkid取 UUID; swapfile 还要filefrag -v /swapfile拿 offset; -
initramfs 有 resume hook:
1
2sudo 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 | [Login] |
改完: sudo systemctl restart systemd-logind (会断 GUI session, 在 TTY 做);
熄屏 (Display)
1 | # X11: DPMS |
锁屏 (Lock Session)
锁屏是 user session 层概念, 跟硬件无关:
1 | # Linux: DBus → screen locker (i3lock/swaylock/...) |
Inhibitor (防睡眠)
1 | # Linux: 看谁阻止睡眠 |
防"合盖立刻又醒"
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 | # 检查支持 |
发送 magic packet
1 | wakeonlan aa:bb:cc:dd:ee:ff # 默认 255.255.255.255 |
WoL over WAN
广播默认不跨路由器, 三种突破:
- Directed broadcast — 路由器 DNAT WAN 进来的包到
192.168.1.255(Smurf 攻击面, 务必限源 IP); - 跳板机 — 树莓派/NAS 常开,
ssh pi@home 'wakeonlan MAC', 最干净; - VPN (WireGuard/Tailscale) — 接入 LAN 当本地子网; Tailscale 有原生 “Wake-on-LAN over Tailnet” 文档;
IPMI / BMC — 服务器带外管理
WoL 只能在主板有电时听网卡; IPMI 用独立 BMC 微控制器, 跟主电源完全独立 — 主机断电/内核 panic/BIOS 卡死都能控制; Dell 叫 iDRAC, HP 叫 iLO, Supermicro 叫 IPMI;
1 | export IPMI="-H 10.0.0.100 -U admin -P pw -I lanplus" |
安全: BMC 必须放管理网, 改默认密码 (ADMIN/ADMIN), 关 cipher 0; 永远别暴露公网, 用 VPN/跳板;
RTC Wake (定时自醒)
1 | rtcwake -m mem -s 600 # suspend 600 秒 |
Debug Cheat Sheet
1 | # 上次睡眠为何醒 |
常见症状对应
| 症状 | 怀疑方向 |
|---|---|
| 合盖立刻醒 | 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) |
