s2idle卡死与休眠

发布于

事情起因也很简单。

昨天下班没关机器,合上笔记本盖子,外接屏还停在登录界面。今天早上看见整机卡死,强制关机前屏幕上还挂着 Chrome 和系统通知。前天也冒过系统通知,于是第一反应是:

是不是通知把系统弄挂了?

查完 journalctl 之后,答案很明确:不是。

卡死长什么样

上一启动周期里,大约 05:12 起,内核开始报 soft lockup:

网卡是 Motorcomm YT8531S(驱动 dwmac_motorcomm),口子叫 enp2s0

这是典型的 中断风暴:MAC 状态位清不干净,中断一直来,某个 CPU 被硬中断打满,界面当然没反应。Chrome / 桌面通知只是卡死时还留在锁屏上的东西,不是诱因。

日志里反复出现的:

ucsi_acpi USBC000:00: failed to re-enable notifications (-110)

也不是桌面通知,而是 USB-C 控制器在 suspend/resume 边界超时。容易望文生义,但和弹窗无关。

真正的上游:整晚在浅睡里空转

合盖后系统走的是:

PM: suspend entry (s2idle)

这台机 /sys/power/mem_sleep 只有 [s2idle],没有传统 S3 deep
s2idle 是浅睡:CPU 进深 C-state,但中断子系统还醒着,USB / Type-C / 外接显示很容易当「门铃」。

更扎眼的是节奏:

  1. 睡着大约 十几秒 就被叫醒
  2. 锁屏上空闲约 15 分钟 又自动挂起
  3. 整夜循环几十次

15 分钟从哪来?GNOME 电源设置:

sleep-inactive-ac-timeout = 900
sleep-inactive-ac-type = suspend

所以「反复唤醒」其实是两段机制拼起来的:

有线网卡更像是 反复 suspend/resume 之后的受害者,不是当晚的门铃。某一次循环里 MAC 中断死循环,机器就彻底卡死了。

当晚插着什么

合盖时外接大致是:

其中全功能 Type-C 屏嫌疑最大;两个接收器和键盘也都是合法 USB 唤醒源。闲置接收器等于多挂一个门铃,平时就该拔掉。

为什么显示器 Type-C 特别容易反复叫醒

全功能 Type-C 接显示器,往往不只是「多一个 USB 设备」,而是一条线上叠了好几套协议:

  1. DP Alt Mode:视频信号。面板电源、链路训练、热插拔检测(HPD)在休眠边界上很容易抖一下,内核会当成「显示状态变了」。
  2. USB / USB BillBoard / Hub:很多显示器 Type-C 口背后还有内部 hub,会枚举键鼠转发、摄像头、上游充电协商等。
  3. UCSI / PD 供电协商:插着就会谈电压电流。本机日志每轮 resume 后都有
    ucsi_acpi ... failed to re-enable notifications (-110)(超时),说明 Type-C 控制器在 suspend/resume 时状态不干净,随后更容易再打中断。
  4. 和 s2idle 合在一起更糟:浅睡不断电到「门铃全关」,控制器和线缆还活着,上述任一事件都能把 CPU 从 idle 里拉起来。传统深睡 S3 会把更多设备真下电,外接屏没那么容易每隔十几秒喊人。

所以不是「Type-C 显示器有毒」,而是:一根线绑了显示 + USB + 供电,事件面太宽,又正好撞上只能 s2idle 的平台。合盖后外接屏还亮着登录界面,等于整晚把这条嘈杂链路留在唤醒路径上。

挂起和休眠不是一回事

排查过程中,概念也值得写清楚:

说法 实际 状态在哪 能否真正断电
挂起 / 睡眠 suspend(此处多为 s2idle) 内存 否,内存仍供电
休眠 hibernate 磁盘 swap 可以

挂起 ≈ 休眠到内存:醒得快,合盖过夜仍耗电,也容易被外设叫醒。
休眠 ≈ 休眠到硬盘:更省电,依赖够大的磁盘 swap。

还有一种听起来很香的折中:suspend-then-hibernate(先挂起,挂够一段时间再自动休眠)。
在这台「合盖 + DP/USB-C 外接屏」的机器上,它不可靠

原因很直接:这条路径的前提是先能稳稳挂着。日志里 s2idle 往往十几秒就被叫醒;门铃还在响,后面的「长时间后再写盘休眠」根本走不到。结果不是「挂久了会休眠」,而是永远进不了休眠,只剩被 DP 反复吵醒的浅睡循环。

GNOME 50 的「设置 → 电源」里,也基本看不到「空闲后休眠」——界面多半只有自动挂起,以及电源键选挂起/休眠。底层键其实在:

org.gnome.settings-daemon.plugins.power sleep-inactive-ac-type
org.gnome.settings-daemon.plugins.power sleep-inactive-battery-type

可用 gsettings 或 Dconf Editor 设成 hibernate / nothing,但不要指望外接 DP 场景下用「先挂起再等休眠」解决问题。外接屏过夜更稳妥的是:直接休眠、关机,或拔掉显示后再合盖

Fedora 默认只有 zram(内存里的压缩交换区)。关机 zram 就没了,不能拿来真正断电休眠。

想真正休眠,先搞清 swap 在哪

本机当时大致是:

btrfs 根分区占满了 Linux 那块盘,末尾没有空闲扇区;要切真正的 swap 分区就得缩容,等于「高速上换胎」,更稳妥是 Live USB 离线做。

更省事、也符合 Fedora 文档方向的,是在现有盘上建 swap 文件
恢复时内核也不是「先挂载文件系统再 open() 文件」,而是用块设备 + resume_offset(或 UEFI 里的 HibernateLocation)按物理偏移读镜像。btrfs 因为 CoW,要用 btrfs filesystem mkswapfile 这类专用路径,关掉写时复制,位置才稳定。

第一版 DIY,和官方路径的差距

一开始按「手写 resume」做了一版:

systemctl hibernate 先撞上 Access denied。不是 root 没权,是 SELinux:

systemd_logind_t 无法 read/open /swap/swapfile(标签 default_t)

audit2allow 放行 default_t 能救急,但太宽。

Fedora Magazine 更推荐的做法是:

  1. 独立 subvolume:/var/swap
  2. 文件:/var/swap/swapfile
  3. SELinux 标签:swapfile_t
  4. UEFI 下靠 systemd 写入 HibernateLocation,不必长期维护 cmdline 里的 resume=
  5. dracut 加上 resume 模块后重建 initramfs

于是把 DIY 残留清掉,迁到官方路径:

zram 仍保持禁用(个人选择;官方文档允许磁盘 swap 与 zram 并存)。

实用结论

合盖 + 外接屏 + 只有 s2idle 的笔记本,过夜很容易变成「浅睡空转」。空闲自动挂起会放大循环;Motorcomm 有线网卡则可能在循环末端用中断风暴把机器钉死。
有 DP 误唤醒时,suspend-then-hibernate 也救不了:挂都挂不住,就谈不上稍后自动休眠。

短期规避:

长期:

下次再在锁屏上看到通知,至少知道:它多半只是证人,不是凶手。

环境备忘