分类: LINUX
2014-07-11 16:00:52
原文地址:Vi Linux内存 之 Slub分配器(八) 作者:palals
本节介绍slub分配器的初始化。
kmem_cache_init
初始化slub分配器。
void __init kmem_cache_init(void)
{
int i;
int caches = 0;
struct kmem_cache *temp_kmem_cache;
int order;
struct kmem_cache *temp_kmem_cache_node;
unsigned long kmalloc_size;
/* 计算struct kmem_cache对象大小,数据结构struct kmem_cache_node的作用与slab分配器的三链类似,只是做了封装,里面只有双链,去掉了空链 */
kmem_size = offsetof(struct kmem_cache, node) +
nr_node_ids * sizeof(struct kmem_cache_node *);
/* Allocate two kmem_caches from the page allocator */
/* 按cache line大小对齐 */
kmalloc_size = ALIGN(kmem_size, cache_line_size());
/* 首先通过页分配器分配两个struct kmem_cache临时对象,这里计算它们所占页块的order。此时struct kmem_cache本身的cache还没有建立起来,所以不能使用kmem_cache_alloc */
order = get_order(2 * kmalloc_size);
/* 全局变量kmem_cache指向第一个struct kmem_cache临时对象 */
kmem_cache = (void *)__get_free_pages(GFP_NOWAIT, order);
/*
* Must first have the slab cache available for the allocations of the
* struct kmem_cache_node's. There is special bootstrap code in
* kmem_cache_open for slab_state == DOWN.
*/
/* 全局变量kmem_cache_node指向第二个struct kmem_cache临时对象 */
kmem_cache_node = (void *)kmem_cache + kmalloc_size;
/* 初始化这个struct kmem_cache临时对象,这个cache用于保存struct kmem_cache_node对象 */
kmem_cache_open(kmem_cache_node, "kmem_cache_node",
sizeof(struct kmem_cache_node),
0, SLAB_HWCACHE_ALIGN | SLAB_PANIC, NULL);
/* 内存热插拔相关 */
hotplug_memory_notifier(slab_memory_callback, SLAB_CALLBACK_PRI);
/* Able to allocate the per node structures */
/* 修改slab初始化进度。建立好struct kmem_cache_node对象所在cache之后,即可使用它来分配struct kmem_cache_node对象了,不用再通过__get_free_pages分配了 */
slab_state = PARTIAL;
/* 指向第一个struct kmem_cache临时对象 */
temp_kmem_cache = kmem_cache;
/* 初始化这个struct kmem_cache临时对象,这个cache用于保存struct kmem_cache对象 */
kmem_cache_open(kmem_cache, "kmem_cache", kmem_size,
0, SLAB_HWCACHE_ALIGN | SLAB_PANIC, NULL);
/* 上面两个cache建立好后,就可以通过kmem_cache_alloc从里面分配对象,从而替换前面通过页分配器分配的临时对象。这一步骤与slab分配器类似,只不过slab分配器是静态分配的全局变量,slub分配器是__get_free_pages分配的。*/
/* 全局变量kmem_cache指向新分配的struct kmem_cache对象 */
kmem_cache = kmem_cache_alloc(kmem_cache, GFP_NOWAIT);
/* 对象复制 */
memcpy(kmem_cache, temp_kmem_cache, kmem_size);
/*
* Allocate kmem_cache_node properly from the kmem_cache slab.
* kmem_cache_node is separately allocated so no need to
* update any list pointers.
*/
/* 指向第二个struct kmem_cache临时对象 */
temp_kmem_cache_node = kmem_cache_node;
/* 全局变量kmem_cache_node指向新分配的struct kmem_cache对象 */
kmem_cache_node = kmem_cache_alloc(kmem_cache, GFP_NOWAIT);
/* 对象复制 */
memcpy(kmem_cache_node, temp_kmem_cache_node, kmem_size);
/* 初始化阶段,配置cache中的slab,使每个slab首页面的slab指针指向所在的cache节点 /
kmem_cache_bootstrap_fixup(kmem_cache_node);
/* cache数加一 */
caches++;
kmem_cache_bootstrap_fixup(kmem_cache);
caches++;
/* Free temporary boot structure */
/* 释放初始化阶段使用的临时页面 */
free_pages((unsigned long)temp_kmem_cache, order);
/* Now we can use the kmem_cache to allocate kmalloc slabs */
/* 至此,slub分配器正常运转需要的struct kmem_cache对象和struct kmem_cache_node对象的cache节点都已经初始化完毕,可以通过它们继续创建kmalloc所用的cache了 */
/*
* Patch up the size_index table if we have strange large alignment
* requirements for the kmalloc array. This is only the case for
* MIPS it seems. The standard arches will not generate any code here.
*
* Largest permitted alignment is 256 bytes due to the way we
* handle the index determination for the smaller caches.
*
* Make sure that nothing crazy happens if someone starts tinkering
* around with ARCH_KMALLOC_MINALIGN
*/
/* 有必要先了解一下几个重要的宏:
1) ARCH_DMA_MINALIGN:体系结构定义的DMA操作时的最小对齐值。通常是针对MIPS而言,可能的取值为:32字节、128字节等。
2) ARCH_KMALLOC_MINALIGN:如果定义了ARCH_DMA_MINALIGN,比如MIPS,那么取值就是ARCH_DMA_MINALIGN,否则取值为默认的8字节。这个宏定义了kmalloc cache中对象的最小对齐方式,比如取值128字节,那么kmalloc cache中所有对象都必须是128字节对齐的。Slub模型支持的这个宏的最大值为256字节。
3) KMALLOC_MIN_SIZE:如果定义了ARCH_DMA_MINALIGN,并且ARCH_DMA_MINALIGN大于8,比如MIPS,那么取值就是ARCH_DMA_MINALIGN,否则取值为默认的8字节。定义了kmalloc cache中对象的最小大小。比如取值为128字节,那么kmalloc cache中的最小对象大小是128字节,当申请一个60字节的对象时,需要到128字节的kmalloc cache中分配。
4) KMALLOC_SHIFT_LOW:根据KMALLOC_MIN_SIZE算出的最小对象在kmalloc_caches数组中的索引。
然后需要了解一下kmalloc的原理,如图所示:
我们以通常情况下KMALLOC_MIN_SIZE等于8为例进行说明。size_index[0-23]数组根据对象大小映射到不同的kmalloc_caches[0-13]保存的cache。观察kmalloc_caches[0-13]数组,可见索引即该cache slab块的order。
由于最小对象为8(23)字节,kmalloc_caches[0-2]这三个数组元素没有用到,slub使用kmalloc_caches[1]保存96字节大小的对象,kmalloc_caches[2] 保存192字节大小的对象,相当于细分了kmalloc的粒度,有利于减少空间的浪费。kmalloc_caches[0]未使用。
当申请一个对象时,首先确定该对象所属的kmalloc区间,即order。如果没有96、192这两个特殊的大小,那么就没有必要引入size_index[0-23]数组了,可直接通过对象大小得到order。由于这两个特殊大小kmalloc cache的存在,我们需要通过size_index[0-23]数组将某些大小的对象对应到kmalloc_caches[1-2]去。size_index[0-23]数组按照最小的对象大小KMALLOC_MIN_SIZE递增,如上图所示,72-96大小的对象都被对应到kmalloc_caches[1],136-192大小的对象都被对应到kmalloc_caches[2]。大于192的无需特别计算,直接计算order即可。
*/
/* 对KMALLOC_MIN_SIZE进行检查,不得大于256,必须是2的幂数 */
BUILD_BUG_ON(KMALLOC_MIN_SIZE > 256 ||
(KMALLOC_MIN_SIZE & (KMALLOC_MIN_SIZE - 1)));
/* 下面这些代码仅针对KMALLOC_MIN_SIZE大于8的情况,对size_index数组进行调整 */
/* 如果KMALLOC_MIN_SIZE大于8,size_index用于根据对象的大小,计算其对应的kmalloc_caches索引 */
for (i = 8; i < KMALLOC_MIN_SIZE; i += 8) {
/* 根据对象大小计算其在size_index数组中的索引 */
int elem = size_index_elem(i);
/* 索引超出size_index数组的范围 */
if (elem >= ARRAY_SIZE(size_index))
break;
/* 这些size_index数组的值都指向最小的kmalloc order。比如KMALLOC_MIN_SIZE为128,那么所有小于128的size_index都对应到27 kmalloc cache*/
size_index[elem] = KMALLOC_SHIFT_LOW;
}
if (KMALLOC_MIN_SIZE == 64) {
/*
* The 96 byte size cache is not used if the alignment
* is 64 byte.
*/
/* KMALLOC_MIN_SIZE为64时,说明对象的最小对齐大小为64,96大小的kmalloc cache无法满足要求,将原本映射到该cache的对象映射到128(27)kmalloc cache去 */
for (i = 64 + 8; i <= 96; i += 8)
size_index[size_index_elem(i)] = 7;
} else if (KMALLOC_MIN_SIZE == 128) {
/*
* The 192 byte sized cache is not used if the alignment
* is 128 byte. Redirect kmalloc to use the 256 byte cache
* instead.
*/
/* 原理同上,将原本映射到192 kmalloc cache的对象映射到128(27)kmalloc cache去 */
for (i = 128 + 8; i <= 192; i += 8)
size_index[size_index_elem(i)] = 8;
}
/* Caches that are not of the two-to-the-power-of size */
/* KMALLOC_MIN_SIZE小于等于32时,才创建96 kmalloc cache,大于32时96不满足对象对齐要求,无需创建 */
if (KMALLOC_MIN_SIZE <= 32) {
kmalloc_caches[1] = create_kmalloc_cache("kmalloc-96", 96, 0);
caches++;
}
/* KMALLOC_MIN_SIZE小于等于64时,才创建192 kmalloc cache,大于64时192不满足对象对齐要求,无需创建 */
if (KMALLOC_MIN_SIZE <= 64) {
kmalloc_caches[2] = create_kmalloc_cache("kmalloc-192", 192, 0);
caches++;
}
/* 这里创建kmalloc大小为23、24、…213 所对应的cache,分别保存在kmalloc_caches[3-13]中,注意这些cache名字相同,后面会重命名。当KMALLOC_MIN_SIZE大于8时,比如64,kmalloc_caches[3-5]未使用 */
for (i = KMALLOC_SHIFT_LOW; i < SLUB_PAGE_SHIFT; i++) {
kmalloc_caches[i] = create_kmalloc_cache("kmalloc", 1 << i, 0);
caches++;
}
/* 修改slub分配器的初始化状态为UP,表示kmalloc可用了 */
slab_state = UP;
/* Provide the correct kmalloc names now that the caches are up */
/* 前面创建kmalloc cache时,其name指针都指向一个常量字符串,需要为该指针分配一个存储空间 */
if (KMALLOC_MIN_SIZE <= 32) {
kmalloc_caches[1]->name = kstrdup(kmalloc_caches[1]->name, GFP_NOWAIT);
BUG_ON(!kmalloc_caches[1]->name);
}
if (KMALLOC_MIN_SIZE <= 64) {
kmalloc_caches[2]->name = kstrdup(kmalloc_caches[2]->name, GFP_NOWAIT);
BUG_ON(!kmalloc_caches[2]->name);
}
/* 这里不只分配存储空间,还按照大小重新命名 */
for (i = KMALLOC_SHIFT_LOW; i < SLUB_PAGE_SHIFT; i++) {
char *s = kasprintf(GFP_NOWAIT, "kmalloc-%d", 1 << i);
BUG_ON(!s);
kmalloc_caches[i]->name = s;
}
#ifdef CONFIG_SMP
/* 添加一个CPU通知链节点,当CPU下线时清除该CPU的local slab */
register_cpu_notifier(&slab_notifier);
#endif
#ifdef CONFIG_ZONE_DMA
/* 如果支持DMA,那么创建一组与kmalloc_caches相同的cache供DMA使用 */
for (i = 0; i < SLUB_PAGE_SHIFT; i++) {
struct kmem_cache *s = kmalloc_caches[i];
if (s && s->size) {
char *name = kasprintf(GFP_NOWAIT,
"dma-kmalloc-%d", s->objsize);
BUG_ON(!name);
kmalloc_dma_caches[i] = create_kmalloc_cache(name,
s->objsize, SLAB_CACHE_DMA);
}
}
#endif
printk(KERN_INFO
"SLUB: Genslabs=%d, HWalign=%d, Order=%d-%d, MinObjects=%d,"
" CPUs=%d, Nodes=%d\n",
caches, cache_line_size(),
slub_min_order, slub_max_order, slub_min_objects,
nr_cpu_ids, nr_node_ids);
}
kmem_cache_bootstrap_fixup
初始化阶段,配置cache中的slab。
static void __init kmem_cache_bootstrap_fixup(struct kmem_cache *s)
{
int node;
/* 将cache加入到全局cache链(由全局变量slab_caches保存) */
list_add(&s->list, &slab_caches);
/* slub模型初始化阶段创建的struct kmem_cache_node cache和struct kmem_cache cache不允许复用,设置refcount为-1,参见slab_unmergeable。而其他cache创建后,refcount为1,参见kmem_cache_open */
s->refcount = -1;
/* 遍历每个内存节点 */
for_each_node_state(node, N_NORMAL_MEMORY) {
/* 获得本节点的slab链 */
struct kmem_cache_node *n = get_node(s, node);
struct page *p;
if (n) {
/* 遍历部分满slab链,设置每个slab指向cache的指针 */
list_for_each_entry(p, &n->partial, lru)
p->slab = s;
#ifdef CONFIG_SLAB_DEBUG
/* 遍历满slab链,设置每个slab指向cache的指针 */
list_for_each_entry(p, &n->full, lru)
p->slab = s;
#endif
}
}
}
create_kmalloc_cache
创建kmalloc使用的cache。kmem_cache_create是基于kmalloc的,所以slub初始化阶段不能使用kmem_cache_create。这点与slab模型不同,slab模型的kmalloc cache也是通过kmem_cache_create创建的。
static struct kmem_cache *__init create_kmalloc_cache(const char *name,
int size, unsigned int flags)
{
struct kmem_cache *s;
/* 分配一个cache对象 */
s = kmem_cache_alloc(kmem_cache, GFP_NOWAIT);
/*
* This function is called with IRQs disabled during early-boot on
* single CPU so there's no need to take slub_lock here.
*/
/* 初始化这个cache */
if (!kmem_cache_open(s, name, size, ARCH_KMALLOC_MINALIGN,
flags, NULL))
goto panic;
/* 加入到全局cache链中 */
list_add(&s->list, &slab_caches);
return s;
panic:
panic("Creation of kmalloc slab %s size=%d failed.\n", name, size);
return NULL;
}
至此,Linux slub分配器已经介绍完毕,怎么样,是不是体会到了其相对于slab模型的优越之处。