内核内存泄漏最容易被误判。free 变少不一定是泄漏,page cache、slab cache、网络 buffer、文件系统缓存都可能让可用内存下降。真正的泄漏,是某类对象持续增长,并且没有合理回收路径。

所以排查内核内存问题时,第一步不是问“谁泄漏了”,而是问“内存去了哪里”。

先区分内存类型

先看整体内存:

cat /proc/meminfo
slabtop
cat /proc/slabinfo

重点区分:

  • page cache
  • slab reclaimable
  • slab unreclaimable
  • vmalloc
  • page table
  • socket buffer
  • driver 私有 DMA buffer

如果 SReclaimable 增长,可能是缓存,不一定是泄漏。如果 SUnreclaim 持续增长,就要重点关注 slab 对象。

slabtop 找增长对象

slabtop 能快速看到哪些 cache 占用高:

slabtop -o

如果某个 cache 持续增长,例如:

kmalloc-512
dentry
inode_cache
skbuff_head_cache
foo_request

下一步要判断这是正常工作负载,还是对象没有释放。

通用 kmalloc cache 比较麻烦,因为很多调用点都会使用 kmalloc-512 这类 cache。专用 cache 更容易分析,例如驱动自己创建的 foo_request

kmemleak 适合找不可达对象

如果内核启用了 kmemleak:

CONFIG_DEBUG_KMEMLEAK=y

可以使用:

echo scan > /sys/kernel/debug/kmemleak
cat /sys/kernel/debug/kmemleak

kmemleak 的思路是扫描内存引用,找出已经没有指针可达但仍未释放的对象。

它很适合抓:

  • 错误路径漏 kfree
  • probe 失败后资源未释放
  • 初始化中途失败
  • 某些 list 上对象被摘掉后没有释放

但 kmemleak 不是万能的。对象仍然被全局链表引用,但逻辑上已经不该存在,这种泄漏 kmemleak 未必会报。

page_owner 看页分配来源

如果问题不是 slab,而是 page 分配持续增长,可以启用 page owner:

page_owner=on
CONFIG_PAGE_OWNER=y

然后查看:

cat /sys/kernel/debug/page_owner

实际使用时通常要配合脚本聚合调用栈。page owner 能回答一个关键问题:这些页是谁分配的。

它适合分析:

  • 大量 page allocation
  • driver DMA buffer 泄漏
  • 网络收包路径积压
  • 文件系统或块层 buffer 异常

代价是有额外开销,不建议一直在生产环境打开。

引用计数泄漏比内存泄漏更常见

很多内存泄漏表面上是对象没释放,根因是引用计数没有归零。

典型模式:

obj = get_obj(id);
if (!obj)
        return -ENOENT;

ret = do_something(obj);
if (ret)
        return ret;

put_obj(obj);

错误路径漏了 put_obj()。对象仍然被引用,所以不会释放。kmemleak 也不会报,因为对象仍然可达。

这类问题要追:

  • get/put 是否成对
  • 错误路径是否覆盖
  • 异步 work 是否持有引用
  • timer 是否持有引用
  • file private data 是否释放
  • open/release 是否平衡

refcount 泄漏是内核资源泄漏里非常高频的一类。

probe 错误路径是高危区

驱动 probe 阶段常见泄漏:

  • 分配私有结构后中途失败
  • 注册子设备后失败但未注销
  • 开启 clock/reset 后失败未关闭
  • request irq 后硬件仍可能触发
  • 创建 workqueue 后未 destroy
  • devm 和非 devm 资源混用

devm_ 能降低释放负担,但不能覆盖所有资源。比如注册到子系统的对象,通常仍需要显式 unregister。

遇到 probe 失败相关泄漏,建议主动注入失败点。只测试成功路径,很难发现错误路径资源泄漏。

网络内存增长要看队列

如果 skbuff_head_cache 或网络相关 buffer 增长,要先看队列是否积压:

ss -m
cat /proc/net/sockstat
cat /proc/net/softnet_stat

网络内存不一定是泄漏,可能是:

  • 用户态不读 socket
  • qdisc 队列积压
  • NAPI 处理不过来
  • 驱动回收 TX descriptor 异常
  • RX buffer replenishment 失衡

如果是驱动问题,要重点看 TX complete 和 RX recycle 路径。很多网卡驱动泄漏不是 kmalloc 漏释放,而是 DMA buffer 没有 unmap 或 skb 没有 dev_kfree。

vmalloc 增长要看模块和映射

vmalloc 区域增长可以看:

cat /proc/vmallocinfo

常见来源:

  • ioremap
  • module load
  • vmalloc 分配的大 buffer
  • bpf map
  • vmap

如果是驱动 ioremap 泄漏,通常和 probe/remove 或热插拔有关。如果模块反复加载卸载后 vmalloc 增长,就要检查 module exit 路径。

一份内存泄漏排查顺序

1. 记录 meminfo,确认增长类型
2. 用 slabtop/slabinfo 找增长 cache
3. 判断是 reclaimable 还是 unreclaimable
4. 如果是 slab,找对象类型和分配路径
5. 如果是 page,启用 page_owner 聚合调用栈
6. 如果疑似不可达对象,使用 kmemleak
7. 检查 get/put、open/release、probe/remove
8. 构造错误路径和反复加载卸载测试
9. 用 trace 或统计计数确认修复

结论

内核内存泄漏排查的关键是归属。先确认内存属于 page cache、slab、page allocation、vmalloc 还是网络 buffer,再追对应对象的生命周期。

真正可靠的结论不是“内存变少了”,而是“某类对象持续增长,分配路径是这里,释放路径没有走到,补丁后计数稳定”。这才是一条完整证据链。