ist;
counters = page->counters;
new.counters = counters;
VM_BUG_ON(!new.frozen);
// 更新 inuse 字段,表示 page 中的对象 objects 全部被分配出去了
new.inuse = page->objects;
// 如果 freelist != null,表示其他 cpu 又释放了一些对象到 page 中 (slub)。
// 则 page->frozen = 1 , slub 依然冻结在 cpu 本地缓存中
// 如果 freelist == null,则 page->frozen = 0, slub 从 cpu 本地缓存中脱离解冻
new.frozen = freelist != NULL;
// 最后 cas 原子更新 page 结构中的相应属性
// 这里需要注意的是,当 page 被 slab cache 本地 cpu 缓存时,page -> freelist 需要置空。
// 因为在本地 cpu 缓存场景下 page -> freelist 指向其他 cpu 释放的空闲对象列表
// kmem_cache_cpu->freelist 指向的是被本地 cpu 缓存的空闲对象列表
// 这两个列表中的空闲对象共同组成了 slub 中的空闲对象
} while (!__cmpxchg_double_slab(s, page,
freelist, counters,
NULL, new.counters,
"get_freelist"));
return freelist;
}
3.1 从本地 cpu 缓存 partial 列表中分配
内核经过在 redo
分支的检查,现在已经确认了 slab cache 在当前 cpu 本地缓存的 slab 已经没有任何可供分配的空闲对象了。
下面内核正式进入到 slowpath 开始分配对象,首先内核会到本地 cpu 缓存的 partial 列表中去查看是否有一个 slab 可以分配对象。这里内核会从 partial 列表中的头结点开始遍历直到找到一个可以满足分配的 slab 出来。
随后内核会将该 slab 从 partial 列表中摘下,直接提升为新的本地 cpu 缓存,这样一来 slab cache 的本地 cpu 缓存就被更新了,内核通过 kmem_cache_cpu->freelist 指针将缓存 slab 中的第一个空闲对象分配出去,随后更新 kmem_cache_cpu->freelist 指向 slab 中的下一个空闲对象。
static void *___slab_alloc(struct kmem_cache *s, gfp_t gfpflags, int node,
unsigned long addr, struct kmem_cache_cpu *c)
{
............ 检查本地 cpu 缓存是否为空 ...........
redo:
............ 再次确认 kmem_cache_cpu->freelist 中是否有空闲对象 ...........
............ 再次确认 page->freelist 中是否有空闲对象 ...........
load_freelist:
............ 回到 fastpath 直接从 freelist 中分配对象 ...........
new_slab:
// 查看 kmem_cache_cpu->partial 链表中是否有 slab 可供分配对象
if (slub_percpu_partial(c)) {
// 获取 cpu 本地缓存 kmem_cache_cpu 的 partial 列表中的第一个 slub (用 page 表示)
// 并将这个 slub 提升为 cpu 本地缓存中的 slub,赋值给 c->page
page = c->page = slub_percpu_partial(c);
// 将 partial 列表中第一个 slub (c->page)从 partial 列表中摘下
// 并将列表中的下一个 slub 更新为 partial 列表的头结点
slub_set_percpu_partial(c, page);
// 更新状态信息,记录本次分配是从 kmem_cache_cpu 的 partial 列表中分配
stat(s, CPU_PARTIAL_ALLOC);
// 重新回到 redo 分支,这下就可以从 page->freelist 中获取对象了
// 并且在 load_freelist 分支中将 page->freelist 更新到 c->freelist 中,page->freelist 设置为 null
// 此时 slab cache 中的 cpu 本地缓存 kmem_cache_cpu 的 freelist 以及 page 就变为了 partial 列表中的 slub
goto redo;
}
// 流程走到这里表示 slab cache 中的 cpu 本地缓存 partial 列表中也没有 slub 了
// 需要近一步降级到 numa node cache —— kmem_cache_node 中的 partial 列表去查找
// 如果还是没有,就只能去伙伴系统中申请新的 slub,然后分配对象
// 该函数为 slab cache 在慢速路径下分配对象的核心逻辑
freelist = new_slab_objects(s, gfpflags, node, &c);
if (unlikely(!freelist)) {
// 如果伙伴系统中无法分配 slub 所需的 page,那么就提示内存不足,分配失败,返回 null
slab_out_of_memory(s, gfpflags, node);
return NULL;
}
page = c->page;
if (likely(!kmem_cache_debug(s) && pfmemalloc_match(page, gfpflags)))
// 此时从 kmem_cache_node->partial 列表中获取的 slub
// 或者从伙伴系统中重新申请的 slub 已经被提升为本地 cpu 缓存了 kmem_cache_cpu->page
// 这里需要跳转到 load_freelist 分支,从本地 cpu 缓存 slub 中获取第一个对象返回
goto load_freelist;
}
内核对 kmem_cache_cpu->partial 链表的相关操作:
// 定义在文件 /include/linux/slub_def.h 中
#ifdef CONFIG_SLUB_CPU_PARTIAL
// 获取 slab cache 本地 cpu 缓存的 partial 列表
#define slub_percpu_partial(c) ((c)->partial)
// 将 partial 列表中第一个 slub 摘下,提升为 cpu 本地缓存,用于后续快速分配对象
#define slub_set_percpu_partial(c, p) \
({ \
slub_percpu_partial(c) = (p)->next; \
})
如果 slab cache 本地 cpu 缓存 kmem_cache_cpu->partial 链表也是空的,接下来内核就只能到对应 NUMA 节点缓存中去分配对象了。
3.2 从 NUMA 节点缓存中分配
// slab cache 慢速路径下分配对象核心