显存用完了会怎样?
大多数游戏玩家的预期是:崩了。游戏开始一个接一个崩溃,帧率掉到没法玩,游戏体验彻底完蛋。
但 pixelcluster 的作者追问了一句:这真的是不可避免的吗?显存耗尽为什么非得这么痛苦?能不能让它痛苦得少一点?
几个月前,他写了一篇关于改进 Linux 游戏显存管理的文章。如今,他的内核补丁终于合入了上游,排队进入 Linux 7.3。这篇新文章,是他对「显存耗尽」这个问题的深挖。

理论上,这只是性能问题
理论上,显存耗尽应该只影响性能,不影响稳定性。
GPU 驱动从诞生起就支持「显存超售」(overcommit):你可以请求任意多的显存,内核驱动会根据 GPU 物理内存的实际情况决定分配多少。
性能变差的根本原因也很简单:一旦游戏请求的显存超过物理显存,一部分数据就要被驱逐(evict)到 CPU 内存。而 GPU 访问 CPU 内存要走 PCIe 总线,比访问显存慢得多。
作者算了一笔硬账:假设 GPU 走 PCIe 4.0 x16,带宽不到 32GiB/s。每毫秒能传约 32.2MiB 数据。要维持 30 FPS(每帧 33.3ms),GPU 每帧能访问的数据上限是约 1GiB。
换句话说,只要被驱逐到 CPU 内存的数据多到 GPU 每帧要抓取超过 1GiB,就无论如何也打不到 30 帧了。
但这里有个反直觉的地方:并非所有内存被驱逐都一样痛。
缓存改变了游戏规则
作者在 RDNA3 显卡上跑了微基准测试。结果符合预期:只要数据能命中 L2 缓存,访问延迟和显存一致——因为数据直接从缓存读取。
但一旦超过 L2 缓存大小(RDNA3 上是 6MB),访问 CPU 内存的延迟飙升到约 2400 个时钟周期,而访问显存则维持在同一水平。
更糟的是,CPU 内存访问不走 Infinity Cache(它直接搭在显存之上)。PCIe 抓取比 Infinity Cache 命中慢约 7.3 倍,比直接读显存慢约 4.6 倍。
所以结论是:显存耗尽必然带来一定程度的性能下降,但下降多少,取决于被驱逐的内存访问模式。 有些分配 GPU 只会访问其中一小部分,哪怕驱逐了几个 GiB,实际每帧访问的数据可能仍在 1GiB 硬限制之下。
理论上,你完全可能在不彻底毁掉性能的情况下耗尽显存。
但实践里,崩溃先来了
作者满怀信心地打开 SteamOS,启动游戏,调高画质——然后:
radv/amdgpu: Not enough memory for command submission.
这个错误很诡异。它说的是「命令提交内存不足」,但命令提交本身并不分配新资源——所有命令缓冲区早就分配好了,而且分配是成功的。
那问题出在哪?
一次内核锁的恐怖之旅
amdgpu 驱动每次提交命令前,必须确保 GPU 命令可能引用的所有内存都是可访问的。在现代无绑定(bindless)图形 API 下,你必须假设所有已分配内存都可能被引用。
大多数分配既能放在显存也能放系统内存。但有些分配只能放显存。如果这些分配因为别的应用占用了显存而被驱逐到系统内存,amdgpu 就得把它们挪回显存。而显存已经满了——要挪回来,就得驱逐别的东西。
结果驱逐失败,内核报了内存不足。
为什么驱逐会随机失败?这要绕到内核的锁机制。
驱逐一个内存分配,需要先拿到跟这个分配关联的锁。而命令提交时,也要锁住提交里引用的每一个分配,防止别的应用在准备 GPU 工作时把内存挪走。
如果两个 GPU 提交并发进行,就可能出现经典的 ABBA 死锁:提交 A 想驱逐提交 B 已经锁住的分配,而提交 B 又需要锁住提交 A 的分配才能继续。
内核有死锁检测机制(wound-wait mutex):当检测到死锁,一个事务被标记为「受伤」,返回 -EDEADLCK 错误,要求事务释放所有锁并从头重试。
在图形子系统中,这套机制被封装成一个叫 drm_exec 的辅助库。但作者研究 TTM(Linux 共享 GPU 内存管理层)的锁代码时发现——TTM 里几乎没有使用 drm_exec。
代码里甚至有一条注释,明说 -EDEADLCK 会导致驱逐失败。
问题找到了:当命令提交时遇到内存压力触发了死锁条件,内核不是重试,而是直接放弃提交。
一周的折磨,终于合入内核
其实早在 2024 年就有人提交过把 drm_exec 接入 TTM 的补丁集,但因为一些未解决的 bug 始终没能合入。
作者的工作就是:把补丁集 rebase 到他的内核版本上,找出那些剩下的 bug。
「rebase 不算太麻烦,找 bug 只花了一周的折磨——期间游戏会在显存激烈争抢下运行三分钟就随机挂起。不算太糟!」
他重新提交了修复后的补丁集,但要正式合入还需要更多工作。不过没关系——显存耗尽至少不会再让应用随机崩溃了。
这是一篇典型的「从内核源码追根究底」的文章。作者没有停在「显存耗尽性能差」这个表层结论,而是顺着一条报错信息,挖到了内核 GPU 内存管理层的死锁处理缺陷。这种把「崩溃」变成「可修复 bug」的过程,正是开源内核协作的日常。
来源: