设为首页 加入收藏

TOP

深入理解 slab cache 内存分配全链路实现(四)
2023-07-23 13:32:28 】 浏览:172
Tags:slab cache 全链路
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 已经没有任何可供分配的空闲对象了。

image

下面内核正式进入到 slowpath 开始分配对象,首先内核会到本地 cpu 缓存的 partial 列表中去查看是否有一个 slab 可以分配对象。这里内核会从 partial 列表中的头结点开始遍历直到找到一个可以满足分配的 slab 出来。

随后内核会将该 slab 从 partial 列表中摘下,直接提升为新的本地 cpu 缓存,这样一来 slab cache 的本地 cpu 缓存就被更新了,内核通过 kmem_cache_cpu->freelist 指针将缓存 slab 中的第一个空闲对象分配出去,随后更新 kmem_cache_cpu->freelist 指向 slab 中的下一个空闲对象。

image

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 节点缓存中去分配对象了。

image

3.2 从 NUMA 节点缓存中分配

// slab cache 慢速路径下分配对象核心
首页 上一页 1 2 3 4 5 6 7 下一页 尾页 4/10/10
】【打印繁体】【投稿】【收藏】 【推荐】【举报】【评论】 【关闭】 【返回顶部
上一篇在docker 环境中 websocket 通过n.. 下一篇关于 Bash 脚本中 Shebang 的趣事

最新文章

热门文章

Hot 文章

Python

C 语言

C++基础

大数据基础

linux编程基础

C/C++面试题目