前言

SLUB 是 Linux 默认的通用内核对象分配器。常见小对象 kmalloc() 分配通常会落到某个 kmem_cache(例如 kmalloc-8 ~ kmalloc-8k,具体范围取决于内核版本、页大小和 KMALLOC_MAX_CACHE_SIZE 等配置),更大的分配可能走页分配或大对象路径。内核子系统通过 kmem_cache_create() 建立的专用缓存(dentryext4_inode_cache……)也共享同一套管理框架:每个 cache 由若干 slab(order-N 的伙伴页)组成,每个 slab 切成等大的 object

本文源码路径和行号以 Linux/Android common kernel 的 SLUB 实现为参考;不同 Android 厂商内核常有 backport 和私有补丁,实际排查时应以目标设备的 kernel tree 为准。

当我们分析是否发生 slab 内存泄漏时,一般流程是这样的:

  1. 查看 /proc/meminfo,确认 SlabSReclaimableSUnreclaim 是否异常。

  2. 查看 /proc/slabinfo,确认是哪一个 kmem_cache 占用异常。

  3. 做时间序列采样,区分持续增长、峰值残留和可回收缓存。

  4. 打开 slub_debug 或使用 trace/ftrace/kmemleak 等工具,抓取分配调用栈定位异常模块和函数。
    所以本文着手于 /proc/slabinfo 进行讲解!

slabinfo的代码实现

/proc/slabinfomm/slab_common.c 中注册的 seq_file 接口(SLAB/SLUB 共用框架,get_slabinfo() 由具体分配器实现);

接口注册与 seq_file 骨架

/proc/slabinfomm/slab_common.c:1193slab_proc_init() 中注册,权限 SLABINFO_RIGHTS——对 SLUB 是 0400(只读、仅 root):

static int __init slab_proc_init(void)
{
	proc_create("slabinfo", SLABINFO_RIGHTS, NULL, &slabinfo_proc_ops);
	return 0;
}
module_init(slab_proc_init);

输出走标准 seq_file 迭代器(mm/slab_common.c:1172):

static const struct seq_operations slabinfo_op = {
	.start = slab_start,
	.next  = slab_next,
	.stop  = slab_stop,
	.show  = slab_show,
};

"迭代单元"就是全局链表 slab_caches 上的每一个 kmem_cache

void *slab_start(struct seq_file *m, loff_t *pos)
{
	mutex_lock(&slab_mutex);                    // 整个读取期间持有 slab_mutex
	return seq_list_start(&slab_caches, *pos);  // 定位到第 *pos 个 cache
}
void *slab_next(struct seq_file *m, void *p, loff_t *pos)
{
	return seq_list_next(p, &slab_caches, pos);
}
void slab_stop(struct seq_file *m, void *p)
{
	mutex_unlock(&slab_mutex);
}

slab_show() 对第一个元素打印表头,然后对每个 cache 调用 cache_show()mm/slab_common.c:1085):

static void cache_show(struct kmem_cache *s, struct seq_file *m)
{
	struct slabinfo sinfo;

	memset(&sinfo, 0, sizeof(sinfo));
	get_slabinfo(s, &sinfo);                     // ← 数字从这来

	seq_printf(m, "%-17s %6lu %6lu %6u %4u %4d",
		   s->name, sinfo.active_objs, sinfo.num_objs, s->size,
		   sinfo.objects_per_slab, (1 << sinfo.cache_order));
	seq_printf(m, " : tunables %4u %4u %4u", ...);   // SLUB 下恒为 0
	seq_printf(m, " : slabdata %6lu %6lu %6lu", ...);
	slabinfo_show_stats(m, s);                       // SLUB 下是空函数
	seq_putc(m, '\n');
}

表头(print_slabinfo_header()mm/slab_common.c:1048)即我们熟悉的 slabinfo - version: 2.1 两行。

数字从哪来:SLUB 版 get_slabinfo()

get_slabinfo() 声明为分配器各实现一份,SLUB 版本在 mm/slub.c:6309

void get_slabinfo(struct kmem_cache *s, struct slabinfo *sinfo)
{
	unsigned long nr_slabs = 0;
	unsigned long nr_objs = 0;
	unsigned long nr_free = 0;
	int node;
	struct kmem_cache_node *n;

	for_each_kmem_cache_node(s, node, n) {
		nr_slabs += node_nr_slabs(n);          // atomic_long_read(&n->nr_slabs)
		nr_objs += node_nr_objs(n);            // atomic_long_read(&n->total_objects)
		nr_free += count_partial(n, count_free);  // 遍历 partial 链表统计空闲 object
	}

	sinfo->active_objs = nr_objs - nr_free;
	sinfo->num_objs    = nr_objs;
	sinfo->active_slabs = nr_slabs;
	sinfo->num_slabs   = nr_slabs;
	sinfo->objects_per_slab = oo_objects(s->oo);   // 每个 slab 切几个 object
	sinfo->cache_order = oo_order(s->oo);          // 每个 slab 占 2^order 页
}

三个关键语义,直接决定了如何正确读 slabinfo:

  1. total_objects/nr_slabs 在 slab 页诞生/销毁时增减alloc_slab_page()atomic_long_inc(&n->nr_slabs); atomic_long_add(objects, &n->total_objects)free_slab() 中反向操作)。因此它们包含 per-cpu 活跃 slab(c->page)和 per-cpu partial 链表上的 slab,不只是 node 链表上可见的部分。

  2. 空闲 object 只遍历各 node 的 partial 链表统计。per-cpu 活跃 slab 的 freelist 不会被扫到——也就是说,per-cpu 缓存里"空闲但尚未归还"的 object 会被计入 active_objs。因此 active_objs 是 slabinfo 视角下的近似活跃对象数,不等价于精确的 live allocation;CPU 数较多、per-cpu partial 较多或系统压力较大时,这个偏差可能被放大。

  3. SLUB 下 active_slabs == num_slabs 恒成立(都等于 nr_slabs),表头里 "active/total slab" 两列在 SLUB 上没有区分度——这是 SLAB 分配器遗留的 ABI 形状。

tunables <limit> <batchcount> <sharedfactor>slabdata ... <sharedavail> 是 SLAB 分配器的批量分配/共享数组概念,SLUB 没有对应物,slabinfo 里写死为 0。 slabinfo_show_stats() 在 SLUB 里是空函数(mm/slub.c:6331)。 写路径 slabinfo_write() 同样直接返回 -EIOmm/slub.c:6335)——SLUB 下 /proc/slabinfo 是纯只读文件;只有 SLAB(SLABINFO_RIGHTS=0600)才支持写 tunables。

顺带一提,get_slabinfo() 还被 OOM 路径复用:dump_unreclaimable_slab()mm/slab_common.c:1114)在 OOM 时遍历 slab_caches,把所有没有 SLAB_RECLAIM_ACCOUNT 标记的 cache 的用量打到内核日志,作为 OOM 报告的一部分。

输出逐列解读

一次真实的 cat /proc/slabinfo

spring:/ $ cat /proc/slabinfo
slabinfo - version: 2.1
# name            <active_objs> <num_objs> <objsize> <objperslab> <pagesperslab> : tunables <limit> <batchcount> <sharedfactor> : slabdata <active_slabs> <num_slabs> <sharedavail>
zspage             42237 134758     56   73    1 : tunables    0    0    0 : slabdata   1846   1846      0
zs_handle         251097 796672      8  512    1 : tunables    0    0    0 : slabdata   1556   1556      0
dm_verity_fec_buffers      8      8   4048    8    8 : tunables    0    0    0 : slabdata      1      1      0
dm_bufio_buffer       26     26    152   26    1 : tunables    0    0    0 : slabdata      1      1      0
dm_bufio_buffer       26     26    152   26    1 : tunables    0    0    0 : slabdata      1      1      0
dm_bufio_buffer-4    100    100    160   25    1 : tunables    0    0    0 : slabdata      4      4      0
dm_bufio_buffer-4     50     50    160   25    1 : tunables    0    0    0 : slabdata      2      2      0
dm_bufio_buffer-4     75     75    160   25    1 : tunables    0    0    0 : slabdata      3      3      0
dm_bufio_buffer-4    125    125    160   25    1 : tunables    0    0    0 : slabdata      5      5      0
dm_bufio_buffer-4     75     75    160   25    1 : tunables    0    0    0 : slabdata      3      3      0
dm_bufio_buffer-4     50     50    160   25    1 : tunables    0    0    0 : slabdata      2      2      0
dm_bufio_buffer-4     25     25    160   25    1 : tunables    0    0    0 : slabdata      1      1      0
dm_bufio_buffer-4     50     50    160   25    1 : tunables    0    0    0 : slabdata      2      2      0
dm_bufio_buffer-4     50     50    160   25    1 : tunables    0    0    0 : slabdata      2      2      0
dm_bufio_buffer-4     50     50    160   25    1 : tunables    0    0    0 : slabdata      2      2      0
dm_bufio_buffer-4     50     50    160   25    1 : tunables    0    0    0 : slabdata      2      2      0
dm_bufio_buffer-4     25     25    160   25    1 : tunables    0    0    0 : slabdata      1      1      0

//...

fscrypt_info        1922   4890    136   30    1 : tunables    0    0    0 : slabdata    163    163      0
wakeup_irq_node_cache    128    128     32  128    1 : tunables    0    0    0 : slabdata      1      1      0
AF_VSOCK             176    176   1472   22    8 : tunables    0    0    0 : slabdata      8      8      0
bridge_fdb_cache       0      0    128   32    1 : tunables    0    0    0 : slabdata      0      0      0
nf-frags               0      0    200   20    1 : tunables    0    0    0 : slabdata      0      0      0
xfrm6_tunnel_spi       0      0    128   32    1 : tunables    0    0    0 : slabdata      0      0      0
ip6-frags              0      0    200   20    1 : tunables    0    0    0 : slabdata      0      0      0
fib6_nodes           256    256    128   32    1 : tunables    0    0    0 : slabdata      8      8      0
ip6_dst_cache        256    256    256   32    2 : tunables    0    0    0 : slabdata      8      8      0
ip6_mrt_cache          0      0    192   21    1 : tunables    0    0    0 : slabdata      0      0      0
PINGv6                 0      0   1344   24    8 : tunables    0    0    0 : slabdata      0      0      0
RAWv6                216    216   1344   24    8 : tunables    0    0    0 : slabdata      9      9      0
UDPLITEv6              0      0   1472   22    8 : tunables    0    0    0 : slabdata      0      0      0
UDPv6                198    198   1472   22    8 : tunables    0    0    0 : slabdata      9      9      0
tw_sock_TCPv6        232    232    280   29    2 : tunables    0    0    0 : slabdata      8      8      0
request_sock_TCPv6      0      0    328   24    2 : tunables    0    0    0 : slabdata      0      0      0
TCPv6                108    108   2560   12    8 : tunables    0    0    0 : slabdata      9      9      0
xt_hashlimit         272    272    120   34    1 : tunables    0    0    0 : slabdata      8      8      0
nf_conncount_rb        0      0     96   42    1 : tunables    0    0    0 : slabdata      0      0      0
nf_conncount_tuple      0      0     72   56    1 : tunables    0    0    0 : slabdata      0      0      0
nf_conntrack_expect      0      0    232   35    2 : tunables    0    0    0 : slabdata      0      0      0
nf_conntrack         600    600    320   25    2 : tunables    0    0    0 : slabdata     24     24      0
fq_flow_cache          0      0    128   32    1 : tunables    0    0    0 : slabdata      0      0      0
ashmem_range_cache    512    512     64   64    1 : tunables    0    0    0 : slabdata      8      8      0
ashmem_area_cache    702    702    312   26    2 : tunables    0    0    0 : slabdata     27     27      0
dm_snap_pending_exception      0      0    128   32    1 : tunables    0    0    0 : slabdata      0      0      0
dm_exception           0      0     32  128    1 : tunables    0    0    0 : slabdata      0      0      0
kcopyd_job             0      0   3384    9    8 : tunables    0    0    0 : slabdata      0      0      0
io                  1344   1344     64   64    1 : tunables    0    0    0 : slabdata     21     21      0
dm_uevent              0      0   2888   11    8 : tunables    0    0    0 : slabdata      0      0      0
wg_peer                0      0   1760   18    8 : tunables    0    0    0 : slabdata      0      0      0
allowedips_node        0      0     72   56    1 : tunables    0    0    0 : slabdata      0      0      0

