page 结构中与 slab 相关的属性。
通过 kasan_poison_slab 函数将 slab 中的内存用 0xFC 填充,用于 kasan 对于内存越界相关的检查。
// 定义在文件:/mm/kasan/kasan.h
#define KASAN_KMALLOC_REDZONE 0xFC /* redzone inside slub object */
// 定义在文件:/mm/kasan/common.c
void kasan_poison_slab(struct page *page)
{
unsigned long i;
// slub 可能包含多个内存页 page,挨个遍历这些 page
// 清除这些 page->flag 中的内存越界检查标记
// 表示当访问到这些内存页的时候临时禁止内存越界检查
for (i = 0; i < compound_nr(page); i++)
page_kasan_tag_reset(page + i);
// 用 0xFC 填充这些内存页的内存,用于内存访问越界检查
kasan_poison_shadow(page_address(page), page_size(page),
KASAN_KMALLOC_REDZONE);
}
最后会初始化 slab 中的 freelist 链表,将内存页中的空闲内存块通过 page->freelist 链表组织起来。
如果内核开启了 CONFIG_SLAB_FREELIST_RANDOM
选项,那么就会通过
shuffle_freelist 函数将内存页中空闲的内存块按照随机的顺序串联在 page->freelist 中。
如果没有开启,则会在 if (!shuffle)
分支中,按照正常的顺序初始化 page->freelist。
最后通过 inc_slabs_node 更新 NUMA 节点缓存 kmem_cache_node 结构中的相关计数。
struct kmem_cache_node {
// slab 的个数
atomic_long_t nr_slabs;
// 该 node 节点中缓存的所有 slab 中包含的对象总和
atomic_long_t total_objects;
};
static inline void inc_slabs_node(struct kmem_cache *s, int node, int objects)
{
// 获取 page 所在 numa node 再 slab cache 中的缓存
struct kmem_cache_node *n = get_node(s, node);
if (likely(n)) {
// kmem_cache_node 中的 slab 计数加1
atomic_long_inc(&n->nr_slabs);
// kmem_cache_node 中包含的总对象计数加 objects
atomic_long_add(objects, &n->total_objects);
}
}
4. 初始化 slab freelist 链表
内核在对 slab 中的 freelist 链表初始化的时候,会有两种方式,一种是按照内存地址的顺序,一个一个的通过对象 freepointer 指针顺序串联所有空闲对象。
另外一种则是通过随机的方式,随机获取空闲对象,然后通过对象的 freepointer 指针将 slab 中的空闲对象按照随机的顺序串联起来。
考虑到顺序初始化 freelist 比较直观,为了方便大家的理解,笔者先为大家介绍顺序初始化的方式。
static struct page *allocate_slab(struct kmem_cache *s, gfp_t flags, int node)
{
// 获取 slab 的起始内存地址
start = page_address(page);
// shuffle_freelist 随机初始化 freelist 链表,返回 false 表示需要顺序初始化 freelist
shuffle = shuffle_freelist(s, page);
// shuffle = false 则按照正常的顺序来初始化 freelist
if (!shuffle) {
// 获取 slub 第一个空闲对象的真正起始地址
// slub 可能配置了 SLAB_RED_ZONE,这样会在 slub 对象内存空间两侧填充 red zone,防止内存访问越界
// 这里需要跳过 red zone 获取真正存放对象的内存地址
start = fixup_red_left(s, start);
// 填充对象的内存区域以及初始化空闲对象
start = setup_object(s, page, start);
// 用 slub 中的第一个空闲对象作为 freelist 的头结点,而不是随机的一个空闲对象
page->freelist = start;
// 从 slub 中的第一个空闲对象开始,按照正常的顺序通过对象的 freepointer 串联起 freelist
for (idx = 0, p = start; idx < page->objects - 1; idx++) {
// 获取下一个对象的内存地址
next = p + s->size;
// 填充下一个对象的内存区域以及初始化
next = setup_object(s, page, next);
// 通过 p 的 freepointer 指针指向 next,设置 p 的下一个空闲对象为 next
set_freepointer(s, p, next);
// 通过循环遍历,就把 slub 中的空闲对象按照正常顺序串联在 freelist 中了
p = next;
}
// freelist 中的尾结点的 freepointer 设置为 null
set_freepointer(s, p, NULL);
}
}
内核在顺序初始化 slab 中的 freelist 之前,首先需要知道 slab 的起始内存地址 start,但是考虑到 slab 如果配置了 SLAB_RED_ZONE 的情况,那么在 slab 对象左右两侧,内核均会插入两段 red zone,为了防止内存访问越界。
所以在这种情况下,我们通过 page_address
获取到的只是 slab 的起始内存地址,正是 slab 中第一个空闲对象的左侧 red zone 的起始位置。
所以我们需要通过 fixup_red_left 方法来修正 start 位置,使其越过 slab 对象左侧的 red zone,指向对象内存真正的起始位置,如上图中所示。
void *fixup_red_left(struct kmem_cache *s, void *p)
{
// 如果 slub 配置了 SLAB_RED_ZONE,则意味着需要再 slub 对象内存空间两侧填充 red zone,防止内存访问越界
// 这里需要跳过填充的 red zone 获取真正的空闲对象起始地址
if (kmem_cache_debug(s) && s->flags & SLAB_RED_ZONE)
p += s->red_left_pad;
// 如果没有配置 red zone,则直接返回对象的起始地址
return p;
}
当我们确定了对象的起始位置之后,对象所在的内存块也就确定了,随后调用 setup_object 函数来初始化内存块,这里会按照 slab 对象的内存布局进行填充相应的区域。
slab 对象详细的内存布局介绍,可以回看下笔者之前的文章 《