作者 nyanpasu64 于 2024 年 12 月 29 日撰写。其桌面双系统启动 Windows 和 Linux,多年来 Linux 在高内存使用时睡眠电脑会经常崩溃,唤醒后显示黑屏或光标移动,或进入无图像的“植物人”状态,仅对 magic SysRq 或硬重置有响应。作者将此行为归因于 amdgpu 驱动的电源/内存管理错误,花了一年多时间 brainstorm 和实施解决方案。
诊断问题:
- 2023 年 9 月开始调试,设置为 Gigabyte B550M DS3H 主板、AMD RX 570 GPU 和 1TB Kingston A2000 NVMe SSD,运行 Arch Linux 及 systemd-boot 和 Linux 6.4。系统崩溃后首先检查日志,如
journalctl --system -b -1
可打印上次启动的系统日志,但有时崩溃后日志无记录。电脑进入“不死”状态,能显示 KDE 锁屏时钟实时更新,但登录或交互会锁定,推测是 NVMe 存储驱动在系统唤醒时未重新初始化导致系统冻结和日志停止写入。尝试通过添加内核参数nvme_core.default_ps_max_latency_us=0
和启用软件 IOMMU 解决 APST 问题但未成功,安装 SSD 固件升级和升级到 2TB 启动 SSD 也无帮助。系统会连续尝试多种睡眠模式,导致系统日志混乱和内核进一步损坏,关闭此功能后简化了调试但未解决根本问题。还尝试通过echo 1 > /sys/power/pm_trace
检查睡眠失败的位置,发现 Linux 会从 amdgpu 挂起失败中恢复而不是进入全系统挂起,pm_trace
将睡眠 - 唤醒进度存储在计算机系统时间中。之后启用 systemd 调试 shell,通过添加systemd.debug_shell
内核参数或运行systemctl enable debug-shell
可在系统崩溃时运行命令,因键盘问题还使用了 PS/2 键盘和后来设置的串行控制台。通过查看崩溃日志,发现崩溃通常发生在 amdgpu 的 TTM 缓冲区驱逐中,查找 amdgpu 的 Gitlab 错误跟踪器发现相关问题,了解到 Linux amdgpu 驱动在高内存使用下有错误,会导致系统内存不足崩溃而不是将内存移动到磁盘交换。
上游调试:
- 认为要在挂起磁盘存储之前将 VRAM 驱逐到系统内存,在讨论后 Mario Limonciello 建议启用
/sys/power/pm_print_times
和/sys/power/pm_debug_messages
,日志显示睡眠时 NVMe 和 amdgpu 驱动并行进入pci_pm_suspend
。尝试将 GPU 挂起提前到 SSD 挂起之前,通过移动 VRAM 驱逐的位置来解决问题,但仍会失败,因为pm_restrict_gfp_mask()
在dpm_prepare
或dpm_suspend
调用之前禁用了交换,导致 amdgpu 备份 VRAM 时内存不足。测试补丁时“daqiu li”报告挂起极慢并建议使用__GFP_NORETRY
进行分配,但作者未体验到问题也未得到 amdgpu 开发者的回复。
中途:使用 Ghidra 调试崩溃:
- 在测试
prepare()
期间的交换时,遇到 amdgpu 崩溃错误BUG: unable to handle page fault for address: fffffffffffffffc
,通过保存并提取amdgpu.ko
内核模块,在 Ghidra 中反编译并映射崩溃位置到内核源代码,发现是dm_resume
中的空指针解引用错误,原因是drm_atomic_helper_suspend()
可能返回错误指针并被直接赋值给指针导致解引用,Mario 修复了此问题。
放弃:在 prepare()期间允许交换?:
- 高内存使用仍会导致睡眠失败,想在
dpm_prepare
备份 VRAM 后禁用交换,但面临实际挑战,如pm_restrict_gfp_mask()
的声明和调用位置问题,以及在dpm_suspend_start
中禁用交换的正确性挑战,如 hybrid sleep 的情况,此方法虽减少了失败或崩溃的挂起次数,但未完全解决问题,且作者未继续尝试上游更改。
附注:关机时损坏的控制台:
- 关机时间歇性出现充满 8x16 颜色块的损坏屏幕,通常在之前的睡眠尝试中找到 amdgpu 驱动的错误,作者已多次报告此问题但未单独提交 bug 报告,也未确定原因。
解决方法:电源管理通知器:
- 2024 年 11 月,Mario 要求用户测试一个允许多交换时驱逐的补丁,该补丁通过调用
register_pm_notifier()
函数在回调结构上,在PM_SUSPEND_PREPARE
消息时调用amdgpu_device_evict_resources()
来驱逐 VRAM 到系统内存,在enter_state
函数中修改流程图,此方法允许 amdgpu 在交换被禁用或磁盘被冻结之前驱逐 VRAM 到系统内存,测试后能在高内存和 VRAM 使用下多次成功挂起,唯一问题是有几秒音频循环,此补丁经过几轮代码审查后合并到 amdgpu 树中,最终解决了该问题,此补丁应会在 2025 年的稳定 Linux 内核 6.14 中推出,并将在发行版更新周期中应用。还发现了另一个 amdgpu 工作区 memreserver,它通过分配系统内存并填充 0xFF 字节来为 VRAM 腾出空间,但作者未测试其功能和性能。
总之,此问题经过一年多的调试和多人尝试才得以解决,预计 2025 年进入稳定 Linux 内核 6.14 并向发行版推出。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。