trace_event_file    2024   2024     88   46    1 : tunables    0    0    0 : slabdata     44     44      0
ftrace_event_field   6862   6862     56   73    1 : tunables    0    0    0 : slabdata     94     94      0
pool_workqueue       384    384    256   32    2 : tunables    0    0    0 : slabdata     12     12      0
maple_node         20929  45664    256   32    2 : tunables    0    0    0 : slabdata   1427   1427      0
radix_tree_node    42247 110768    584   28    4 : tunables    0    0    0 : slabdata   3956   3956      0
task_group            64     64    512   32    4 : tunables    0    0    0 : slabdata      2      2      0
mm_struct            544    544   1024   32    8 : tunables    0    0    0 : slabdata     17     17      0
vmap_area          15877  31680     64   64    1 : tunables    0    0    0 : slabdata    495    495      0
kmalloc-rcl-8k         0      0   8192    4    8 : tunables    0    0    0 : slabdata      0      0      0
kmalloc-rcl-4k         0      0   4096    8    8 : tunables    0    0    0 : slabdata      0      0      0
kmalloc-rcl-2k         0      0   2048   16    8 : tunables    0    0    0 : slabdata      0      0      0
kmalloc-rcl-1k         0      0   1024   32    8 : tunables    0    0    0 : slabdata      0      0      0
kmalloc-rcl-512        0      0    512   32    4 : tunables    0    0    0 : slabdata      0      0      0
kmalloc-rcl-256        0      0    256   32    2 : tunables    0    0    0 : slabdata      0      0      0
kmalloc-rcl-192     1134   1134    192   21    1 : tunables    0    0    0 : slabdata     54     54      0
kmalloc-rcl-128     1376   1568    128   32    1 : tunables    0    0    0 : slabdata     49     49      0
kmalloc-rcl-64      2426   4096     64   64    1 : tunables    0    0    0 : slabdata     64     64      0
kmalloc-8k          1405   1424   8192    4    8 : tunables    0    0    0 : slabdata    356    356      0
kmalloc-4k          4348   4400   4096    8    8 : tunables    0    0    0 : slabdata    550    550      0
kmalloc-2k          3815   4304   2048   16    8 : tunables    0    0    0 : slabdata    269    269      0
kmalloc-1k          9343   9408   1024   32    8 : tunables    0    0    0 : slabdata    294    294      0
kmalloc-512        10527  12928    512   32    4 : tunables    0    0    0 : slabdata    404    404      0
kmalloc-256         5908   6048    256   32    2 : tunables    0    0    0 : slabdata    189    189      0
kmalloc-192        11417  15540    192   21    1 : tunables    0    0    0 : slabdata    740    740      0
kmalloc-128       74961686 74966336    128   32    1 : tunables    0    0    0 : slabdata 2342698 2342698      0
kmalloc-64        263841 277632     64   64    1 : tunables    0    0    0 : slabdata   4338   4338      0
kmem_cache_node      576    576     64   64    1 : tunables    0    0    0 : slabdata      9      9      0
kmem_cache           448    448    256   32    2 : tunables    0    0    0 : slabdata     14     14      0

