理解 Memcached源码 — Slab III
上次我们看完了内存分配,以及形成待分配列表(free list,即slots
)的过程。本篇我们继续查看如何使用建立好的数据结构来分配/回收块内存,并将它们用于存储对象。
首先,我们来看
do_slabs_alloc
这个函数对应讨论过的do_slabs_free.
这里do_slabs_alloc
的“公有”接口是slabs_alloc
。slabs_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_NEWPAGE
(flags
)即使在没有空闲内存块时也不会额外分配新的 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.