- 论坛徽章:
- 0
|
Slab初始化过程
静态初始化
所谓静态初始化就是指编译内核时进行的初始化过程。
/* internal cache of cache description objs */
static kmem_cache_t cache_cache = {
slabs_full: LIST_HEAD_INIT(cache_cache.slabs_full),
slabs_partial: LIST_HEAD_INIT(cache_cache.slabs_partial),
slabs_free: LIST_HEAD_INIT(cache_cache.slabs_free),
objsize: sizeof(kmem_cache_t),
flags: SLAB_NO_REAP,
spinlock: SPIN_LOCK_UNLOCKED,
colour_off: L1_CACHE_BYTES,
name: "kmem_cache",
};
之所以需要预先静态的定义一个缓存器,是因为如果内核需要创建一个缓存器,则必须要
从某一个缓存器中分配一个 kmem_cache_t”对象”,可是这个缓存器从哪里来呢?显然,
这是一个先有鸡还是先有蛋的问题。所以就需要预先静态的定义一个缓存器,并初始化
一些能够在编译时初始化的字段。
动态初始化
所谓动态初始化,当然就是 Linux 内核被加载到内存,并开始初始化系统的阶段,对 Slab
Allocator 进行的初始化。
起点是在 init/main.c 文件中的 start_kernel()函数,其调用 kmem_cache_init()。
故 kmem_cache_init()函数负责对 Slab Allocator 进一步的初始化。
/* Initialisation - setup the `cache' cache. */
void __init kmem_cache_init(void)
{
size_t left_over;
init_MUTEX(&cache_chain_sem);
INIT_LIST_HEAD(&cache_chain);
该函数负责计算给定大小的 slab 上能够存放多少个”对象”以及所浪费的字节数
kmem_cache_estimate(0, cache_cache.objsize, 0,
&left_over, &cache_cache.num);
if (!cache_cache.num)
BUG();
cache_cache.colour = left_over/cache_cache.colour_off;
cache_cache.colour_next = 0;
}
上面粗体部分就是 cache_cache 结构中被初始化了的字段
接着会调用 kmem_cache_sizes_init()。
/* Initialisation - setup remaining internal and general caches.
* Called after the gfp() functions have been enabled, and before smp_init().
*/
void __init kmem_cache_sizes_init(void)
{
cache_sizes_t *sizes = cache_sizes;
char name[20];
/*
* Fragmentation resistance on low memory - only use bigger
* page orders on machines with more than 32MB of memory.
*/
if (num_physpages > (32 << 20) >> PAGE_SHIFT)
slab_break_gfp_order = BREAK_GFP_ORDER_HI;
do {
/* For performance, all the general caches are L1 aligned.
* This should be particularly beneficial on SMP boxes, as it
* eliminates "false sharing".
* Note for systems short on memory removing the alignment will
* allow tighter packing of the smaller caches. */
snprintf(name, sizeof(name), "size-%Zd",sizes->cs_size);
if (!(sizes->cs_cachep =
从 cache_cache 中获取一个缓存器描述符,并且根据参数初始化一些必要字段。
kmem_cache_create(name, sizes->cs_size,
0, SLAB_HWCACHE_ALIGN, NULL, NULL))) {
BUG();
}
/* Inc off-slab bufctl limit until the ceiling is hit. */
if (!(OFF_SLAB(sizes->cs_cachep))) {
offslab_limit = sizes->cs_size-sizeof(slab_t);
offslab_limit /= 2;
}
snprintf(name, sizeof(name), "size-%Zd(DMA)",sizes->cs_size);
sizes->cs_dmacachep = kmem_cache_create(name, sizes->cs_size, 0,
SLAB_CACHE_DMA|SLAB_HWCACHE_ALIGN, NULL, NULL);
if (!sizes->cs_dmacachep)
BUG();
sizes++;
} while (sizes->cs_size);
}
do-while 循环中针对 cache_sizes 中的每一项调用 kmem_cache_creat 。 建立在
NORMAL 区以及 DMA 区的固定大小的缓存器。并且间接的进一步初始化 cache_cache。
总结初始化过程
由于 Slab Allocator 中需要缓存器来描述管理 Slab,并且”对象”都在 Slab 之上,所谓 Slab 就
是一组连续的页面,数量一定是二的幂次方。当系统初始化时,从 boot memory allocator
到 buddy allocator 都是一次分配一组页面,但初始化 Slab Allocator 时,可能就会有疑问,第
一个缓存器描述符从哪里来,因为缓存器描述符的大小远小于一个页面。所以肯定不能
从 buddy allocator 中动态的获取 , 于是乎 , 就只能先静态的定义一个缓存描述符 , 即
cache_cache。这样以后所需要的缓存器描述符就可以从这个专门管理缓存器描述符分
配的缓存器中分配了。当然初始化 cache_cache 时,这个缓存器还没有 slab。是当继续初
始化另外的数据结构时需要缓存器描述符时,发现 cache_cache 没有可用 Slab 时才会为
其分配一个 Slab。还需要注意一点,Slab 描述符分为 On-Slab 和 Off-Slab。意思就是存储
Slab 描述符有两种方案,一种是放在所分配的 Slab 的开始处,另外一种是放在另外的一个
地方,放在 Slab 的开始处很容易,可以放在另外的地方,这个另外的地方是哪里呢.在这里
就需要注意,查看源代码可以清楚 ,当”对象”的大小大于三个页面 ,并且满足一些列的额
外的条件,(具体参考源代码,这里只考虑初始化,不考虑其他调用所指定 CFLGS_OFF_SLAB
标识的情况)则 slab 描述符就是 Off_Slab。 又由于 slab 描述符的后面一定是 kmem_bufctl_t
类型的数组,而 kmem_bufctl_t 数组的大小又不是一定的,所以如果 slab 描述符是 Off_Slab,
所以就不能将 slab 描述符称为一个”对象”。 又考虑到也许有许多常用的结构并不一定是
定长的 , 所以 Slab Allocator 又建立了一个通用的固定大小的缓存 , 即 cache_sizes 。
cache_sizes 中包含许多缓存器,每个缓存器的”对象”的大小都是 2 的幂次方,当然既然是
通用,就不要求每个”对象”都恰好满足其大小,只要足够大能够容纳即可。这样就可以解
决 slab 描述符的位置是 Off_Slab 的问题了。
综上所述:
系统通过静态定义的 cache_cache 来提供缓存器描述符这个”对象”的分配,然后将那
些其管理的 Slab 的位置是 Off_Slab 的缓存器的 slab 描述符放在通用的缓存器中。
每当创建某缓存器时就可以从 cache_cache 这个缓存器中获取缓存器描述符”对象”,当第
一次从该缓存器上申请”对象”时,就会为其创建 Slab。
具体 Slab Allocator 如何分配以及管理”对象”在此不详述。
附---每 CPU”对象”缓存初始化
这个部分我并不认为是对 Slab Allocator 初始化,因为这个初始化是建立在 Slab Allocator
的基本结构完全初始化之后的。应成为关于 Slab Allocator 的每 CPU 结构初始化。
初始化的结构是 cachep->cpudata[]。
其初始化起点是由 init 进程调用 kmem_cpucache_init()所开始执行的。
具体代码不赘述(参考相关代码)
过程简述为:针对已建立的的每个缓存器,初始化其上的 cpudata[]结构。具体初始化过程
为,根据缓存器所管理的”对象”的大小,确定这个”对象”在每 CPU 上的规模,即数量。然后
从通用缓存器组中申请足以容纳 cpudata_t 结构以及所需管理”对象”的指针数组。并把
这块内存的起始地址填入以 CPU ID 为索引的 cachep->cpudata[]的元素中。 |
|