来源

含义

name

s->name

cache 名;kmalloc 系列按尺寸命名(kmalloc_info[]mm/slab_common.c:790

active_objs

total_objects - count_partial(count_free)

已分配在用(+per-cpu 缓存)的 object 数

num_objs

total_objects 之和

该 cache 当前持有的全部 object 容量

objsize

s->size

含对齐/元数据的 object 大小

objperslab

oo_objects(s->oo)

每个 slab 容纳的 object 数

pagesperslab

1 << oo_order(s->oo)

每个 slab 占多少页(order)

slabdata 前两列

nr_slabs

slab 总个数(SLUB 下两列相等)

核心公式:slab 实际吃掉的物理内存 = num_slabs × pagesperslab × PAGE_SIZE 注意不是 active_objs × objsize——后者只是"装进去多少数据",前者才是"占了多少页"。多数 Android 设备长期使用 4KB 页,但新平台可能使用 16KB 页,因此现场计算时不要把页大小写死。

实例:定位 "slab 内存去哪了"

我们就以上面一个章节展示的 slabinfo 来作为实例场景来分析一下slab内存去哪里了?

其实从 slabinfo 中我们可以非常容易的得到 kmalloc-128 的内存是异常的!

# name            <active_objs> <num_objs> <objsize> <objperslab> <pagesperslab> : tunables <limit> <batchcount> <sharedfactor> : slabdata <active_slabs> <num_slabs> <sharedavail>
kmalloc-128       74961686      74966336   128       32           1              : tunables 0       0            0              : slabdata 2342698        2342698     0

字段逐项解读

  • namekmalloc-128。这是内核中最常用的通用内存分配器,专门分配128字节大小的内存块。很多内核驱动、网络数据包结构、临时缓冲都会用到它。

  • active_objs (74,961,686): 当前正在使用中的对象数量。约 7496万 个。

  • num_objs (74,966,336): 内核已经向系统申请并准备好的对象总数(包括空闲的)。约 7496万 个。

  • objsize (128): 每个对象 128 字节。

  • objperslab (32): 每个 slab 页块里包含 32 个这样的对象。

  • pagesperslab (1): 每个 slab 占用 1 个物理页。页大小以目标设备 PAGE_SIZE 为准,常见是 4KB,也可能是 16KB。

  • tunables (0 0 0): SLAB 分配器遗留调优字段,SLUB 下无实际意义。

  • slabdata:

    • active_slabs (2,342,698): 当前有约 234万个 slab 处于活跃状态。

    • num_slabs (2,342,698): slab 总数为 234万。

    • sharedavail (0): 无共享可用。

计算

自洽校验

74,966,336(num_objs) ÷ 32(objperslab) = 2,342,698(num_slabs) ✓

内存占用庞大:
若目标设备页大小为 4KB:

总内存 = num_slabs (2,342,698) × pagesperslab (1) × 4KB ≈ 9.37 GBkmalloc-128 这一个缓存就占用了接近 10 GB 的内存!

在用量巨高
active_objs(74,961,686) × objsize(128b) ≈ 8.94 GiB

占用率极高:

active_objs(74,961,686) / num_objs(74,966,336) = 99.99%
空闲slab object : num_objs(74,966,336) - active_objs(74,961,686) = 4650

活跃对象几乎等于总对象数(差额仅 4,650 个)。这意味着 99.99% 的 128字节内存块都被内核占用着,几乎没有空闲缓存可用。

99.99% 满、空闲对象几乎为零——这说明它不像“cache 中大量空闲 object 尚未归还”这一类问题,更像确实有约 7500 万个 128 字节对象仍被内核持有。但单次快照不能直接等同于泄漏,还需要结合时间序列和调用栈确认。

顺带解读布局列:objperslab=32, pagesperslab=1calculate_order()mm/slub.c:3825)的标准输出——它先取 min_objects = 4×(fls(nr_cpus)+1), 从满足它的最低 order 起找"页内剩余 rem = slab_size % size 最小"的布局; 128B 在 order-0 页上 4096 % 128 = 0 零浪费,直接命中 32 obj/页。 核数很多的大机器上 min_objects 抬高,同一 cache 可能变成 order-1(64 obj/2 页), 所以 objperslab 与机器 CPU 数相关,不是固定值。

