Skip to content

CmaFree=0 不等于内存耗尽:一次 RK3588 DMA-BUF 排障复盘

看到 /proc/meminfo 里的 CmaFree: 0 kB,最自然的结论是“CMA 已经耗尽”。这次排障最有价值的地方,就是证明了这个结论在特定 Rockchip kernel 配置下完全错误。

如果当时直接扩大 CMA,只会继续挤占普通 RAM,却没有解决任何真实问题。

现场:数字看起来非常危险

板端运行着相机采集、ISP、鱼眼处理、视觉算法和 NPU 推理。现场读数大致是:

text
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

决定性证据是内核启用了:

text
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、循环重启和高峰负载测试。

最终验收至少应包含:

  1. 生产负载长时间运行;
  2. 相机、算法和编码服务循环重启;
  3. 同时观察 DMA-BUF owner、分配失败和普通内存;
  4. 明确区分生产态与开启额外诊断流的状态;
  5. 记录峰值而不是只看最后一帧。

结论

内核指标没有上下文就没有结论。CmaFree=0 只是一个现象,只有把 kernel config、DMA-BUF owner、分配失败和重启行为连起来,才能判断 CMA 是否真的不足。

这次排障避免了一个典型的“看到危险数字就扩容”的错误修复,也把容量选择从感觉变成了可以复测的工程决策。

参考:Linux Kernel DMA-BUF 文档