CmaFree=0 不等于内存耗尽:一次 RK3588 DMA-BUF 排障复盘
看到 /proc/meminfo 里的 CmaFree: 0 kB,最自然的结论是“CMA 已经耗尽”。这次排障最有价值的地方,就是证明了这个结论在特定 Rockchip kernel 配置下完全错误。
如果当时直接扩大 CMA,只会继续挤占普通 RAM,却没有解决任何真实问题。
现场:数字看起来非常危险
板端运行着相机采集、ISP、鱼眼处理、视觉算法和 NPU 推理。现场读数大致是:
CmaTotal: 1048576 kB
CmaFree: 0 kB
MemAvailable: 5.6 ~ 6.7 GiB一边是 1 GiB CMA 全部显示“不可用”,另一边是普通内存仍然充足。单看这三个数字,无法判断到底是 CMA 真耗尽、统计口径异常,还是内核采用了不同的页管理语义。
第一次误判:把 CmaFree 当成真实余量
最初的排查方向很容易落到“谁吃光了 1 GiB”。但通过 DMA-BUF 统计,能看到的主要占用只有约 237~246 MiB:
| Owner 类型 | 可见 DMA-BUF 占用 |
|---|---|
| NPU/视觉算法进程 | 约 152 MiB |
| 相机流水线 | 约 50~58 MiB |
| ISP/3A 服务 | 约 33 MiB |
| 其他图形进程 | 少量 |
可见占用和 1 GiB 之间存在巨大差距。如果这时继续盯着 CmaFree,分析就会陷入“还有几百 MiB 到底去哪了”的错误问题。
证据怎样收敛
这类内存问题不能依赖一个指标。我最终把证据拆成四层:
1. 先看普通内存是否真的紧张
MemAvailable 仍有数 GiB,说明系统并没有普通意义上的内存耗尽。它不能证明 CMA 健康,但可以排除“全系统已经濒临 OOM”的叙述。
2. 再看 DMA-BUF owner
/sys/kernel/debug/dma_buf/bufinfo 能回答谁持有可见 DMA-BUF,以及每个 owner 大致占了多少。它比只看进程 RSS 更接近相机、RGA、DRM 和 NPU 的真实共享内存路径。
不过,bufinfo 也不是全知视角。驱动内部池、不可见的连续内存和短时峰值仍可能不在当前快照里,所以不能拿“可见总量”直接等同于完整 CMA 使用量。
3. 检查分配成功与失败
现场 cma_alloc_fail 持续为 0,短时采样期间 DMA-BUF 总量也没有不断增长。这个证据比 CmaFree 更关键:至少在当前负载下,没有看到连续内存分配失败。
4. 最后查 kernel config
决定性证据是内核启用了:
CONFIG_CMA_INACTIVE=y在这套内核语义里,CMA 页不会按通常方式激活到普通页分配器,因此 CmaFree / nr_free_cma 不能直接代表仍可用于 CMA 分配的 headroom。0 kB 是统计语义的结果,不等于所有连续内存已经被业务占用。
根因:用错了指标,不是内存突然消失
这次问题的根因不是某个进程泄漏了 1 GiB,而是诊断方法默认套用了另一种 kernel 配置下的指标解释。
真正应该关注的是:
- kernel 是否启用特殊 CMA 模式;
cma_alloc_success/cma_alloc_fail是否恶化;- DMA-BUF owner 和总量是否持续增长;
- 服务重启时是否出现双池重叠和分配失败;
- 长时间运行是否存在稳定的内存斜率。
384 MiB 还是 512 MiB
现场可见 DMA-BUF 约 238 MiB。若只做静态减法,384 MiB 似乎还剩约 146 MiB;但生产系统还要覆盖驱动内部占用、服务重启时新旧 buffer pool 重叠、RGA/MPP/ISP 峰值和诊断流临时开启。
因此这次最终选择是 512 MiB,而不是 384 MiB。
这个结论不是说 384 MiB 一定运行不了,而是它给重启和峰值留下的工程余量偏小。1 GiB 同样没有必要,因为在 CMA_INACTIVE 模式下,过大的 CMA 会真实挤占普通 RAM。
哪些证据还不够
短时间内 cma_alloc_fail=0,不能证明系统永远不会泄漏。两分钟采样也不能代替一小时以上的 steady-state、循环重启和高峰负载测试。
最终验收至少应包含:
- 生产负载长时间运行;
- 相机、算法和编码服务循环重启;
- 同时观察 DMA-BUF owner、分配失败和普通内存;
- 明确区分生产态与开启额外诊断流的状态;
- 记录峰值而不是只看最后一帧。
结论
内核指标没有上下文就没有结论。CmaFree=0 只是一个现象,只有把 kernel config、DMA-BUF owner、分配失败和重启行为连起来,才能判断 CMA 是否真的不足。
这次排障避免了一个典型的“看到危险数字就扩容”的错误修复,也把容量选择从感觉变成了可以复测的工程决策。