结论
NixOS 的「睡眠」与 Windows 用户习惯的「睡眠」名同实异。本机走的是 s2idle(Suspend-to-Idle):CPU 停止调度、用户态整体冻结,但平台不整体断电。系统用两条钩子链把睡眠态与唤醒态变得可控、可审计:睡眠前主动断开外设与网络,唤醒后按序恢复。「合盖后热点里还显示连着」是 AP(路由器/手机)侧的客户端列表刷新滞后,不是本机还在联网 —— 日志能证明睡眠前本机主动发过解除关联帧。
睡眠态:系统做了什么
睡眠模式
内核只暴露一种挂起方式:s2idle(/sys/power/mem_sleep 仅此一项)。它与「深睡(deep/S3)」的区别在于平台不断电 —— 这正是需要钩子主动断外设的原因:若什么都不做,网卡是会在睡眠期间潜活的。
睡眠触发源(只有三种,其余刻意排除)
- 合盖
- 快捷键(组合键)
- 开始菜单的「睡眠」
电源键、闲置倒计时不触发睡眠(HandlePowerKey 刻意不设,IdleAction=ignore)。合盖策略分档:外接电源与「已接坞站」都睡眠,只有「外接电源 + 合盖」这一种组合走抑制锁逻辑,电池档与低电档交给桌面环境自己的电源管理界面。
睡眠前的钩子链
各声明式模块写的 powerManagement.powerDownCommands 会被 NixOS 合并成同一个单元 sleep-actions.service 的 ExecStart。当前实际顺序:
| 顺序 | 动作 | 作用 |
|---|---|---|
| 1 | awake --snapshot | 落盘本次睡眠前的唤醒源快照,供唤醒后比对 |
| 2 | 无线网卡卸载脚本 | 链路 down(主动发解除关联)→ 按反依赖序卸载无线驱动模块 |
睡眠期间的设备状态
- 用户态整体冻结:systemd 冻结
user.slice,任何应用都无法运行、无法联网。 - 无线网卡不工作:驱动已被卸载,链路已 down ⇒ 物理上不通。这样刻意设计是为了绕开该网卡驱动长期存在的固件崩溃问题(睡眠前卸载可避免唤醒时固件状态损坏)。
- 外接显示熄灭:DPMS 关断,唤醒侧另有一轮点亮补偿。
- 网络唤醒未启用:网卡唤醒位处于禁用状态,因此睡眠期间也不存在「收特定帧唤醒」的通道。
唤醒态:系统做了什么
唤醒源(只有四类)
- 开盖
- 内置键盘按键
- USB 键盘
- USB 鼠标
电源键、定时任务(RTC/alarmtimer)、蓝牙适配器、指纹、雷雳热插拔、电源适配器拔插全部禁用,不做唤醒源。
唤醒后的钩子链
同为 sleep-actions.service,但挂在 ExecStop(单元退出时执行)。当前实际顺序:
| 顺序 | 动作 | 作用 |
|---|---|---|
| 1 | awake --record-wake | 记录本次唤醒来自哪个唤醒源,并回写清单供比对 |
| 2 | 显示补偿脚本 | 对 DPMS 做一次「关→开」循环,把外接屏唤醒 |
| 3 | 无线恢复脚本 | 重新装载驱动模块 → 等网口出现 → 等网络管理器自动重连(实测 6–8 秒)→ 超时才显式激活 |
| 4 | 核显色深脚本 | 等待 30 秒后重放 MST 菊花链的色深约束(看得见的多屏链路修复) |
睡眠源 / 唤醒源的声明式管理
这套东西不是散落在 udev 规则里的临时配置,而是有单一数据源的三件套:
- 清单文件(每台机器一份,文件名以本机 ESP 的 UUID 派生,因此克隆机之间天然隔离):每行一个设备,登记「设备路径 / 是否启用 / 人类可读说明」,例如「合盖开关:启用」「电源键:禁用」「USB 键鼠:启用」「蓝牙适配器:禁用」。
- 管理命令
awake:list打印运行时清单(含「清单里有但运行时不在位」的条目)、on/off改运行时唤醒位、sync才把改动写回清单文件。声明式配置与命令读的是同一份文件,没有第二份副本。 - udev 规则做黑名单式收口:USB 子系统默认全部禁用唤醒,只有 HID 类(键盘/鼠标)才重新启用;电池事件、电源键、睡眠键、RTC 闹钟、计时器、雷雳、触摸板、电源适配器一律禁用。
此外有一条按电源状态联动的规则:交流电接入/拔出时,切换对应的合盖抑制锁单元 —— 这就是「只做插电模式」那条策略的落点。
实测证据
| 判据 | 实测 |
|---|---|
| 挂起方式 | s2idle(唯一可选项) |
| 睡眠前断开网络 | 日志出现「解除关联帧(by local choice)」+「链路 down」+ 两个驱动模块先后卸载 |
| 用户态冻结 | 日志出现「Successfully froze unit 'user.slice'」 |
| 钩子确实执行 | 每次睡眠都在日志里成对出现睡眠侧与唤醒侧的动作行 |
| 唤醒后重连耗时 | 网口 1 秒内出现;网络管理器 6–8 秒内自动重连 |
| 钩子单元归属 | systemctl cat sleep-actions.service:ExecStart=睡眠前脚本,ExecStop=唤醒后脚本 |
为什么 Windows 用户会焦虑
| 场景 | Windows 的习惯 | 本机(NixOS)的行为 |
|---|---|---|
| 睡眠期间继续下载 | 「现代待机」机型常能保持联网继续下载 | 完全不能:用户态冻结 + 驱动卸载 |
| 睡眠期间收消息 | 部分机型能收(网络保活) | 不能:同上;消息在唤醒后一次性补收 |
| 唤醒速度 | 即时 | 即时(s2idle 不落盘,唤醒很快) |
| 唤醒后网络 | 通常已在 | 6–8 秒重连(驱动重载 + 扫描 + 关联) |
| 合盖后是否真睡 | 不一定(可能被外设或策略阻止) | 一定睡(合盖 = suspend,无例外) |
| 被网络唤醒 | 常见(网卡默认允许) | 不允许(唤醒位禁用,且未启用无线网络唤醒) |
结论:焦虑的点其实是两件事 —— ①「睡眠还在联网」在本机不成立;②「睡眠期间不能保持联网」是本机刻意的设计,代价换来的是唤醒可靠(规避网卡固件崩溃)。两者都可改,改动方式见下节。
可配置性
| 想改什么 | 改哪里 | 代价 / 风险 |
|---|---|---|
| 睡眠期间保持无线连接 | 注释掉无线模块里挂到 powerDownCommands/resumeCommands 的两行 | 不推荐:该网卡驱动的固件崩溃问题可能让唤醒失败;且保活还依赖 AP 不把客户端踢下线 |
| 允许被网络唤醒(无线) | 启用网卡唤醒位 + 打开无线网络唤醒(magic packet) | 只影响「能否被唤醒」,不等于保持连接;且会牺牲当前「睡眠零网络活动」的性质 |
| 调整可用的唤醒源 | awake on/off <条目> 改运行时;awake sync 写回清单 | 无 —— 这是设计内的操作方式 |
| 改变睡眠触发方式(如电源键也睡) | 在 logind 声明式设置里放开对应项 | 会让合盖/快捷键之外的通路也能入睡,与当前「只有三种触发源」的约定冲突 |
| 什么都不改(当前策略) | — | 睡眠零网络活动、唤醒可靠;代价是睡眠期间无法联网/收消息 |
查询命令(运行时清单)
awake list # 睡眠源/唤醒源运行时清单(含「清单里有但设备不在位」)
cat /sys/power/mem_sleep # 挂起方式(本机只有 s2idle)
systemctl cat sleep-actions.service # 睡眠钩子的入口(ExecStart/ExecStop)
systemctl list-dependencies sleep.target # 睡眠目标依赖了谁
journalctl -u sleep-actions -u systemd-suspend -b # 本次开机实际执行了什么
journalctl -b -2 | grep -E 'PM: suspend (entry|exit)' # 历史睡眠次数与挂起方式
cat /proc/acpi/wakeup # ACPI 侧唤醒位一览(与 awake list 互为印证)边界
- 本文只梳理睡眠态/唤醒态的系统管理机制,不含具体硬件的排障(外接屏、eGPU 等另有专档)。
- 「AP 侧客户端列表刷新滞后」的具体超时取决于对端设备,未取证(无对端日志);本机侧「睡眠前主动断开」是日志确证的。
- 无线网卡对无线网络唤醒(WoWLAN)的支持情况未复核;可确定的是其唤醒位处于禁用状态。
- 本次为只读梳理,未改动任何配置、未触发任何睡眠/唤醒动作。