对比另一种形态:active_objs 远小于 num_objs(比如 dentry 用了 12000 个 slab 但 active 只有 3 万/23 万)——说明大量 slab 处于空闲/半空状态,更常见原因是负载波动后 SLUB 还没归还、cache 可回收但尚未触发 shrink、per-cpu/node partial 残留或对象布局导致的内部碎片。是否泄漏要看 active_objs 是否持续增长,而不是只看 active_objs / num_objs 一次快照。

Android 现场排查流程

Android 设备上排查 slab 问题时,不建议只截一份 /proc/slabinfo 就下结论。更稳妥的流程是先确认总量,再找 cache,再看趋势,最后抓调用栈。

1. 先从 meminfo 判断 slab 类型

adb root
adb shell "grep -E 'Slab|SReclaimable|SUnreclaim|KReclaimable' /proc/meminfo"

重点看:

  • Slab:内核 slab 总量。

  • SReclaimable / KReclaimable:理论上可回收的 slab,例如部分 inode/dentry 类缓存。

  • SUnreclaim:不可回收 slab,更容易直接压缩可用内存空间,也是 Android 低内存问题中更需要优先关注的部分。

如果 Slab 高但主要落在 SReclaimable,排查方向偏向文件系统缓存、dcache/inode shrinker 是否正常、是否存在负载峰值残留。如果 SUnreclaim 持续增长,则更要怀疑驱动、网络、Binder、GPU、SELinux、kmalloc-* 等不可回收对象。

2. 对 slabinfo 按真实占页排序

adb shell "cat /proc/slabinfo" > slabinfo.txt
adb shell "awk 'NR>2 {bytes=\$15*\$6*4096; printf \"%12s %-32s active=%-12s total=%-12s size=%-8s objslab=%-6s pages=%-4s slabs=%-10s\n\", bytes,\$1,\$2,\$3,\$4,\$5,\$6,\$15}' /proc/slabinfo | sort -nr | head -40"

上面的 4096 只适合 4KB page 设备。若目标平台是 16KB page,需要替换为 16384。真正的判断公式始终是:

bytes = num_slabs * pagesperslab * PAGE_SIZE

3. 做时间序列,而不是只看单点

adb shell "while true; do date; grep -E 'kmalloc-128|dentry|inode_cache|vm_area_struct|binder|kgsl|zspage|zs_handle' /proc/slabinfo; sleep 10; done"

判断方式:

  • num_slabsactive_objs 持续单调增长:高度怀疑泄漏或长期持有。

  • active_objs 增长后稳定,num_objs 高位不降:可能是峰值负载后 cache 未及时回收。

  • active_objs 低但 num_objs 高:优先看可回收性、shrink 行为、per-cpu/node partial 和碎片,而不是直接定性为泄漏。

  • 只有 kmalloc-* 增长:只能说明对象大小,不能说明归属模块,必须抓分配调用栈。

Android 常见需要额外关注的 cache 或相关方向包括:binder/binder_nodekgsl/adrenoion/dma-bufashmemzspage/zs_handleskbuff_*TCP/UDPdentry/inode_cachef2fs/ext4、SELinux 相关 avc_*/lsm_*

深入定位 —— 从 slabinfo 到"谁在分配"

前面我们通过 slabinfo 确认了 kmalloc-128 异常,但它只能告诉我们"哪里"出了问题,无法回答""在分配。这就好比医生测出你发烧了,但不知道是病毒还是细菌感染。

