我们之前一直在看各种的理论性的介绍,尽管网络上的文章写的都很好!但是总觉得缺少对slab的理想构图!所以我萌生出了从trace32去理解slab cache的方法,希望对大家理解slab内存的内部逻辑有一定的帮助
本文以我手里的一份dump来分析一下kmalloc-64作为案例
kmalloc_caches
v.v kmalloc_caches
kmalloc_caches = (
(0x0, 0x0, 0xFFFFFF800B21E400, 0x0, 0x0, 0x0, 0xFFFFFF800B21E200, 0xFFFFFF800B21E300, 0xFFFFFF80
(0x0, 0x0, 0xFFFFFF800B21E400, 0x0, 0x0, 0x0, 0xFFFFFF800B21E200, 0xFFFFFF800B21E300, 0xFFFFFF80
(0x0, 0x0, 0xFFFFFF800B21ED00, 0x0, 0x0, 0x0, 0xFFFFFF800B21EB00, 0xFFFFFF800B21EC00, 0xFFFFFF80
第一维:
[0]、[1]……(不同KMALLOC_TYPES,一般 0 是 normal)第二维:
[0]..[N],每个元素是struct kmem_cache *
所以点开第一个找到name为kmalloc-64的那一个
kmalloc_caches = (
(
0x0,
0x0,
0xFFFFFF800B21E400,
0x0,
0x0,
0x0,
0xFFFFFF800B21E200 -> (
cpu_slab = 0xFFFFFFC009FB2D30,
flags = 1073745920,
min_partial = 5,
size = 64,
object_size = 64,
reciprocal_size = (m = 1, sh1 = 1, sh2 = 5),
offset = 32,
cpu_partial = 120,
cpu_partial_slabs = 4,
oo = (x = 64),
min = (x = 64),
allocflags = 0,
refcount = -1,
ctor = 0x0,
inuse = 64,
align = 64,
red_left_pad = 0,
name = 0xFFFFFFC00956482B -> "kmalloc-64",
list = (next = 0xFFFFFF800B21E168, prev = 0xFFFFFF800B21E368),
kobj = (name = 0xFFFFFFC00956482B -> "kmalloc-64", entry = (next = 0xFFFFFF800B21E180, prev
random = 1024558225630158183,
random_seq = 0xFFFFFF8045752100,
kasan_info = (is_kmalloc = FALSE),
useroffset = 0,
usersize = 64,
node = (0xFFFFFF800B21C080)),
0xFFFFFF800B21E300,
所以kmalloc-64对应的地址是0xFFFFFF800B21E200,结构体类型为:struct kmem_cache
kmem_cache
(struct kmem_cache *) kmalloc_caches[0][6] = 0xFFFFFF800B21E200 -> (
(struct kmem_cache_cpu *) cpu_slab = 0xFFFFFFC009FB2D30,
(slab_flags_t) flags = 1073745920,
(unsigned long) min_partial = 5,
(unsigned int) size = 64,
(unsigned int) object_size = 64,
(struct reciprocal_value) reciprocal_size = ((u32) m = 1, (u8) sh1 = 1, (u8) sh2 = 5),
(unsigned int) offset = 32,
(unsigned int) cpu_partial = 120,
(unsigned int) cpu_partial_slabs = 4,
(struct kmem_cache_order_objects) oo = ((unsigned int) x = 64),
(struct kmem_cache_order_objects) min = ((unsigned int) x = 64),
(gfp_t) allocflags = 0,
(int) refcount = -1,
(void (*)()) ctor = 0x0,
(unsigned int) inuse = 64,
(unsigned int) align = 64,
(unsigned int) red_left_pad = 0,
(const char *) name = 0xFFFFFFC00956482B -> "kmalloc-64",
(struct list_head) list = ((struct list_head *) next = 0xFFFFFF800B21E168, (struct list_head *) prev = 0xFFFFFF800B21E368),
(struct kobject) kobj = ((const char *) name = 0xFFFFFFC00956482B -> "kmalloc-64", (struct list_head) entry = ((struct list_head *) next = 0xFFFFFF800B21E180, (struct list_head *) prev =
(unsigned long) random = 1024558225630158183,
(unsigned int *) random_seq = 0xFFFFFF8045752100,
(struct kasan_cache) kasan_info = ((bool) is_kmalloc = FALSE),
(unsigned int) useroffset = 0,
(unsigned int) usersize = 64,
(struct kmem_cache_node * [1]) node = ([0] = 0xFFFFFF800B21C080))
cpu_slab
(struct kmem_cache *) kmalloc_caches[0][6] = 0xFFFFFF800B21E200 -> (
(struct kmem_cache_cpu *) cpu_slab = 0xFFFFFFC009FB2D30 -> (
(void * *) freelist = ERROR:SPACEIDINVALID,
(unsigned long) tid = ERROR:SPACEIDINVALID,
(struct slab *) slab = ERROR:SPACEIDINVALID,
(struct slab *) partial = ERROR:SPACEIDINVALID,
(local_lock_t) lock = ()),
cpu_slab 里全是 ERROR:SPACEIDINVALID,是因为 读不到那块内存(per-cpu 区域),而不是结构体坏掉
cpu_slab指向的是 per-CPU 内存(alloc_percpu出来的)每个 CPU 有一个自己的
struct kmem_cache_cpu你看到的地址
0xFFFFFFC009FB2D30就是在 per-CPU 段 里的地址
而 TRACE32 要想读 per-CPU 区域,必须有正确的 MMU/地址空间配置或者在 dump 里包含那块内存。如果没有,就会出现类似:
ERROR:READERRORERROR:SPACEIDINVALID
node
(struct kmem_cache_node * [1]) node = (
[0] = 0xFFFFFF800B21C080 -> (
(spinlock_t) list_lock = ((struct raw_spinlock) rlock = ((arch_spinlock_t) raw_lock = ((atomic_t) val = ((int) counter = 0), (u8) locked = 0, (u8) pending = 0, (u16) locked_pending =
(unsigned long) nr_partial = 0,
(struct list_head) partial = (
(struct list_head *) next = 0xFFFFFF800B21C090,
(struct list_head *) prev = 0xFFFFFF800B21C090),
(atomic_long_t) nr_slabs = ((s64) counter = 3073),
(atomic_long_t) total_objects = ((s64) counter = 196672),
(struct list_head) full = (
(struct list_head *) next = 0xFFFFFF800B21C0B0,
(struct list_head *) prev = 0xFFFFFF800B21C0B0))))
1. 这段输出在说什么?
nr_partial = 0partial.next = partial.prev = 0xFFFFFF800B21C090
说明:partial 这个链表是自环,也就是 空链表。
同理:
full.next = full.prev = 0xFFFFFF800B21C0B0
full 也是自环 → 也是空链表。
但同时:
nr_slabs = 3073total_objects = 196672
说明:这个 node 上是有很多 slab 的,只不过它们没有挂在 partial / full 这两个链表上。
2. 为什么 full 链表是空的,但 nr_slabs 却是 3073?
这是 SLUB 的一个“坑点”:
kmem_cache_node.full这个链表只在启用 SLUB debug(slub_debug或相关配置)时才真正用来挂 full slabs;在普通配置下,
full基本是个“摆设”,不维护 slab 列表,所以你看到的就是自环。
换句话说:
在你现在的配置里,不能指望通过node->full找到所有 full slabs。nr_slabs/total_objects这些计数器会更新,但 slab 实体不一定挂在full上。
加上前面看到的:
nr_partial = 0,partial空full也空
→ 结合 cpu_slab 又看不到(per-CPU 区域读不到),极大概率是所有 slab 都在各 CPU 的 kmem_cache_cpu.slab / partial 链里,没挂回 node 的 partial/full。
然后我打开了slub_debug=FPZU后重新抓了一份dump
(*((kmalloc_caches[0][6]))).node[0] = 0xFFFFFF800B21C240 -> (
list_lock = (rlock = (raw_lock = (val = (counter = 0), locked = 0, pending = 0, locked_pending =
nr_partial = 1693,
partial = (
next = 0xFFFFFFFE036A7188 -> (
next = 0xFFFFFFFE073C9F08 -> (
next = 0xFFFFFFFE02AE9508 -> (
next = 0xFFFFFFFE00EBA788 -> (
next = 0xFFFFFFFE03296708,
prev = 0xFFFFFFFE02AE9508),
prev = 0xFFFFFFFE073C9F08),
prev = 0xFFFFFFFE036A7188),
prev = 0xFFFFFF800B21C250),
prev = 0xFFFFFFFE06877788),
nr_slabs = (counter = 8094),
total_objects = (counter = 258992),
full = (
next = 0xFFFFFFFE00E44008 -> (
next = 0xFFFFFFFE00B50B88 -> (
next = 0xFFFFFFFE02D69088,
prev = 0xFFFFFFFE00E44008),
prev = 0xFFFFFF800B21C270),
prev = 0xFFFFFFFE002C8788))
nr_partial = 1693,partial链表确实挂了很多 slabfull.next、full.prev也不再自环,说明 full slab 也在链表里了
而挂载partial和full上面的next 和prev指针并不是slab的首地址,而是:
挂在链表上的
struct list_head成员的地址,
这个list_head嵌在struct page里(一般叫slab_list或有的版本直接用lru)。
也就是说:
partial.next = 0xFFFFFFFE036A7188
代表的是:
有一个
struct page,它里面的某个struct list_head成员(比如page->slab_list)的地址就是0xFFFFFFFE036A7188
我们只要知道这个成员在
struct page里的偏移,就能反推回struct page *的地址
即:
page = (void *)list_head_addr - offsetof(struct page, slab_list)
page
查询list_head在page里的偏移
struct page {
unsigned long flags; /* Atomic flags, some possibly
* updated asynchronously */
/*
* Five words (20/40 bytes) are available in this union.
* WARNING: bit 0 of the first word is used for PageTail(). That
* means the other users of this union MUST NOT use the bit to
* avoid collision and false-positive PageTail().
*/
union {
struct { /* Page cache and anonymous pages */
/**
* @lru: Pageout list, eg. active_list protected by
* lruvec->lru_lock. Sometimes used as a generic list
* by the page owner.
*/
union {
struct list_head lru;
/* Or, for the Unevictable "LRU list" slot */
struct {
/* Always even, to negate PageTail */
void *__filler;
/* Count page's or folio's mlocks */
unsigned int mlock_count;
};
/* Or, free page */
struct list_head buddy_list;
struct list_head pcp_list;
};
/* See page-flags.h for PAGE_MAPPING_FLAGS */
struct address_space *mapping;
pgoff_t index; /* Our offset within mapping. */
/**
* @private: Mapping-private opaque data.
* Usually used for buffer_heads if PagePrivate.
* Used for swp_entry_t if PageSwapCache.
* Indicates order in the buddy system if PageBuddy.
*/
unsigned long private;
};lru 的偏移是0x8,所以当前partial->next =0xFFFFFFFE036A7188 对应的page的地址为0xFFFFFFFE036A7180

这里的链表顺序也和partial链表对的上
当 page 用作 slab 页时,struct page 里很多字段是通过 union 被 SLUB 重用的,根本不是“普通匿名页”的含义。
也就是说:
对 slab 页来说:
mapping、index、private、pp、__filler这些字段实际上被当成 slab 的元数据(slab_cache 指针、freelist、对象计数等)来用
所以这些字段用“通用 page 视角”看起来乱,是正常的,并不是算错了
我们只需要用slab类型来解析就可以得到真正的slab结构体
(struct slab *)0xFFFFFFFE036A7180 = 0xFFFFFFFE036A7180 -> (
__page_flags = 4611686018427453952,
slab_list = (
next = 0xFFFFFFFE073C9F08 -> (
next = 0xFFFFFFFE02AE9508 -> (
next = 0xFFFFFFFE00EBA788,
prev = 0xFFFFFFFE073C9F08),
prev = 0xFFFFFFFE036A7188),
prev = 0xFFFFFF800B21C250),
callback_head = (next = 0xFFFFFFFE073C9F08, func = 0xFFFFFF800B21C250),
next = 0xFFFFFFFE073C9F08,
slabs = 186761808,
slab_cache = 0xFFFFFF8045750700,
freelist = 0xFFFFFF80DA9C6D40 -> ,
counters = 2097175,
inuse = 23,
objects = 32,
frozen = 0,
__unused = 4294967295,
__page_refcount = (counter = 1),
memcg_data = 0)
这块 slab 页是什么状态?
关键字段:
slab_cache = 0xFFFFFF8045750700
👉 这是 kmalloc-64 的一个 slab 页。
objects = 32
👉 这个 slab 一共管理 32 个对象(64B object + 对齐 + metadata 后的结果),
不是单纯 “4K / 64 = 64”,SLUB 为自己的元数据留了空间。
inuse = 23
👉 当前有 23 个对象正在被分配出去,
剩余 free = 32 - 23 = 9 个空闲对象。
frozen = 0
👉 这块 slab 没有被冻结在某个 CPU 的 kmem_cache_cpu 上,
也就是正常挂在 kmem_cache_node.partial 的 partial 链里——
和你刚刚看到的 node[0].partial 是一致的。
slab_list
👉 这就是在 node 里看的那个链表节点:
prev = 0xFFFFFF800B21C250:就是node->partial的头结点next = 0xFFFFFFFE073C9F08:指向 partial 链上的下一个 slab
也就是说:
已经成功从 node->partial 链表,走到了一个具体的 kmalloc-64 的 partial slab 页,而且看到了它有 32 个 object,其中 23 个在用。
freelist
我们要了解到的是freelist是指向的当前slab页中的第一个空闲slab object,所以我们来看
freelist = 0xFFFFFF80DA9C6D40 这个对应的内存是怎样的

正如我们设置的slub_debug=FPZU
这个freelist的指针指向的是slab object的freepointer指针的位置,
我们索要了解的是这个freepointer的位置在free时的位置是放在object部分的,所以我们现在看到的这张图0xFFFFFF80DA9C6D40处就是redzone的末尾和object的起始位置,所以前面就是填充的redzone区域,应该为0xB,查看内存推导结果也如我们所料


开启 slub_debug 且带 U(SLAB_STORE_USER)选项时,每个对象尾部会多出一个或两个 struct track
查看后面的FFFFFFC001386FC8,这个就是alloc track
查看后面的 FFFFFFC00138864C,这个就是free track
freelist 里的 slab object = 已经 free 的对象
里面的用户数据基本都被 0x6B/0x5A/0xFF 这些 poison 覆盖掉了,剩下的只是 redzone 和 track 等 debug 元数据。
“找一个 正在使用 的 kmalloc-64 对象,看看它的 payload + alloc/free track”
就必须换目标:
去找一个不在 freelist 里的 slot,也就是 allocated object。
allocated object
总结一下现在已经有的:
对某个 slab页:
objects = 32inuse = 23→ 有 23 个已分配、9 个 freefreelist指向第一个 free 对象(你已经 dump 过,都是0x6Bpoison)
你知道:
PAGE_SIZE(4K)kmem_cache->size(64)kmem_cache->red_left_pad(要再看一下 kmalloc-64 的 struct)
算出这一页 slab 的起始地址(slab_base)
0xFFFFFF80DA9C6D40 & ~(0x1000-1) = 0xFFFFFF80DA9C6000计算 kmem_cache的
size/red_left_pad
size = ((struct kmem_cache *)&0xFFFFFF80DA9C6000)->size
kmalloc_caches[0][6] = 0xFFFFFF8045750700 -> (
cpu_slab = 0xFFFFFFC00A0C2D30,
flags = 2147556608,
min_partial = 5,
size = 256,
object_size = 64,
reciprocal_size = (m = 1, sh1 = 1, sh2 = 7),
offset = 72,
cpu_partial = 0,
cpu_partial_slabs = 0,
oo = (x = 65568),
min = (x = 16),
allocflags = 262144,
refcount = -1,
ctor = 0x0,
inuse = 72,
align = 64,
red_left_pad = 64,
name = 0xFFFFFFC0093624B8 -> "kmalloc-64",
对于这个 cache:
每个 slot 步长:
size = 256 (0x100)用户真正能用的大小:
object_size = 64左边红区 / padding:
red_left_pad = 64 (0x40)freelist 指针在 object 内部的偏移:
offset = 72 (0x48)(后面遍历 freelist 时用)
所以这一块 slab 里,第 i 个 slot 的地址是:
slot[i] = slab_base + red_left_pad + i * size
= 0xFFFFFF80DA9C6000 + 0x40 + i * 0x100我们来验证一下前面那条 freelist 指向的 object 地址:
freelist = 0xFFFFFF80DA9C6D40
算它是第几个 slot:
slab_base + pad = 0xFFFFFF80DA9C6040差值:
0x6D40 - 0x6040 = 0x0D000x0D00 / 0x100 = 0x0D = 13
👉 也就是说:freelist 指向的是这一 slab 的第 13 号 slot(index = 13)
那这个对吗?我们已知这个slab页一共32个,其中23个已经allocated!那我们推导出freelist指向的是地13号slot,那是不是说明我们分析的有问题?
其实不然!
SLUB 在一个 slab 里:
对象物理布局:按固定步长排好(
slot[i] = base + pad + i * size),这一点是确定的;对象分配/释放顺序:不保证任何顺序,只看
freelist链表头是谁——
特别是有 SLAB_FREELIST_RANDOM 的话,free 的顺序还会被打乱。
也就是说:
地址上的顺序:
slot[0], slot[1], slot[2]...分配的顺序:
slot[13] → slot[2] → slot[27] → ...完全不必按地址来。
我们现在找一个已经分配的slab object分析一下

看到了熟悉的DEAD000000000122
这个正是slab的
查看slab的alloc track和free track
(struct track *)0xFFFFFF80DA9C6290 = 0xFFFFFF80DA9C6290 -> (
addr = 0xFFFFFFC00834EAAC,
handle = 0x0,
cpu = 0x0,
pid = 0x2456,
when = 0xFFFF7230)
(struct track *)0xFFFFFF80DA9C62B0 = 0xFFFFFF80DA9C62B0 -> (
addr = 0xFFFFFFC001388730,
handle = 0x0,
cpu = 0x0,
pid = 0x0A,
when = 0xFFFF7224)


Congratuations!完美符合!
总结
整篇文章主要就利用trace32来梳理slab内存的这些结构,最起码我在梳理的时候觉得以前一些不通的概念就想明白了,希望对您有所帮助!
另外,本篇文章涉及到的一些概念,请配合以下文章一起食用: