设为首页 加入收藏

TOP

深入理解 slab cache 内存分配全链路实现(八)
2023-07-23 13:32:28 】 浏览:181
Tags:slab cache 全链路
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 中的空闲对象按照随机的顺序串联起来。

image

考虑到顺序初始化 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,为了防止内存访问越界。

image

所以在这种情况下,我们通过 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 对象详细的内存布局介绍,可以回看下笔者之前的文章

首页 上一页 5 6 7 8 9 10 下一页 尾页 8/10/10
】【打印繁体】【投稿】【收藏】 【推荐】【举报】【评论】 【关闭】 【返回顶部
上一篇在docker 环境中 websocket 通过n.. 下一篇关于 Bash 脚本中 Shebang 的趣事

最新文章

热门文章

Hot 文章

Python

C 语言

C++基础

大数据基础

linux编程基础

C/C++面试题目