理解 Memcached源码 — Slab III

Holmes He
source code
Published in
5 min readMar 24, 2019
Photo by Sharon McCutcheon on Unsplash

上次我们看完了内存分配,以及形成待分配列表(free list,即slots)的过程。本篇我们继续查看如何使用建立好的数据结构来分配/回收块内存,并将它们用于存储对象

首先,我们来看

do_slabs_alloc

这个函数对应讨论过的do_slabs_free.

这里do_slabs_alloc的“公有”接口是slabs_allocslabs_alloc除了提供了对外接口外,还对核心数据结构加了线程锁以保证此函数(在Memcached被配置为多线程时,multithreaded)线程安全。

static void *do_slabs_alloc(const size_t size, unsigned int id, unsigned int *total_chunks,
unsigned int flags) {
slabclass_t *p;
void *ret = NULL;
item *it = NULL;
...
p = &slabclass[id]; // scr: -------------------------------> 1)
...
if (total_chunks != NULL) {
*total_chunks = p->slabs * p->perslab; // scr: --------> 2)
}
/* fail unless we have space at the end of a recently allocated page,
we have something on our freelist, or we could allocate a new page */
if (p->sl_curr == 0 && flags != SLABS_ALLOC_NO_NEWPAGE) { // scr: --> *)
do_slabs_newslab(id); // scr: -------------------------> 3)
}

if (p->sl_curr != 0) {
/* return off our freelist */
it = (item *)p->slots; // scr: ------------------------> 4)
p->slots = it->next;
if (it->next) it->next->prev = 0;
/* Kill flag and initialize refcount here for lock safety in slab
* mover's freeness detection. */
it->it_flags &= ~ITEM_SLABBED; // scr: ----------------> 5)
it->refcount = 1;
p->sl_curr--;
ret = (void *)it; // scr: -----------------------------> 6)
} else {
ret = NULL;
}

...
return ret;
}

1)id代表 Slab组。之前提到过,不同大小的对象会用不同的 Slab组 来存储。换句话说,id 的值由对象大小决定。这个过程后面会讨论。

2)total_chunks 是出参,用于存储当前Slab组的空闲内存块memory chunk),或者说是待分配列表里还有多少空位。if (total_chunks != NULL) 则说明这个是可选参数。

*)和字面意思一样,SLABS_ALLOC_NO_NEWPAGEflags)即使在没有空闲内存块时也不会额外分配新的 Slab 来满足后续分配需要。这个选项并不属于对象分配的通常路径,所以暂时忽略。

3)没有空闲内存块时分配新 Slab。这里很容易看到p->sl_curr 表示空闲内存块的数量。这个变量的值会在每次调用这个函数时递减(看第5步)。

另一方面, 这个字段在do_slabs_free里自增。 注意 new slab这篇也提到过。

4)从待分配列表(free list,即slots)表头干掉一个元素(f),并将其赋值给it

do_slabs_free, 内存块也是从表头加入的。

5) 清除对应内存块(f)的ITEM_SLABBED标志,将引用次数设置为1,并且内存块的数量p->sl_curr 减少1

同样,这个标志在do_slabs_free中被设置。

6) 返回(f).

下面我们来看如何通过对象大小来决定id,对应的函数是

slabs_clsid

unsigned int slabs_clsid(const size_t size) {
int res = POWER_SMALLEST;

if (size == 0)
return 0;
while (size > slabclass[res].size)
if (res++ == power_largest) /* won't fit in the biggest slab */
return 0;
return res;
}

slabs_clsid 主要由一个 while 循环组成,这个循环会渐次找到最小的Slab组来刚好达到申请对象的大小要求。这个函数是在 do_item_alloc 中先于 slabs_alloc 被调用。 我们会在后面的文章中讨论 do_item_alloc

Originally published at holmeshe.me.

--

--