理解 Memcached 源码— Slab II
这次我们继续看用于 Slab 的内存是如何分配的。
首先我们继续看 slabs_init
的两个实参。第一个是 settings.maxbytes
— 控制这个 Memcached 实例可以使用的总内存大小。在传入 slabs_init
之前,这个参数被赋值为全局变量 mem_limit
。
另外一个怎是 preallocate
。它决定了是否为(各个)Slab组 预分配 内存。这个参数的值由 L
命令行参数来决定。
下面我们来看 slabs 的内存分配函数。
New slab
具体来说,这个函数用于给 Slab组 分配大小为1M的内存块(slab)。而 Slab组 由参数 id
指定。
static int do_slabs_newslab(const unsigned int id) {
slabclass_t *p = &slabclass[id]; // scr: ------------------> 1)
slabclass_t *g = &slabclass[SLAB_GLOBAL_PAGE_POOL]; // scr:> *)
int len = settings.slab_reassign ? settings.item_size_max // scr: ---> 2)
: p->size * p->perslab;
char *ptr;
if ((mem_limit && mem_malloced + len > mem_limit && p->slabs > 0 // -> 3)
&& g->slabs == 0)) {
mem_limit_reached = true;
MEMCACHED_SLABS_SLABCLASS_ALLOCATE_FAILED(id);
return 0;
}
if ((grow_slab_list(id) == 0) || // scr: -----------------> 4)
(((ptr = get_page_from_global_pool()) == NULL) && // -> *)
((ptr = memory_allocate((size_t)len)) == 0))) { //scr:> 5)
MEMCACHED_SLABS_SLABCLASS_ALLOCATE_FAILED(id);
return 0;
}
memset(ptr, 0, (size_t)len);
split_slab_page_into_freelist(ptr, id); // scr: ----------> 6)
p->slab_list[p->slabs++] = ptr; // scr: ------------------> 7)
MEMCACHED_SLABS_SLABCLASS_ALLOCATE(id);
return 1;
}
1)slabclass[id]
是 Slab组 的数据结构。上篇讨论了这个数组的初始化。
2)settings.slab_reassign
决定是否启用 再平衡 策略。如果启用,未使用的 slabs 不会被立即释放,而是分配给其他 Slab组 使用,这就产生了一个问题,即所有 Slab组 都需要使用统一大小的 slab。所以这个设置同时也决定了是否使用 同种slab (大小为 settings.item_size_max
,或者上述的1M),还是 异种slab (p->size * p->perslab
)。除了用命令行参数 "slab_reassign
" 以外,"modern
" 也会设置这个值,而本文也会用1M作为 slab 的大小。
N.b. *, rebalancing mechanism will be discussed later when we have a better understanding of the LRU module.
3)检查内存使用是否超出上线。
4)grow_slab_list
检查是否增长 slabclass_t.slab_list
,如果需要,则增长之。
5)memory_allocate
是真正分配 slab 内存的函数。如上述,这里的 len
是1M。
6)split_slab_page_into_freelist
初始化 (或者是 free)刚刚分配的 slab 内存用作对象存储。这个函数会在下一节讨论。
7) 将刚刚分配的 slab 加入到 slabclass_t.slab_list
.
下图总结了这个过程(我们想象 do_slabs_newslab(n)
被调用了两次)
接下来我们来看在第6)步中一块 slab 是如何被初始化的。
split_slab_page_into_freelist
static void split_slab_page_into_freelist(char *ptr, const unsigned int id) {
slabclass_t *p = &slabclass[id];
int x;
for (x = 0; x < p->perslab; x++) {
do_slabs_free(ptr, 0, id);
ptr += p->size;
}
}
这个函数会遍历 slab 里的所有 item 块(slabclass_t.size
),然后调用 do_slabs_free
来初始化每个 item 块的元数据。换一个说法,就是 “拆分 slab到待分配列表”-“split a slab into item free list”。你也许已经猜到了,这个 待分配列表 会被直接用于 对象分配,这个过程后面会详细讨论。
do_slabs_free
static void do_slabs_free(void *ptr, const size_t size, unsigned int id) {
slabclass_t *p;
item *it;
...
p = &slabclass[id];
it = (item *)ptr;
it->it_flags = ITEM_SLABBED; // scr: ---------------> 1)
it->slabs_clsid = 0;
it->prev = 0; // scr: ------------------------------> 2)
it->next = p->slots;
if (it->next) it->next->prev = it;
p->slots = it;
p->sl_curr++; // scr: ------------------------------> 3)
p->requested -= size;
return;
}
技术上讲,这个函数处理的 元数据 元数据存在于每个 item 块的开始。
1)初始化一些域。这里 item
是另一个核心数据结构,后续会讨论。
2)将 item 加入到上述的 待分配列表 ,并且更新链表表头,slabclass_t.slots
。
3)更新可分配项目数量,slabclass_t.sl_curr
;并且更新 slabclass_t.requested
负责统计。注意这里并没有真正的释放对象,所以传入的 size
是0
。
Slab preallocate
下面我们来看 do_slabs_newslab
怎么使用。其中一个地方是之前看到过的 slabs_init(preallocate
设置为 true
),
static void slabs_preallocate (const unsigned int maxslabs) {
int i;
unsigned int prealloc = 0;
/* pre-allocate a 1MB slab in every size class so people don't get
confused by non-intuitive "SERVER_ERROR out of memory"
messages. this is the most common question on the mailing
list. if you really don't want this, you can rebuild without
these three lines. */
for (i = POWER_SMALLEST /* scr: 1 */; i < MAX_NUMBER_OF_SLAB_CLASSES; i++) {
if (++prealloc > maxslabs)
return;
if (do_slabs_newslab(i) == 0) {
fprintf(stderr, "Error while preallocating slab memory!\n"
"If using -L or other prealloc options, max memory must be "
"at least %d megabytes.\n", power_largest);
exit(1);
}
}
}
这个方法从POWER_SMALLEST
(1)开始遍历所有的 slabclass,然后给每个 Slab组 预分配一个 slab。(下标为0th
的 Slab组 是一个特殊的组,存储空闲的 slab 用于上面提到的 再平衡 策略)。
References
和上文一样。
Originally published at holmeshe.me.