slub/slab的内存泄漏、越界访问、UAF、效率分析等调试手段和工具(slabinfo/slub_debug/slabtop/kmemleak/kfence/kasan等)总结
1 简单认识slab/slub
1.1 slab/slub是什么?
slab/slub都是对象缓存分配器,目标是高效地分配和释放内核中频繁使用的小尺寸内存对象(如 task_struct, inode, dentry, sk_buff 等)。
基本思想:
- 预分配: 在内核启动或模块加载时,预先从伙伴系统分配一整块较大的内存页。
- 划分: 将这块大内存划分为一个个大小相等的对象。
- 缓存: 这些预分配好的、包含相同大小对象的“池子”称为 cache。
- 分配: 当内核需要分配该大小的对象时,直接从对应的 cache 中获取一个空闲对象,速度极快。
- 释放: 当对象不再需要时,将其标记为空闲并放回 cache 中,供后续分配使用,而不是立即释放回伙伴系统。
- 着色: 通过调整对象在 slab 内的起始偏移量(着色),来优化不同对象在 CPU 硬件缓存(Cache)中的分布,减少缓存冲突(Cache Thrashing),提升访问速度。
关系:
slab: 原始的分配器,由 Sun Microsystems 的 Jeff Bonwick 设计并引入 Linux。结构相对复杂,管理开销稍大,但功能完善(如调试支持强)。
slub: slab 的简化、高性能改进版。由 Christoph Lameter 设计并引入 Linux(大约在 2.6.22 时代)。它简化了管理数据结构,减少了元数据开销,提高了性能,并成为现代 Linux 内核的默认小内存分配器。SLUB 即 The unqueued slab allocator 的缩写。
你可以简单理解为:slub 是 slab 的优化升级版,目标相同(对象缓存),但实现更高效。现在提到 “slab 分配器”,通常泛指包括 SLAB、SLUB(以及更老的 SLOB)在内的这一系列基于对象缓存的分配机制,而 SLUB 是当前标准。
1.2 为什么要使用slab/slub?有哪些优缺点?
为什么要使用slab/slub?(核心优势/动机)
1. 解决伙伴系统低效问题: 伙伴系统 (alloc_pages) 按页(通常是 4KB)分配。频繁分配小对象(几十到几百字节)会导致:
内部碎片: 分配一页只用了其中一小部分,浪费严重。
分配/释放速度慢: 每次都需要操作页表、查找合适的页块,开销大。
2. 提升性能:
快速分配/释放: 对象是预先分配好的,分配和释放操作几乎只是操作空闲链表指针,速度极快。
硬件缓存友好: “着色”机制和将同一 cache 的对象集中存放,能显著提高 CPU 硬件缓存命中率,减少访存延迟。
减少锁竞争: SLUB 等优化了锁机制(如 per-CPU slab),降低了多核并发访问时的锁争用。
3. 减少碎片:
有效管理小对象: 将小对象集中管理在 cache 中,避免了它们在伙伴系统中零散分布导致的外部碎片。
延迟释放: 释放的对象放回 cache 而不是立即还页给伙伴系统,下次需要同样大小的对象可以直接复用,避免了频繁的页分配/释放操作本身可能导致的碎片。
4. 内存使用统计: 方便跟踪特定类型对象的使用情况(通过 /proc/slabinfo)。
5. 构造/析构支持: 可以为特定 cache 注册构造函数 (ctor) 和析构函数 (dtor),在对象分配时初始化其状态,释放时清理资源。
使用slab/slub的优缺点?
优点:
1. 极高的分配/释放速度: 对小对象的操作远快于直接调用伙伴系统。
2. 显著降低内部碎片: 对象大小精确匹配请求(或 cache 对齐的大小)。
3. 有效缓解外部碎片: 通过对象缓存和延迟释放策略。
4. 提升硬件缓存利用率: “着色”和局部性优化。
5. 方便调试和监控: 提供丰富的统计信息和调试选项(如 RedZoning, Poisoning)。
6. 支持对象初始化/清理: 通过构造函数/析构函数。
7. (SLUB 特有)更低的元数据开销: 比 SLAB 更节省内存。
8. (SLUB 特有)更简单的设计: 代码更简洁,潜在 Bug 更少。
9. (SLUB 特有)更好的可扩展性: 在多核系统上性能更好。
缺点:
1. 管理开销: 需要维护 cache 结构、slab 结构、空闲链表等元数据。
2. 潜在浪费:
一个 slab 中可能只有少量对象被使用,但整个 slab 的页都被占用(尤其是大对象或不常用的 cache)。
如果对象大小不是 cache 对齐大小的整数倍,对象内部会有少量填充(内部碎片)。
3. 复杂性: slab/slub 的实现本身是内核中非常复杂的子系统。
4. (SLAB 特有)较高的元数据开销和复杂性: 相比 SLUB。
5. (SLUB 特有)调试支持稍弱(早期): 早期 SLUB 调试功能不如 SLAB 丰富,但现在差距已不大。
1.3 什么API从slab/slub分配内存?
用户态程序员通常不直接操作 slab/slub。内核开发者通过以下常用 API 来利用 slab/slub 机制分配内存:
1. kmalloc / kzalloc: 最常用的通用小内存分配接口。
void *kmalloc(size_t size, gfp_t flags); void *kzalloc(size_t size, gfp_t flags); (分配并清零内存)
底层: 它们会查找或创建与请求 size 最匹配的 slab/slub cache,然后从中分配一个对象。flags 指定分配行为(如 GFP_KERNEL - 可睡眠, GFP_ATOMIC - 原子上下文, __GFP_ZERO - 清零等)。
2. kmem_cache_alloc / kmem_cache_zalloc: 用于分配特定类型的对象。需要先创建一个专用的 cache。
void *kmem_cache_alloc(struct kmem_cache *cachep, gfp_t flags); void *kmem_cache_zalloc(struct kmem_cache *cachep, gfp_t flags);
底层: 直接从指定的 cachep 中分配一个对象。性能通常比 kmalloc 更好(省去了查找匹配 cache 的开销),尤其在高频分配场景。
3. 专用 Cache 的创建与管理: 为了使用 kmem_cache_alloc,需要先创建专用的 cache。
struct kmem_cache *kmem_cache_create(const char *name, size_t size, size_t align, slab_flags_t flags, void (*ctor)(void *));
name: Cache 名称(出现在 /proc/slabinfo)。
size: 缓存中每个对象的大小。
align: 对象对齐要求。
flags: 创建标志(如 SLAB_HWCACHE_ALIGN 要求硬件缓存行对齐)。
ctor: 对象的构造函数(可选)。
void kmem_cache_destroy(struct kmem_cache *cachep); (销毁不再需要的 cache)
4. alloc_percpu / __alloc_percpu: 分配每个 CPU 的变量。
void __percpu *__alloc_percpu(size_t size, size_t align); void __percpu *alloc_percpu(type); (类型安全版本)
底层: 为每个 CPU 核心分配一个指定大小的内存块,这些内存块通常也是通过 slab/slub 机制管理的。访问时使用 this_cpu_ptr() 等宏获取当前 CPU 上的变量指针。
kmalloc/kzalloc:通用小内存分配,内核自动选择合适大小的 cache。
kmem_cache_alloc/kmem_cache_zalloc:分配特定类型的对象(需预先创建专用 cache),性能最优。
alloc_percpu/__alloc_percpu:分配每个 CPU 变量。
所有这些 API 的底层实现都依赖于当前内核配置使用的 slab 分配器(通常是 SLUB)来高效地管理对象缓存。
2 slab/slub的调试手段
下面是不同调试手段对常见slab/slub问题(内存泄漏、内存越界、释放后使用、使用量统计)等的支持情况:
| 手段/方法 | 内存泄漏 (Leak) | 内存越界 (OOB) | 释放后使用 (UAF) | 使用量统计 (Usage) | 关键特性/启用方式 | 性能开销 | 典型场景/备注 |
|---|---|---|---|---|---|---|---|
/proc/slabinfo |
△ (间接) | × | × | ● | 基础状态,直接 cat 查看 |
极低 | 快速查看各缓存对象数、slab数、大小等,识别占用高或活跃对象比例异常的缓存。间接判断泄漏(高active_objs比例)。 |
slabtop |
△ (间接) | × | × | ● | 动态排序查看 /proc/slabinfo (slabtop) |
低 | 实时监控,快速识别增长最快或占用最高的缓存。 |
/sys/kernel/slab/ |
△ (需配置) | △ (需配置) | △ (需配置) | ● | 更详细统计、控制接口。需配置 slub_debug 或内核选项开启调试功能。 |
低~高 | 查看详细统计(alloc_calls等),启用/控制调试功能(trace, validate, store_user, red_zone)的核心接口。 |
vmstat -m |
△ (间接) | × | × | ● | 简洁版 slabinfo (vmstat -m) |
极低 | 快速概览。 |
slub_debug= |
● (U/T) | ● (Z) | ● (P/F) | ● | 内核启动参数。选项:F(基本错误), Z(Redzone), U(栈追踪), P(Poison), T(跟踪), A(所有)。可指定缓存。 |
高 | 定位泄漏(U/T看alloc_traces)、越界(Z)、UAF(P/F)的黄金手段。 需重启。对性能影响大,主要用于调试环境。 |
kmemleak |
● | × | × | △ | 内核配置+启动参数 (CONFIG_DEBUG_KMEMLEAK=y, kmemleak=on)。扫描查找孤立指针。 |
高 | 系统级内核内存泄漏检测。 报告包含分配栈。可能有假阳性。需定期scan。性能开销大。 |
KASAN |
△ (间接) | ● | ● | × | 内核配置 (CONFIG_KASAN=y)。动态检测内存错误。 |
很高 | 定位越界(OOB)、UAF、重复释放等内存损坏的首选。 报告极其详细精确。性能开销极大,仅限开发/测试内核。 |
KFENCE |
× | ● | ● | × | 内核配置 (CONFIG_KFENCE=y)。低开销抽样检测内存错误。 |
低 | 生产/测试环境长期运行,捕获OOB/UAF。 报告类似KASAN。概率性捕获(抽样)。性能开销小,可在线使用。 |
ftrace/trace-cmd |
△ (事件统计) | × | × | ● | 内核事件追踪 (kmem:kmalloc, kmem:kfree, kmem:kmem_cache_* 等)。需挂载 debugfs。 |
中~高 | 统计分配/释放频率,追踪调用路径(热点分析),结合过滤条件追踪特定行为。性能开销取决于追踪粒度和事件量。 |
perf |
△ (事件统计) | × | × | ● | 采样分析工具 (perf record/report -e kmem:*)。 |
中 | 性能剖析,识别 slab 分配/释放热点路径(-g生成调用图)。分析内存访问模式(perf mem)。 |
systemtap/bpftrace |
● (可定制) | ● (可定制) | ● (可定制) | ● | 动态追踪框架。编写脚本挂钩 slab 分配/释放函数及内部。 | 低~高 | 最灵活强大! 可定制泄漏检测(关联分配/释放)、对象追踪、越界/UAF检测(需结合逻辑)、详细统计。需要编程能力。 |
slabinfo工具 |
△ (间接) | × | × | ● | 命令行工具 (通常包含在 linux-tools 包中)。 |
低 | 比 /proc/slabinfo 更深入的分析,计算碎片率、真实开销(slabinfo -X)。 |
-
● : 原生支持或直接有效的主要手段。
-
○ : 部分支持、特定条件下支持或作为辅助手段。
-
△ : 间接支持、需要配置或结合其他方法、或者能提供相关线索但不是主要手段。
-
× : 不直接支持或不是该工具的设计目标。
-
U/T: slub_debug的U(Store User) 或T(Tracking) 选项用于泄漏检测。
-
Z: slub_debug的Z(Red Zoning) 选项用于越界检测。
-
P/F: slub_debug的P(Poisoning) 或F(Failures) 选项用于 UAF 检测。
2.1 /proc/slabinfo
slabinfo 输出列解析:
- name:Slab 缓存名称(如 kmalloc-8k 表示分配 ~8KB 对象的缓存)。
- <active_objs>:当前活跃(已分配)的对象数量。
- <num_objs>:缓存中总对象数(活跃 + 空闲)。
- <objsize>:每个对象实际占用内存大小(字节)。
- <objperslab>:单个 Slab 页块可容纳的对象数量。
- <pagesperslab>:组成一个 Slab 页块所需的物理内存页数。
- Slab 缓存调优参数:tunables <limit> <batchcount> <sharedfactor>
- <limit>:每个 CPU 本地缓存的最大空闲对象数(超出则返还全局池)
- <batchcount>:单次从全局池转移的对象数(控制缓存填充/回收粒度)
- <sharedfactor>:NUMA 节点间共享缓存的强度系数(现代内核通常为 0)
注:全 0 表示使用内核默认值
- Slab 页块统计:slabdata <active_slabs> <num_slabs> <sharedavail>
- <active_slabs>:当前非空的 Slab 页块数量
- <num_slabs>:总 Slab 页块数(活跃 + 空闲)
- <sharedavail>:NUMA 中跨节点共享的空闲对象数(通常为 0)
关键关系速查
- 内存效率 = (<objsize> × <num_objs>) / (<pagesperslab> × <num_slabs> × 页大小)。
- 碎片监控:<active_objs>/<num_objs> 越低,空闲对象越多(潜在浪费)。
- 调优参考:若 slabdata 中空闲页块过多,可考虑减小 limit 释放内存。
slabinfo - version: 2.1 # name <active_objs> <num_objs> <objsize> <objperslab> <pagesperslab> : tunables <limit> <batchcount> <sharedfactor> : slabdata <active_slabs> <num_slabs> <sharedavail> kmalloc-8k 13 14 24576 1 8 : tunables 0 0 0 : slabdata 14 14 0 kmalloc-4k 13 14 12288 2 8 : tunables 0 0 0 : slabdata 7 7 0 kmalloc-2k 58 65 6144 5 8 : tunables 0 0 0 : slabdata 13 13 0 kmalloc-1k 739 740 3072 10 8 : tunables 0 0 0 : slabdata 74 74 0 kmalloc-512 176 190 1536 10 4 : tunables 0 0 0 : slabdata 19 19 0 kmalloc-256 794 800 768 10 2 : tunables 0 0 0 : slabdata 80 80 0 kmalloc-192 153 156 296 13 1 : tunables 0 0 0 : slabdata 12 12 0 kmalloc-128 187 190 384 10 1 : tunables 0 0 0 : slabdata 19 19 0 kmalloc-96 1004 1020 200 20 1 : tunables 0 0 0 : slabdata 51 51 0 kmalloc-64 777 784 256 16 1 : tunables 0 0 0 : slabdata 49 49 0 kmalloc-32 658 675 160 25 1 : tunables 0 0 0 : slabdata 27 27 0 kmalloc-16 582 608 128 32 1 : tunables 0 0 0 : slabdata 19 19 0 kmalloc-8 2881 2916 112 36 1 : tunables 0 0 0 : slabdata 81 81 0 kmem_cache_node 185 192 256 16 1 : tunables 0 0 0 : slabdata 12 12 0 kmem_cache 185 190 384 10 1 : tunables 0 0 0 : slabdata 19 19 0
2.2 slabinfo
slabinfo源码位于kernel的tools/mm/slabinfo.c。slabinfo -h查看使用方法,slabinfo默认的输出如下:
- Name:缓存的名称(例如 anon_vma、bdev_cache)。
- Objects:当前已分配的对象数量(例如 anon_vma 缓存中有 131 个对象)。
- Objsize:每个对象的实际大小(字节)(例如 anon_vma 的对象大小为 96 字节)。
- Space:该缓存占用的总内存空间(包括对象和元数据),例如 anon_vma 占用 28.6KB。
- Slabs/Part/Cpu:
- Slabs:总 slab 数量(内存块)。
- Part:部分分配的 slab 数量(非满/非空)。
- Cpu:每 CPU 本地缓存中的对象数量(通常为 0,表示未启用或已全局化)。
(例如 7/3/0 表示 7 个 slab、3 个部分分配、0 个每 CPU 对象)
- O/S:每个 slab 包含的对象数量(例如 anon_vma 的每个 slab 有 24 个对象)。
- O:Slab 的阶(Order),表示 slab 大小 = \(2^{\text{order}}\) 页(例如 0 表示 1 页 = 4KB)。
- %Fr:空闲对象的百分比(基于部分分配的 slab 计算的平均值)。
- %Ef:空间利用率(已分配对象占用的空间 / 缓存总空间)。
- Flg:缓存标志(例如 U=可回收,A=对齐,a=远程对齐)。这个Flg的内容来自于tools/mm/slabinfo.c的分析:
标志 字段 含义 * aliases别名缓存:该缓存被其他缓存共享(例如 dentry缓存共享)d cache_dmaDMA 内存:从 DMA 区域分配内存(供外设直接访问) A hwcache_align硬件缓存对齐:对象对齐到 CPU 缓存行(避免伪共享) P poison毒化标记:释放后用特殊字节( POISON_BYTE)填充对象,检测 UAF 错误a rec1aim_account可回收账户:内存紧张时可回收,且计入 cgroup 内存统计(拼写错误应为 reclaim_account)Z red_zone红区保护:对象前后插入保护页,检测缓冲区溢出 F santiy_checks完整性检查:启用额外内存验证(拼写错误应为 sanitize_checks)U store_user存储分配者:记录分配该对象的调用者信息(用于调试内存泄漏) T trace分配追踪:记录分配/释放的调用栈(需内核配置支持)
实例输出如下:
slabinfo Name Objects Objsize Space Slabs/Part/Cpu O/S O %Fr %Ef Flg anon_vma 131 96 28.6K 7/3/0 24 0 42 43 U anon_vma_chain 179 64 28.6K 7/4/0 32 0 57 39 U bdev_cache 2 1544 32.7K 1/1/0 19 3 100 9 AaU bio-112 2 112 4.0K 1/1/0 21 0 100 5 AU bio-176 2 176 16.3K 4/4/0 16 0 100 2 AU bio-240 36 240 12.2K 3/0/0 12 0 0 70 AU bio_post_read_ctx 128 48 16.3K 4/1/0 36 0 25 37 aU biovec-16 0 256 4.0K 1/1/0 12 0 100 0 AU biovec-64 0 1024 16.3K 1/1/0 15 2 100 0 AU
slabinfo -t显示slab对象的分配/释放跟踪信息,需要打开CONFIG_SLUB_DEBUG和CONFIG_DEBUG_FS;并且需要在/sys/kernel/debug/slab挂载(mount -t debugfs debugfs /sys/kernel/debug)。参考2.6 /sys/kernel/debug/slab。
实例如下:
slabinfo -t anon_vma: Kernel object allocation ----------------------------------------------------------------------- 39 __anon_vma_prepare+0x118/0x1cc age=209/1523151/1605713 pid=1-148--39表示调用路径发生的次数;age表示对象存活最小/平均/最大的jiffies数;pid表示触发操作的进程ID范围。 __anon_vma_prepare+0x118/0x1cc--下面是调用路径的栈。... el0t_64_sync_handler+0xb4/0x12c el0t_64_sync+0x190/0x194 ... anon_vma: Kernel object freeing ------------------------------------------------------------------------ 54 __put_anon_vma+0x34/0xd4 age=212/1456932/1605645 pid=50-148 kmem_cache_free+0x330/0x3b8... do_el0_svc+0x1c/0x28 el0_svc+0x40/0xe4 ...
slabinfo -T查看对slab的总结:
Slabcache Totals ---------------- Slabcaches : 185 Aliases : 0->0 Active: 98 -- 缓存总数:185 别名数:0 活跃缓存数:98 Memory used: 20.7M # Loss : 6.3M MRatio: 44% -- 总内存占用:20.7MB 内存浪费:6.3MB 浪费率:44% # Objects : 55.9K # PartObj: 1.5K ORatio: 2% -- 对象总数:55.9K 部分使用对象数:1.5K 占比:2% Per Cache Average Min Max Total ---------------------------------------------------------------------------- #Objects 570 1 20.2K 55.9K -- 对象数: 平均570/最小1/最大20.2K/总计55.9K #Slabs 34 1 1.0K 3.3K -- Slab块数: 平均34/最小1/最大1K/总计3.3K #PartSlab 1 0 27 171 -- 部分使用Slab数: 平均1/最小0/最大27/总计171 %PartSlab 58% 0% 100% 5% -- 部分Slab占比: 平均58%/最小0%/最大100%/总计5% PartObjs 2 0 259 1.5K -- 部分使用对象数: 平均2/最小0/最大259/总计1.5K % PartObj 54% 0% 100% 2% -- 部分对象占比: 平均54%/最小0%/最大100%/总计2% Memory 211.4K 4.0K 6.0M 20.7M -- 内存占用: 平均211KB/最小4KB/最大6MB/总计20.7MB Used 146.7K 16 5.4M 14.3M -- 已用内存: 平均146KB/最小16B/最大5.4MB/总计14.3MB Loss 64.6K 1.0K 1.5M 6.3M -- 内存浪费: 平均64KB/最小1KB/最大1.5MB/总计6.3MB Per Object Average Min Max ----------------------------------------------------------- Memory 351 80 16.3K -- 对象内存: 平均351B/最小80B/最大16.3KB User 257 8 8.1K -- 有效使用: 平均257B/最小8B/最大8.1KB Loss 94 64 8.1K -- 内存浪费: 平均94B/最小64B/最大8.1KB
对上面的总结信息,可以进行如下分析:
1. 内存浪费严重:总体浪费率达44%(6.3MB/20.7MB),主要来自:
- 对象对齐填充(平均每对象浪费94B)
- Slab内部碎片(部分使用Slab占比达58%)
2. 超大缓存差异:(这个不一定是问题)
- 最大缓存占用6MB(vs 最小4KB)
- 最大对象16.3KB(vs 最小80B)
- 表明系统存在极端大小不一的内核对象
3. 使用效率警示:
- 部分Slab利用率100%(应优先优化)
- 最小对象仅使用8B有效空间(但占用80B内存,浪费90%)
2.3 slabtop
slabtop 是一个强大的 Linux 命令行工具,用于实时显示内核 slab 缓存的使用情况。专注于内核如何管理内存中用于特定对象类型(数据结构)的slab。
2.3.1 作用
1. 监控内核内存使用: 显示内核为各种数据结构(如进程描述符 task_struct、目录项 dentry、索引节点 inode、网络套接字、缓冲区头等)分配了多少内存。
2. 识别内存消耗大户: 快速找出哪些内核对象类型(slab 缓存)占用了最多的内存。
3. 排查内核内存泄漏: 如果某个特定 slab 缓存的活跃对象数量 (ACTIVE) 或缓存大小 (CACHE SIZE) 在系统负载稳定时持续异常增长,这可能表明存在内核内存泄漏(例如某个内核模块有 bug)。
4. 理解内核内存行为: 帮助管理员和开发者了解内核内部的内存管理机制和不同子系统对内存的消耗情况。
5. 性能分析: 在系统内存不足或出现性能问题时,slabtop 可以帮助分析是否是内核 slab 缓存消耗过多内存所致(例如 dentry 或 inode_cache 过大)。
2.3.2 使用方法
1. 基本命令:
slabtop
这会以交互模式启动 slabtop,类似于 top。它会持续刷新显示(默认每秒一次)。
2. 常用选项:
- -d : 设置刷新间隔(秒)。例如 slabtop -d 5 每 5 秒刷新一次。
- -o: 只显示一次。
- -s : 启动时按指定列排序。c 按缓存大小(默认), a 按活动对象数, b 按每个对象大小, l 按缓存中的 slab 数, v 按活动 slab 数, n 按名称, o 按对象数, p 按每 slab 页数, u 按缓存利用率。例如 slabtop -s a 启动时按活跃对象数排序。
- -o: 运行一次(非交互模式),显示当前快照并退出。常用于脚本中。例如 slabtop -o > slab_snapshot.txt。
- -V: 显示版本信息。
3. 交互模式下的快捷键 (运行时按):
- a: 切换显示活动/所有对象 (Alloc/Total)。
- b: 按照OBJ/SLAB排序。
- c: 按缓存大小排序 (Cache Size)。
- l: 按每个缓存中的 slab 数量排序 (Slabs)。
- v: 按每个缓存中的活动 slab 数量排序 (Active Slabs)。
- n: 按名称排序 (Name)。
- o: 按对象数量排序 (Objects)。
- p: 按每个 slab 占用的页数排序 (Pages/Slab)。
- s: 按对象大小排序 (Obj Size)。
- u: 按缓存利用率排序 (Use)。
- <空格>: 强制刷新显示。
- q: 退出 slabtop。
2.3.3 结果解读
slabtop 的输出通常包含以下列(具体列名和顺序可能略有差异):
1. Active / Total Objects (% used):
- ACTIVE: 当前正在被内核使用的对象数量。
- TOTAL: 该 slab 缓存中分配的对象总数(包括空闲的)。
- %USED: (ACTIVE / TOTAL) * 100%,表示该缓存中对象的活跃利用率。接近 100% 表示几乎没有空闲对象,可能分配压力大;较低表示有较多空闲对象缓存。
2. Active / Total Slabs (% used):
- ACTIVE: 当前包含至少一个活动对象的 slab 数量。
- TOTAL: 分配给该缓存的 slab 总数。
- %USED: (ACTIVE / TOTAL) * 100%,表示 slab 的活跃利用率。低利用率可能意味着内存有浪费(整页 slab 只用了少量对象)。
3. Cache / Slab Size:
- CACHE SIZE: 该 slab 缓存占用的总内存大小 (KB 或 MB)。
- SLAB SIZE: 单个 slab 的大小 (KB)。一个 slab 通常占用一页或多页物理内存。
4. Object Size / Objects per Slab:
- OBJ SIZE: 该缓存中存储的单个对象的大小 (字节)。
- OBJ/SLAB: 一个 slab 中可以容纳的对象数量。
5. Pages per Slab:
- PAGES/SLAB: 构成一个 slab 所需的内存页数。
6. Name:
- NAME: slab 缓存的名称。这是解读结果最关键的部分!名称通常对应内核内部的数据结构或功能模块。常见例子:
- dentry: 目录项缓存 (Directory Entry Cache)。通常很大,用于加速文件路径查找。
- * *_cache: 如 inode_cache (索引节点缓存), buffer_head (缓冲区头缓存), ext4_inode_cache (Ext4 文件系统 inode 缓存), kmalloc-* (通用内存分配的缓存,* 代表分配大小,如 kmalloc-4k, kmalloc-8k)。
- task_struct: 进程描述符缓存。
- mm_struct: 内存管理结构缓存。
- vm_area_struct: 虚拟内存区域结构缓存。
- files_cache: 打开文件表缓存。
- sock_inode_cache: 套接字 inode 缓存。
- names_cache: 文件名缓存。
- (module_name): 如果缓存是由内核模块创建的,名称可能包含模块名。
2.3.4 解读要点与常见场景
1. 排序是关键: 启动时或运行时按 c (缓存大小) 或 o (对象总数) 排序,能快速定位消耗内存最多的 slab 缓存。
2. 关注 NAME 和 CACHE SIZE: 找出哪些缓存占用了大量内存 (CACHE SIZE 很大)。
3. 结合 ACTIVE/TOTAL 和 %USED:
如果某个缓存的 TOTAL 和 CACHE SIZE 非常大,但 ACTIVE 和 %USED 很低(例如长期只有 10-20%),这 可能 表示内核缓存了过多空闲对象,存在内存浪费(但内核有时会主动缓存以提升性能)。这种情况有时可以通过 /proc/sys/vm/drop_caches 手动回收部分未使用的缓存(需谨慎,主要用于测试)。
如果 %USED 持续接近 100%,并且 TOTAL 和 CACHE SIZE 还在不断增长,而系统负载相对稳定,这 强烈提示可能存在内核内存泄漏(尤其当增长的是某个特定驱动或模块相关的缓存时)。
4. 理解常见大缓存:
dentry 和 inode_cache 通常都是最大的缓存之一,这是文件系统性能优化的正常结果。它们在文件访问频繁时增长,内存压力大时内核会自动回收。
kmalloc-* 缓存(特别是较大的如 kmalloc-4k, kmalloc-8k) 也可能占用可观内存,它们用于通用内核内存分配。
5. 寻找异常增长: 在系统运行过程中(特别是执行特定操作后),观察是否有特定 slab 缓存的 CACHE SIZE、TOTAL、ACTIVE 异常地、持续地增长,且不回落。这是内存泄漏的典型信号。
6. 内核模块关联: 如果发现一个占用大的缓存名称与某个内核模块相关(比如 my_driver_cache),而你不确定这个模块的作用,或者这个缓存在不需要该模块功能时也很大/增长,需要调查该模块是否有问题。
2.3.5 总结
slabtop 是深入洞察 Linux 内核内存管理细节的必备工具。通过查看其输出,特别是关注按大小排序的缓存列表及其活跃对象比例,你可以:
1. 了解 内核内存是如何被各种内部对象消耗的。
2. 识别 哪些内核数据结构是内存消耗的主要来源(通常是正常的缓存行为)。
3. 诊断 潜在的内核级内存泄漏问题(异常、持续增长的低利用率缓存或高增长的高利用率缓存)。
4. 辅助 系统性能调优和故障排查。
使用时要结合具体场景分析,理解常见大缓存(如 dentry, inode_cache)通常是正常的性能优化机制,而关注那些不寻常的增长模式才是诊断问题的关键。
2.4 vmstat -m
vmstat -m 命令在 Linux 系统中用于显示内核 slab 分配器 的统计信息。
- Cache: Slab 缓存的名称。这通常对应一个内核数据结构(例如 dentry, inode_cache, vm_area_struct, ext4_inode_cache, tcp_sock 等)或者一个通用缓存(如 size-64, size-128, kmalloc-512 等)。名称直观地告诉你这个 slab 缓存是为哪种对象分配的。
- Num: 当前 该 slab 缓存中 已分配(正在使用)的对象数量。
- Total: 该 slab 缓存 总共 包含的对象数量(包括已分配的和空闲的)。
- Size: 该 slab 缓存中 单个对象 的大小(以字节为单位)。
- Pages: 这里对应的应该是<objperslab>,表示一个slab里面包含多少了对象。跟Pages关联并不大。
Cache Num Total Size Pages jbd2_transaction_s 2 10 384 10 kernfs_iattrs_cache 0 0 176 23 kernfs_node_cache 20209 20434 240 17 khugepaged_mm_slot 0 0 136 30 kioctx 0 0 768 10 kmalloc-128 187 190 384 10 kmalloc-16 582 608 128 32 kmalloc-192 153 156 296 13 kmalloc-1k 739 740 3072 10 kmalloc-256 794 800 768 10 kmalloc-2k 57 65 6144 5 kmalloc-32 657 675 160 25 kmalloc-4k 13 14 12288 2 kmalloc-512 176 180 1536 10 kmalloc-64 776 784 256 16 kmalloc-8 2881 2916 112 36 kmalloc-8k 13 14 24576 1 kmalloc-96 1004 1020 200 20 kmalloc-cg-128 15 20 384 10 kmalloc-cg-16 11 32 128 32 kmalloc-cg-192 41 52 296 13
2.5 /sys/kernel/slab/
/sys/kernel/slab是由slab_sysfs_init()创建的目录,里面存放每个slab的详细信息:
slab_sysfs_init
->kset_create_and_add--创建/sys/kernel/slab目录。
->sysfs_slab_add--为每个slab_caches链表上的slab创建目录以及属性。
->kobject_init_and_add--根据name创建目录。
->sysfs_create_group--在slab目录下创建slab_attr_group定义的一系列属性值。
->sysfs_slab_alias
->sysfs_slab_alias
下面以kmalloc-4k这个slab为例解释:
/sys/kernel/slab/kmalloc-4k/ |--aliases--显示此slab缓存的其他命名标识(用于关联相同底层缓存的不同名称)。 |--align--对象的内存对齐字节数(确保对象按特定边界对齐)。 |--allocfastpath--通过快速路径(无锁操作)成功分配对象的次数统计。 |--alloc_from_partial--从partialslab(部分空闲的slab)分配对象的次数统计。 |--alloc_node_mismatch--在错误的NUMA节点尝试分配对象的失败次数统计。 |--alloc_refill--重新填充per-CPU缓存池的次數统计。 |--alloc_slab--为缓存分配新slab(一组连续内存页)的次數统计。 |--alloc_slowpath--通过慢速路径(需获取锁)分配对象的次数统计。 |--cache_dma--标识是否使用DMA内存区域(1=启用,0=禁用)。 |--cmpxchq_double_cpu_fail--per-CPU缓存上的双字原子操作失败次数统计。 |--cmpxchq_double_fail--全局双字原子操作失败总次数统计。 |--cpu_partial--每个CPU的partialslab列表中允许的最大对象数量。 |--cpu_partial_alloc--从CPUpartial列表分配对象的次数统计。 |--cpu_partial_drain--因列表超限而释放CPUpartial对象的次数统计。 |--cpu_partial_free--释放对象到CPUpartial列表的次数统计。 |--cpu_partial_node--每个CPUpartial列表中来自同一NUMA节点的对象数量。 |--cpu_slabs--每个CPU管理的slab数量(只读统计)。 |--cpuslab_flush--刷新per-CPUslab到全局列表的次数统计。 |--ctor--构造函数指针(若启用,在对象分配时自动调用)。 |--deactivate_bypass--绕过deactivate队列直接释放对象的次数统计。 |--deactivate_empty--将空slab移出CPU缓存的次数统计。 |--deactivate_full--将满slab移出CPU缓存的次数统计。 |--deactivate_remote_frees--远程CPU释放导致本地slab移出缓存的次数统计。 |--deactivate_to_head--将slab放回partial列表头部的次数统计。 |--deactivate_to_tail--将slab放回partial列表尾部的次数统计。 |--destroy_by_rcu--标识是否通过RCU机制延迟销毁slab(1=启用)。 |--free_add_partial--释放对象时将其所在slab加入partial列表的次数统计。 |--free_fastpath--通过快速路径释放对象的次数统计。 |--free_frozen--尝试释放对象时发现slab被冻结的次数统计。 |--free_remove_partial--因slab变空而从partial列表移除slab的次数统计。 |--free_slab--释放slab内存页的次数统计。 |--free_slowpath--通过慢速路径释放对象的次数统计。 |--hwcache_align--标识是否与硬件缓存行对齐(1=启用,减少伪共享)。 |--min_partial--每个节点需保留的最小partialslab数量(低于此值不回收)。 |--object_size--对象实际大小(含元数据和填充字节)。 |--objects--每个slab中的对象总数(包含已分配和空闲对象)。 |--objects_partial--partialslab中的对象数量(只读统计)。 |--objs_per_slab--每个slab容纳的对象数量(与objects相同)。 |--order--分配单个slab所需的连续内存页数(以2^order表示)。 |--order_fallback--因内存不足降级分配slab的次数统计。 |--partial--节点上partialslab(部分空闲slab)的总数。 |--poison--标识是否启用内存填充模式(用于检测内存越界,1=启用)。 |--reclaim_account--内存回收相关计数(具体实现依赖,通常用于调试)。 |--red_zone--标识是否在对象边界插入红色警戒区(用于检测溢出,1=启用)。 |--sanity_checks--标识是否启用完整性检查(如对象验证,1=启用)。 |--shrink--手动触发slab收缩的接口(写入1可释放空闲slab)。 |--slab_size--单个slab占用的总内存字节数(包括元数据)。 |--slabs--节点上所有slab的总数(包含partial/full/empty)。 |--slabs_cpu_partial--每个CPUpartial列表中的slab数量统计。 |--store_user--标识是否记录分配者信息(调试用,1=启用)。 |--total_objects--缓存中所有对象的总数(包含已分配和空闲)。 |--trace--标识是否启用分配跟踪(调试用,1=启用)。 |--validate--手动触发slab验证的接口(读取时执行完整性检查)。
核心分类说明:
1.性能统计(如alloc_fastpath,free_slowpath)——记录分配/释放操作的路径频率
2.内存布局(如align,hwcache_align,slab_size)——控制对象在内存中的排列方式
3.调试防护(如poison,red_zone,store_user)——检测内存越界/重复释放等问题
4.资源管理(如shrink,min_partial,cpu_partial)——调节slab内存占用与回收策略
5.NUMA优化(如alloc_node_mismatch,cpu_partial_node)——统计和优化跨节点访问性能
2.6 /sys/kernel/debug/slab
需要打开如下配置:
Kernel hacking ->Generic Kernel Debugging Instruments ->Debug Filesystem ->Memory Debugging ->Enable SLUB debugging support ->SLUB debugging on by default
通过mount -t debugfs debugfs /sys/kernel/debug挂载debugfs,每次创建slab都会在/sys/kernel/debug/slab/下面创建对应slab的alloc_traces/free_traces。
slab_debugfs_init
->debugfs_create_dir--在/sys/kernel/debug下创建slab目录。
->debugfs_slab_add--遍历slab_caches列表,为每个slab创建对应name的目录。
->debugfs_create_file--分别创建alloc_traces/free_traces节点。
2.7 slub_debug
slub_debug 是 Linux 内核中用于调试 SLUB 内存分配器的命令行配置选项。它通过在内核对象周围添加保护性结构(如 Red Zones、Poisoning 等)来检测内存错误。以下是详细说明:
2.7.1 作用与检测能力
slub_debug 主要用于检测以下内存问题:
1. 越界访问(Out-of-Bounds)
- 在对象边界外读写(通过 Red Zones 检测)
2. 释放后使用(Use-After-Free, UAF)
- 访问已被释放的内存(通过 Poisoning 检测)
3. 未初始化使用
- 使用未初始化的内存区域
4. 双重释放(Double Free)
- 多次释放同一内存地址
5. 内存泄漏跟踪
- 记录分配/释放的调用栈(需结合 CONFIG_STACKTRACE)
2.7.2 设置方法
1. 内核启动参数(全局启用)
bootargs="slub_debug=FZP,<slab_name>"
命令行启动slub_debug按照如下格式:
- 开始指定slub的Debug-Options。
- “,”后添加<slab_name>表示仅适用于特定slab,可以添加多个<slab_name>。中间不允许空格。
- 当需要添加多个规则,使用“;”区隔。
slub_debug=<Debug-Options> Enable options for all slabs slub_debug=<Debug-Options>,<slab name1>,<slab name2>,... Enable options only for select slabs (no spaces after a comma)
参数说明:
- F(Sanity Checks/SLAB_DEBUG_CONSISTENCY_CHECKS):定位内部数据结构不一致性问题(如freelist损坏)。解决SLAB遗留问题的核心检查。
- Z(Redzoning):定位缓冲区溢出(overrun)和下溢(underrun)问题。通过在对象前后插入特殊标记(红色区域),检测写入越界。
- P(Poisoning):定位:
*使用已释放内存(Use-after-free):释放时用特定模式填充对象,访问时若发现该模式则表明内存已被释放。
*使用未初始化内存:分配时用特定模式填充对象,有助于识别未正确初始化的读取。
*内存泄漏间接证据:特定填充模式有助于识别未被正确释放或覆盖的对象内容。 - U(User tracking):定位内存泄漏和特定调用路径的内存分配/释放行为。记录分配和释放该内存的调用栈信息。
- T(Trace):详细追踪单个slab内所有对象的分配和释放历史。用于深度诊断极其复杂的slab相关问题(需谨慎使用,开销大)。
- A(fail slab filter):用于测试内核代码对内存分配失败(kmallocfailure)的处理路径是否健壮。可以模拟分配失败。
- O(Switch debugging offf or high-order caches):解决因开启调试导致最小slab阶数增加的问题(可能浪费内存)。选择性关闭特定缓存的调试以恢复较小slab分配。
- -(Switch all debugging off):完全关闭所有SLUB调试功能。主要用于在默认开启调试(CONFIG_SLUB_DEBUG_ON)的内核中临时关闭调试以评估性能影响或排除调试本身干扰。
- <slab_name>:指定要调试的 slab 缓存(如 kmalloc-*, dentry, inode_cache),或 , 对所有 slab 生效。
不同选项和/sys/kernel/slab/<slab name>下的属性节点对应关系如下:
| DebugOption | slab sysfs |
| F | sanity_checks |
| Z | red_zone |
| P | posion |
| U | store_user |
| T | trace |
| A | failslab |
常见slub_debug配置:
#全局开启Sanity checks和Red zoning。 slub_debug=FZ #仅对dentry缓存开启基本调试。 slub_debug=,dentry #仅对仅对kmalloc-*和dentry缓存开Poison填充(查释放后使用)。 slub_debug=P,kmalloc-*,dentry #仅对dentry缓存开启Sanity check。 slub_debug=F,dentry #全局开启全面对象检查(开销最大,查深层错误)。 slub_debug=O #对dentry开Red zoning检查(查溢出);对kmalloc-*开User tracking(查泄漏)。 slub_debug=Z,dentry;U,kmalloc-* #全局开释放Sanity check和Red zoning检查,但排除zs_handle和zspage缓存。 slub_debug=FZ;-,zs_handle,zspage
关于slub_debug定位问题实例参考《Linux内存管理 (22)内存检测技术(slub_debug/kmemleak/kasan) 》。
2.8 kmemleak
作用: 专门检测内核内存泄漏的工具。它跟踪所有通过 kmalloc(), vmalloc(), kmem_cache_alloc() 等分配的内存块,并扫描内存寻找指向这些内存块的“孤立”指针(即没有已知的全局或栈指针能访问到的内存块)。
启用: 内核配置 CONFIG_DEBUG_KMEMLEAK=y,启动参数添加 kmemleak=on。
使用:
echo scan > /sys/kernel/debug/kmemleak:手动触发内存扫描。 cat /sys/kernel/debug/kmemleak:查看检测到的可能泄漏点报告,包含泄漏内存的大小、分配时的调用栈。 echo clear > /sys/kernel/debug/kmemleak:清除当前报告(用于重新开始检测)。
定位用途: 系统性地检测内核中各种类型的内存泄漏(包括 slab 分配器分配的)。报告直接指向分配点,非常有用。⚠️ 注意:它可能有假阳性(False Positive),需要人工判断。
参考《Linux内存管理 (22)内存检测技术(slub_debug/kmemleak/kasan) 》。
2.9 kasan
作用: 动态内存错误检测器,主要用于检测越界访问(堆、栈、全局变量)、释放后使用、重复释放等问题。对性能影响相对较大。
启用: 内核配置 CONFIG_KASAN=y (通常还需要 CONFIG_KASAN_OUTLINE=y 或 CONFIG_KASAN_INLINE=y)。
定位用途: 当发生上述内存错误时,KASAN 会在内核日志中打印详细的错误报告,包括出错地址、错误类型、访问的内存范围、分配/释放栈等。对于快速定位 slab 对象的内存破坏类错误非常高效和准确。 是调试内存损坏的首选工具之一。
参考《Linux内存管理 (22)内存检测技术(slub_debug/kmemleak/kasan) 》。
2.10 kfence
参考《kfence作用、使用、示例 - ArnoldLu - 博客园》。
2.11 ftrace/tarce-cmd
作用: Linux 内核强大的内置跟踪框架。
事件追踪:
kmem:kmalloc, kmem:kmalloc_node, kmem:kfree, kmem:kmem_cache_alloc, kmem:kmem_cache_free 等事件。
启用:
echo 1 > /sys/kernel/debug/tracing/events/kmem/enable
使用 trace-cmd 或 cat /sys/kernel/debug/tracing/trace_pipe 查看实时事件流。
函数图追踪:
追踪特定的 slab 分配/释放函数(如 kmem_cache_alloc, kmem_cache_free, __kmalloc, kfree)及其调用关系。
定位用途:
统计特定类型分配/释放的频率。
观察分配请求的来源(函数调用链)。
结合过滤条件(PID、调用栈、大小等)追踪特定上下文的内存行为。
性能分析(分配延迟)。
2.12 perf
作用: 强大的性能分析工具,也可用于内存分析。
perf record/report:
可以采样 kmem:kmalloc, kmem:kfree 等硬件或软件事件。
生成调用图 (-g),查看哪些代码路径触发了最多的 slab 分配/释放。
perf mem: 分析内存访问模式(需要 CPU 支持)。
perf c2c: 检测缓存行竞争(False Sharing),这有时与 slab 使用模式有关。
定位用途: 性能剖析,识别 slab 分配/释放的热点代码路径。
2.13 systemtap
作用: 动态追踪语言/框架,允许编写脚本在内核任意位置(包括 slab 分配器内部函数)插入探针,收集自定义信息。
能力:
挂钩 kmem_cache_alloc, kmem_cache_free 等函数,打印参数(缓存指针、对象指针)、调用栈、时间戳、PID/Comm 等。
统计特定条件(如某个缓存、某个大小、某个进程)下的分配/释放次数、延迟分布。
追踪对象生命期(记录分配和释放,关联起来)。
检测特定模式(如分配后特定字节被修改)。
定位用途: 最灵活强大的方法,可以构建高度定制化的 slab 行为追踪和分析工具。 适合解决复杂、特定场景的问题。需要一定的编程能力。社区和网上有很多现成的脚本示例(如用于泄漏检测、对象追踪)。
3 总结与定位策略
初步排查 (快速、低开销): 从 /proc/slabinfo, slabtop, vmstat -m 开始,识别内存占用高、活跃对象比例异常(潜在泄漏)、碎片严重的特定缓存。
怀疑内存泄漏:
首选: 启用 slub_debug=U 或 FZPU,重启后观察 /proc/slabinfo 中缓存的 active_objs vs num_objs 比例,并检查 /sys/kernel/slab/<cache>/alloc_traces 查看未释放对象的分配栈。
系统级: 启用 kmemleak,定期扫描并查看报告。
高级追踪: 使用 ftrace 追踪特定缓存的 kmem_cache_alloc/free 事件,或编写 systemtap/bpftrace 脚本统计分配释放不匹配。
怀疑内存损坏 (越界、UAF):
首选: 启用 KASAN。它能非常精确地定位错误类型和位置。
低开销替代: 启用 KFENCE(适合生产环境抽样检测)。
SLUB 调试: 启用 slub_debug=FZP 或 FZPU。Z 检测越界,P 检测 UAF/未初始化使用。出错时会有内核 Oops。
性能瓶颈分析:
使用 ftrace 或 perf 采样 kmem_cache_alloc/free 事件,查看热点调用路径和延迟分布。
使用 slub_debug 的统计 (/sys/kernel/slab//alloc_calls 等) 或在 ftrace/systemtap 中统计分配频率。
检查 /proc/slabinfo 中的 num_slabs 和 active_slabs,碎片可能导致分配变慢。
深入对象级追踪:
slub_debug=T 结合 alloc_traces/free_traces。
编写 systemtap 或 bpftrace 脚本,在分配时记录对象指针和调用栈,在释放时匹配移除,记录“长寿”对象或泄漏对象。
调试功能(尤其是 slub_debug=A, KASAN, kmemleak)会带来显著性能开销和内存占用,通常只应在开发、测试或诊断环境启用,不适合长期在生产环境使用。KFENCE 和部分 ftrace/eBPF 追踪开销相对可控。
浙公网安备 33010602011771号