从零到负一

14. 【Slab】6 - slab的一些情景分析

2023/03/24

这篇笔记算是对slab的总结吧,我会边写边回顾之前的几篇笔记。有新的想法就添加,有不对的地方就修改。争取把slab的一系列流程搞清楚。我还是先从第一个场景,就是从零开始创建普通高速缓存写起。

创建普通高速缓存

创建第一个普通高速缓存

这要从10. 【slab】2 - slab的基本操作 - kmem_cache_init()说起,在kmem_cache_init()中,内核创建的第一个高速缓存就是普通高速缓存,我这里暂且给它取个名字 - GC1

1
2
3
4
5
6
7
sizes->cs_cachep = kmem_cache_create(
names->name, // onst char *name
sizes->cs_size, // size_t size, the size of objects to be created in this cache
ARCH_KMALLOC_MINALIGN, // size_t align:
(ARCH_KMALLOC_FLAGS | SLAB_PANIC), // unsigned long flags
NULL, // void (*ctor)(void*, kmem_cache_t *, unsigned long)
NULL);

我们来看看,这个时候有什么,这个时候我们只有一个静态初始化的高速缓存cache_cache,它的本地高速缓存也是静态初始化的,shared的高速缓存是空的,没有一个分配好的slab,当然也就没有任何对象。这种情况很特殊,因此我把它放在第一个位置。我们来看看在这种情况下如何创建高速缓存。

这里我稍微详细点记录第一个高速缓存是如何创建的。

  1. 计算GC1中的对齐(用于对象地址、offset等地方)以及将对象的大小按WORD进行对齐。

GC1高速缓存的对象大小、对齐已经确定

  1. 获取GC1高速缓存的描述符。
    1. kmem_cache_create()通过调用kmem_cache_alloc() --> __cache_alloc()cache_cache获取GC1缓存的描述符(cache_cache就是专门用于这项工作);
    2. __cache_alloc()检查其本地高速缓存是否有空闲空间 - 目前ac->avail == 0,不满足条件。开始尝试对cache_cache的本地高速缓存进行填充;
    3. 查看shared的本地高速缓存是否有空闲对象 - 目前shared的本地高速缓存还为空;
    4. 查看是否有slab在free或者partial的slab链表上 - 目前还没有任何slab
    5. 需要调用cache_grow()来创建一个新的slab(包括对对象的初始化)- 这个slabcache_cache的,因此使用cache_cache初始化的设置来创建这个slab(比如对象大小,偏移,着色,slab描述符是否在slab外等);
    6. 将新创建的slab放回slab的free链表,然后跳到2.4,通过free链表找到这个刚创建的slab
    7. 将一定数量(< batchcount)的对象给本地高速缓存(其实就是将对象的地址复制给本地高速缓存),在这里,cache_cachebatchcount和本地高速缓存的大小都是1,因此,只需要复制1个对象的地址即可;
    8. __cache_alloc()中返回这个对象的地址,这个地址就是我们需要创建的GC1高速缓存的描述符地址。

GC1高速缓存的描述符已获取,cache_cache有了第一个slab,其array_cache的本地高速缓存实现了填充和使用

  1. 根据对象的大小、对齐等,计算出GC1中slab对象的数量、高速缓存的gfporder、offset、着色、对象大小等等。

GC1高速缓存的gfporder、offset、着色、对象大小等等已经获取,其slab的内部结构已经确定

  1. 创建GC1高速缓存的本地高速缓存
    1. 根据g_cpucache_up的值(NONE),使用静态分配的方法给GC1分配本地高速缓存,完成后g_cpucache_up == PARTIAL
    2. 初始化GC1的本地高速缓存以及部分GC1的参数。

GC1有了本地高速缓存,但没有空闲对象,GC1目前也没有slab

  1. 将GC1连入cache_chain

至此,GC1就算是创建成功了,但它目前还有几个缺点:

  1. 没有slab和对象;
  2. 没有shared的高速缓存;
  3. 本地高速缓存还是空的;

这些缺点在之后的初始化过程中都会被修复,我到时候再说明。

创建第二个普通高速缓存

这里我就给它取名为GC2吧,我们来看看它的创建和第一个普通高速缓存的创建有什么不同。

  1. 这步一样。
  2. 这步基本一样,cache_cache的本地高速缓存依然没有空闲对象,去partial slab链表找到slab和对象,复制给cache_cache的本地高速缓存并被使用。
  3. 这步一样。
  4. 现在g_cpucache_up == PARTIAL,那么就需要使用kmallo()来获取GC2的本地高速缓存了。和cache_cache一样,GC2的本地高速缓存也只能容纳一个对象。这个函数最终会调用__cache_alloc(),那么就要走GC1创建过程中cache_cache走的老路了。GC1创建一个新的slab再获得GC2的高速缓存描述符。其它和GC1的创建一样。

GC1有了第一个slab,其array_cache的本地高速缓存实现了填充和使用

  1. 将GC2连入cache_chain

创建第三个普通高速缓存

1,2,3,5步都一样,4中,kmalloc()去GC1的slab中获取一个对象,不用再创建新的slab。其它一样。

完成kmem_cache_init()剩下部分

在上一部分,我们讨论的是创建普通高速缓存,它们是在kmem_cache_init()中完成的。到目前为止,我们可以知道,cache_cache和GC1是真正分配了slab,其它普通高速缓存都没有分配slab。并且它们的本地高速缓存和只能容纳一个对象、shared高速缓存还没有初始化,这部分都在kmem_cache_init()的后半部分完成。

在完成所有普通高速缓存的创建后,第一步就是替换掉cache_cache和GC1中的本地高速缓存。从上面分析可以知道,这两个高速缓存的本地高速缓存都是静态分配的,我们这里要用kmalloc()来动态的分配内存空间(从GC1中获取)。

再次总结,高速缓存的描述符从cache_cache中获取(包括专用和普通),cache_cache和普通高速缓存的本地高速缓存从GC1获取。

到这里,我们就需要重新初始化cache_cache和所有普通高速缓存的本地高速缓存和shared的高速缓存了。

重新初始化本地高速缓存和shared的高速缓存

这部分直接参考11. 【Slab】3 - Slab的基本操作 - kmem_cache_create()即可,说的很清楚了(当然,我现在又回去修改了一些地方)。

这步结束后,cache_cache, GC1, GC2, …, GCx就都完成了最后的分配,以后就可以直接使用kmalloc()等函数了。

kmem_cache_init()结束之后创建专用高速缓存

在初始化之后,创建专用高速缓存就比较简单了。

  1. 这步一样。
  2. 这步一样,从cache_cache获取高速缓存描述符。
  3. 这步一样。
  4. 现在g_cpucache_up == FULL,说明slab系统初始化已经完成。可以直接在这里创建本地高速缓存和shared的高速缓存。
  5. 这步一样。

到这里,我们就完成了对高速缓存创建的几种特殊情况的分析,同时,我们还复习了如何分配对象以及在不同的情况下,对象分配过程中的不同点。对于对象、slab以及高速缓存的释放、销毁,我在上一篇笔记中已经详细记载了,这里就不重复了。

至此,slab系统的学习总结要告一段落了,以后有什么新的想法再来补充、修改。

CATALOG
  1. 1. 创建普通高速缓存
    1. 1.1. 创建第一个普通高速缓存
    2. 1.2. 创建第二个普通高速缓存
    3. 1.3. 创建第三个普通高速缓存
    4. 1.4. 完成kmem_cache_init()剩下部分
    5. 1.5. 重新初始化本地高速缓存和shared的高速缓存
  2. 2. kmem_cache_init()结束之后创建专用高速缓存