slabinfo 只能提供聚合统计,不记录调用栈信息:

  • 不知道是哪个内核模块在申请

  • 不知道是哪些函数路径在分配

  • 不知道分配/释放的频率模式

所以,我们需要更强的工具来补齐调用栈信息。常用选择如下:

注意: 这里只简单说明一下,关于这些工具如何参与进slab内存泄漏的分析流程中,会在本博客的其他博文中详细说明

1. slub_debug / sysfs trace

如果内核打开了 CONFIG_SLUB_DEBUG,可以通过 bootargs 或 sysfs 针对目标 cache 打开跟踪。以 kmalloc-128 为例,不同内核暴露能力略有差异,现场先检查:

adb shell "ls -l /sys/kernel/slab/kmalloc-128"
adb shell "cat /sys/kernel/slab/kmalloc-128/trace 2>/dev/null"

如果支持 trace,可打开分配跟踪后复现问题,再读取 alloc_traces / free_traces

adb shell "echo 1 > /sys/kernel/slab/kmalloc-128/trace"
adb shell "cat /sys/kernel/slab/kmalloc-128/alloc_traces"
adb shell "cat /sys/kernel/slab/kmalloc-128/free_traces"

注意:slub_debug 对性能和内存都有额外开销,线上用户版本通常不可直接开启,建议在 userdebug/eng 或专项复现版本中使用。

2. ftrace/kmem tracepoint

如果设备允许使用 ftrace,可以抓 kmem:kmallockmem:kfreekmem:kmem_cache_allockmem:kmem_cache_free 等事件,结合调用栈过滤目标大小或目标 cache:

adb shell "echo 0 > /sys/kernel/tracing/tracing_on"
adb shell "echo 1 > /sys/kernel/tracing/events/kmem/kmalloc/enable"
adb shell "echo 1 > /sys/kernel/tracing/options/stacktrace"
adb shell "echo > /sys/kernel/tracing/trace"
adb shell "echo 1 > /sys/kernel/tracing/tracing_on"
# 复现问题
adb shell "echo 0 > /sys/kernel/tracing/tracing_on"
adb shell "cat /sys/kernel/tracing/trace" > kmem_trace.txt

这条路径适合短时间复现。长时间开启会产生大量日志和明显开销。部分 Android 内核仍使用 /sys/kernel/debug/tracing 路径,若 /sys/kernel/tracing 不存在,需要切换到对应 debugfs 路径并确认 debugfs 已挂载。

3. kmemleak / KASAN / vendor debug

如果问题是“分配后永不释放”,CONFIG_DEBUG_KMEMLEAK 可以直接扫描疑似泄漏对象;如果怀疑越界、use-after-free,优先使用 KASAN/KFENCE。Android 厂商内核还可能有私有 debugfs 节点或内存统计,例如 GPU、IPA、camera、display、dma-buf/bufinfo 等,应结合异常 cache 名称反查对应子系统。

/proc/slabinfo 的角色是定位“哪个 cache 异常”,不是直接回答“谁泄漏”。真正闭环至少需要:异常 cache + 时间趋势 + 调用栈 + 复现动作 + 代码释放路径审计。

总结

本文主要就是介绍 /proc/slabinfo 节点的详细介绍,并以一个 kmalloc-128 slab内存泄漏的案例作为案例来确定分析slab内存泄漏的方法!同时我们也简明的说明了一些常见的slab内存泄漏的分析方法。

但是需要注意的是:

不能等问题爆发才去排查。建议建立 slab 内存的常态化监控:

监控项

告警阈值建议

数据来源

Slab 总内存占比

> 系统内存的 30%

/proc/meminfo

kmalloc-128 num_objs 增长率

1 小时内增长 > 100%

/proc/slabinfo(周期性采集)

active_objs 持续增长

持续 10 分钟以上不回落

/proc/slabinfo

active_objs / num_objs

> 95% 且 cache 总量明显异常

/proc/slabinfo

num_slabs 异常增长

同比基线 > 300%

历史基线数据库