设为首页 加入收藏

TOP

深入理解 slab cache 内存分配全链路实现(三)
2023-07-23 13:32:28 】 浏览:169
Tags:slab cache 全链路
ub 所属的 page 必须是 frozen 冻结状态,只允许本地 cpu 从中分配对象 VM_BUG_ON(!c->page->frozen); // kmem_cache_cpu 中的 freelist 指向被 cpu 缓存 slub 中第一个空闲对象 // 由于第一个空闲对象马上要被分配出去,所以这里需要获取下一个空闲对象更新 freelist c->freelist = get_freepointer(s, freelist); // 更新 slab cache 的 cpu 本地缓存分配对象时的全局 transaction id // 每当分配完一次对象,kmem_cache_cpu 中的 tid 都需要改变 c->tid = next_tid(c->tid); // 返回第一个空闲对象 return freelist; new_slab: ......... 进入 slowpath 分配对象 .......... }

在 slab cache 进入慢速路径之前,内核还需要再次检查本地 cpu 缓存的 slab 的存储容量,确保其真的没有空闲对象了。

如果本地 cpu 缓存的 slab 为空( kmem_cache_cpu->page == null ),直接跳转到 new_slab 分支进入 slow path。

如果本地 cpu 缓存的 slab 不为空,那么需要再次检查 slab 中是否有空闲对象,这么做的目的是因为当前进程可能被中断,当重新调度之后,其他进程可能已经释放了一些对象到缓存 slab 中了,所以在进入 slowpath 之前还是有必要再次检查一下 kmem_cache_cpu->freelist。

如果碰巧,其他进程在当前进程被中断之后,已经释放了一些对象回缓存 slab 中了,那么就直接跳转至 load_freelist 分支,走 fastpath 路径,直接从缓存 slab (kmem_cache_cpu->freelist) 中分配对象,避免进入 slowpath。

load_freelist:
    // 更新 freelist,指向下一个空闲对象
    c->freelist = get_freepointer(s, freelist);
    // 更新 tid
    c->tid = next_tid(c->tid);
    // 返回第一个空闲对象
    return freelist;

如果 kmem_cache_cpu->freelist 还是为空,则需要再次检查 slab 本身的 freelist 是否空,注意这里指的是 struct page 结构中的 freelist。

struct page {
           // 指向内存页中第一个空闲对象
           void *freelist;     /* first free object */
           // 该 slab 是否在对应 slab cache 的本地 CPU 缓存中
           // frozen = 1 表示缓存再本地 cpu 缓存中
           unsigned frozen:1;
}

大家读到这里一定会感觉非常懵,kmem_cache_cpu 结构中有一个 freelist,page 结构也有一个 freelist,懵逼的是这两个 freelist 均是指向 slab 中第一个空闲对象,它俩之间有什么差别吗?

事实上,这一块的确比较复杂,逻辑比较绕,所以笔者有必要详细的为大家说明一下,以解决大家心中的困惑。

首先,在 slab cache 的整个架构体系中的确存在两个 freelist:

  • 一个是 page->freelist,因为 slab 在内核中是使用 struct page 结构来表示的,所以 page->freelist 只是单纯的站在 slab 的视角来表示 slab 中的空闲对象列表,这里不考虑 slab 在 slab cache 架构中的位置。

  • 另一个是 kmem_cache_cpu->freelist,特指 slab 被 slab cache 的本地 cpu 缓存之后,slab 中的空闲对象链表。这里可以理解为 slab 中被 cpu 缓存的空闲对象。当 slab 被提升为 cpu 缓存之后,page->freeelist 直接赋值给 kmem_cache_cpu->freelist,然后 page->freeelist 置空。slab->frozen 设置为 1,表示 slab 被冻结在当前 cpu 的本地缓存中。

image

而 slab 一旦被当前 cpu 缓存,它的状态就变为了冻结状态(slab->frozen = 1),处于冻结状态下的 slab,当前 cpu 可以从该 slab 中分配或者释放对象,但是其他 cpu 只能释放对象到该 slab 中,不能从该 slab 中分配对象

  • 如果一个 slab 被一个 cpu 缓存之后,那么这个 cpu 在该 slab 看来就是本地 cpu,当本地 cpu 释放对象回这个 slab 的时候会释放回 kmem_cache_cpu->freelist 链表中

  • 如果其他 cpu 想要释放对象回该 slab 时,其他 cpu 只能将对象释放回该 slab 的 page->freelist 中。

什么意思呢?笔者来举一个具体的例子为大家详细说明。

如下图所示,cpu1 在本地缓存了 slab1,cpu2 在本地缓存了 slab2,进程先从 slab1 中获取了一个对象,正常情况下如果进程一直在 cpu1 上运行的话,当进程释放该对象回 slab1 中时,会直接释放回 kmem_cache_cpu1->freelist 链表中。

但如果进程在 slab1 中获取完对象之后,被调度到了 cpu2 上运行,这时进程想要释放对象回 slab1 中时,就不能走快速路径了,因为 cpu2 本地缓存的是 slab2,所以 cpu2 只能将对象释放至 slab1->freelist 中。

image

这种情况下,在 slab1 的内部视角里,就有了两个 freelist 链表,它们的共同之处都是用于组织 slab1 中的空闲对象,但是 kmem_cache_cpu1->freelist 链表中组织的是缓存再 cpu1 本地的空闲对象,slab1->freelist 链表组织的是由其他 cpu 释放的空闲对象。

明白了这些,让我们再次回到 ___slab_alloc 函数的开始处,首先内核会在 slab cache 的本地 cpu 缓存 kmem_cache_cpu->freelist 中查找是否有空闲对象,如果这里没有,内核会继续到 page->freelist 中查看是否有其他 cpu 释放的空闲对象

如果两个 freelist 链表都没有空闲对象了,那就证明 slab cache 在当前 cpu 本地缓存中的 slab 已经为空了,将该 slab 从当前 cpu 本地缓存中脱离解冻,程序跳转到 new_slab 分支进入慢速分配路径。

// 查看 page->freelist 中是否有其他 cpu 释放的空闲对象
static inline void *get_freelist(struct kmem_cache *s, struct page *page)
{
    // 用于存放要更新的 page 属性值
    struct page new;
    unsigned long counters;
    void *freelist;

    do {
        // 获取 page 结构的 freelist,当其他 cpu 向 page 释放对象时 freelist 指向被释放的空闲对象
        // 当 page 被 slab cache 的 cpu 本地缓存时,freelist 置为 null
        freelist = page->freel
首页 上一页 1 2 3 4 5 6 7 下一页 尾页 3/10/10
】【打印繁体】【投稿】【收藏】 【推荐】【举报】【评论】 【关闭】 【返回顶部
上一篇在docker 环境中 websocket 通过n.. 下一篇关于 Bash 脚本中 Shebang 的趣事

最新文章

热门文章

Hot 文章

Python

C 语言

C++基础

大数据基础

linux编程基础

C/C++面试题目