第三章 内存管理故障诊断:CMA分配失败、IOMMU页表错误、内存泄漏导致GPU hang的排查方法
内存管理这块,说实在的,是嵌入式GPU驱动里最容易出幺蛾子的地方。我这些年排查过的GPU hang,十有八九都跟内存沾点边。CMA分配失败、IOMMU页表乱了套、内存泄漏慢慢把系统拖死——这三个问题,基本覆盖了90%的内存相关故障。
今天咱们就一个一个掰开揉碎了讲。我会把我在项目里踩过的坑、用过的招,都抖出来给你看。
3.1 CMA分配失败:系统内存的"最后防线"崩了
CMA(Contiguous Memory Allocator)是啥?说白了,就是系统预留的一块连续物理内存大池子。GPU这种需要大块连续内存的硬件,特别依赖它。
CMA分配失败,我遇到过好几次。最典型的一次,是某个平板项目,跑着跑着屏幕突然黑了,dmesg里赫然写着:
[ 12.345678] cma: cma_alloc: alloc failed, req-size: 8 MiB, available: 0 KiB
[ 12.345679] ion: ion_alloc: failed to allocate from cma heap
看到这个,基本可以断定是CMA池子被掏空了。
3.1.1 为什么会分配失败?
原因其实就几个:
- CMA池子太小——设备树里配的size不够用。我见过不少项目,默认配个16MB就觉得够了,结果GPU一跑大分辨率就崩。
- 碎片化严重——CMA虽然能迁移页面,但迁移不过来的时候,就真的没辙了。
- 被其他模块占着不还——比如某个驱动申请了CMA内存,用完没释放,或者释放得慢。
关键点:CMA分配失败,不一定真的是内存不够。很多时候是碎片化导致的。你想想看,8MB的连续空间,在碎片严重的池子里可能根本凑不出来。
3.1.2 排查步骤
我个人习惯,遇到CMA问题先做这三步:
- 查dmesg——看有没有cma_alloc failed的日志。同时留意一下available的值,是0还是非0但不够。
- 看/proc/cma——这个节点会告诉你CMA的使用情况。比如:
# cat /proc/cma
cma: name=linux,cma, total=0x10000000, used=0x0fe00000, max_used=0x0ff00000
used接近total的时候,就要警惕了。
- 检查设备树配置——看看CMA size是不是设得太抠门了。我建议至少给GPU留32MB以上,具体看你的分辨率需求。
我的小技巧:可以在驱动初始化时,主动预分配一块CMA内存,用完了再释放。这样能避免运行时突然分配失败。我曾经在一个项目里用这个办法,把CMA分配失败率从5%降到了0.1%。
3.2 IOMMU页表错误:GPU的"虚拟地址翻译官"罢工了
IOMMU(Input-Output Memory Management Unit)是GPU访问内存的翻译官。它把GPU看到的虚拟地址,翻译成物理地址。页表一旦出错,GPU就找不到正确的内存位置,结果就是——hang。
我记得有一次,客户反馈说GPU跑某个3D场景必死。我一看日志,IOMMU page fault刷屏:
[ 234.567890] gpu: iommu page fault: iova=0x12345000, flags=0x4
[ 234.567891] gpu: master: GPU, reason: read access to non-present page
这个iova地址,就是GPU想访问但找不到的虚拟地址。
3.2.1 页表错误的常见原因
| 错误类型 | 原因 | 典型表现 |
|---|---|---|
| 页表未映射 | GPU访问的虚拟地址没有对应的物理页 | flags=0x4(non-present) |
| 权限错误 | GPU试图写一个只读的页 | flags=0x8(write access) |
| 页表损坏 | 内存被踩,页表数据被篡改 | 随机地址报错,或者报错地址不连续 |
| TLB未刷新 | 页表更新了,但GPU的TLB缓存还是旧的 | 同一个地址反复报错 |
3.2.2 排查方法
遇到IOMMU page fault,我一般这么干:
- 看fault地址——iova是多少?这个地址属于哪个buffer?可以通过驱动里的地址映射表反查。
- 检查映射流程——是不是buffer已经释放了,但GPU还在用?这是最常见的"use-after-free"问题。
- 查TLB刷新——页表更新后,有没有调用iommu_flush_tlb_all()?我踩过这个坑,少刷一次TLB,GPU就hang给你看。
注意:IOMMU page fault不一定是驱动的问题。有时候是硬件bug,比如DMA写穿了,把页表区域给踩了。我曾经排查过一个案子,最后发现是某个外设的DMA地址配错了,直接写到了IOMMU的页表内存里。
3.3 内存泄漏导致GPU hang:温水煮青蛙
内存泄漏不像CMA分配失败那么突然,它是慢慢积累的。今天漏一点,明天漏一点,等到系统内存被吃光,GPU就彻底动不了了。
我印象最深的一次,是一个视频播放项目。刚开始跑得好好的,播了半小时后画面开始卡顿,一小时后直接死机。查到最后,是GPU驱动里每次分配command buffer都忘了释放。
3.3.1 泄漏的典型位置
- Command buffer——每次提交渲染命令都分配,但完成回调里忘了释放。
- Fence对象——同步用的fence,创建了没销毁。
- Buffer对象——用户空间申请了buffer,关闭fd时内核没清理。
- Page table——动态映射的页表项,unmap时没回收。
3.3.2 排查工具和方法
排查内存泄漏,我推荐这几个手段:
- /proc/meminfo——看MemAvailable是不是一直在下降。如果每次跑GPU场景都降,基本就是泄漏了。
- kmemleak——内核自带的内存泄漏检测工具。打开后,它会定期扫描未释放的内存块。
# echo scan > /sys/kernel/debug/kmemleak
# cat /sys/kernel/debug/kmemleak
unreferenced object 0xffffffc0a1234000 (size 4096):
comm "gpu_worker", pid 1234, jiffies 4294891234
backtrace:
[] __kmalloc+0x1a0/0x2c0
[] gpu_alloc_cmd_buffer+0x34/0x80
...
看到这个backtrace了吗?它直接告诉你是gpu_alloc_cmd_buffer分配的内存没释放。
- 自己加统计——我习惯在驱动里加一个debugfs节点,显示当前分配了多少buffer、多少command。如果数值只增不减,那就是泄漏了。
我的经验:排查内存泄漏,最笨但也最有效的方法,就是"二分法注释"。把怀疑的代码段注释掉,看泄漏还发不发生。我曾经用这个办法,在一个下午内定位了三个泄漏点。
3.4 综合案例:一次完整的GPU hang排查
最后,我分享一个真实案例。某款车机产品,导航时GPU偶尔hang住。日志如下:
[ 456.789012] gpu: timeout detected, resetting...
[ 456.789013] gpu: last submitted job: ctx=3, seqno=1024
[ 456.789014] iommu: page fault at iova=0x20000000, flags=0x4
我的排查思路:
- 先看IOMMU fault——iova=0x20000000,查映射表发现是某个纹理buffer的地址。
- 查这个buffer的状态——发现它已经被释放了,但GPU还在用。典型的use-after-free。
- 追查释放时机——发现是某个fence回调里提前释放了buffer,但GPU还没执行完。
- 修复——把buffer的释放时机推迟到GPU真正完成之后。
你看,一个看似复杂的GPU hang,拆开来就是IOMMU页表错误 + 内存管理不当。只要按部就班排查,总能找到根因。
总结一下:CMA分配失败看dmesg和/proc/cma;IOMMU页表错误看fault地址和映射关系;内存泄漏靠kmemleak和统计节点。这三个问题,说白了都是内存管理没做好。把基础打牢了,GPU hang就能少一大半。
嗯,这一章就到这里。下一章咱们聊聊GPU调度和同步机制,那又是另一片天地了。