你有没有遇到过这种场景:free -g 看内存,used 冲得很高,但按 RSS 把进程排个序,从头翻到尾也找不到哪个进程是真正的"大户";问业务方说一切正常,可机器就是越来越慢,甚至开始 OOM。如果这时候顺手打开 /proc/meminfo,大概率会看到 Slab 那一栏高得离谱,动辄几个 GB,甚至十几个 GB。这就是典型的 slab 占用内存异常,再往下深挖,往往就是一个 slab leak。
这篇文章是写给内核排查和系统优化工程师的,目的很简单:当 Slab 数值异常时,怎么一步步搞清楚到底是哪些 cache 占了大头,哪个模块在分配对象后不释放,以及如何区分"真泄漏"和"假泄漏"。我会把常用工具和实测套路完整过一遍,包括 /proc/slabinfo 的字段拆解、slabtop 的快速定位、slub_debug 的调用点追踪、kmemleak 的扫描适用边界,最后用一个模拟案例把整个排查链路串起来。新手可以按部就班操作,老手也可以看看有没有遗漏的切入角度。
1. 先定位 slab 在内存账本里的角色:三个分配层与两级缓存
1.1 从进程视角看不到的那块内存
Linux 内存可以粗略分成用户态和内核态两部分。用户态内存都有进程页表对应,RSS、VSZ 这些指标能帮你快速找到"吃内存的进程"。内核态的内存则要复杂得多,一部分是内核代码段、数据段、页表本身,另一部分就是 slab 分配器管理的各种小对象。
内核里的小对象无处不在:文件系统路径解析用的 dentry、索引节点 inode、块层的 buffer_head、网络协议栈的 sk_buff、各种驱动的私有数据结构。单个对象往往只有几十到几百字节,看起来微不足道,但数量一上来就很恐怖。一台高并发的文件服务器上 dentry 数量动辄上百万,一个 dentry 对象按 192 字节算,一百万个就是接近 200MB。如果某个驱动对每个 I/O 请求都分配一个私有对象且忘记释放,SUnreclaim 就会像滚雪球一样涨上去。
1.2 slab/slub/slob:名字不同,本质都是小对象的批发市场
Linux 的历史上有三种 slab 相关分配器:传统 slab、嵌入式场景使用的 slob、以及当前主流内核默认的 slub。虽然大家在排查问题时嘴上都说"slab 涨了",但绝大多数现代服务器上实际运行的是 SLUB。
不管叫哪个名字,核心思想都一样:把伙伴系统申请来的物理页拆成多个同等大小的对象,放进对应的 kmem_cache。每种对象类型对应一个 cache,比如 dentry、inode、kmalloc-256。分配和释放都以对象为粒度,对象释放后不是立刻还给伙伴系统,而是留在 cache 的空闲链表里复用。这样做有两个明显好处:一是减少内核用零碎小对象频繁向伙伴系统申请页带来的外部碎片,二是复用对象时不需要重新做初始化,性能更好。
打个比方,伙伴系统是土地批发商,最少一亩一亩卖;而 slab 分配器是精装公寓运营方,把整层楼隔成统一的小房间出租。大量小对象频繁入住和退租,如果每次都去找批发商要整块地,成本完全不可接受。
1.3 为什么 slab 会成为排查重点
/proc/meminfo 里的 Slab 分为两块:SReclaimable 和 SUnreclaim。SReclaimable 代表可回收对象,比如文件缓存相关的 dentry、i
