理解 Memcached 源码 - Slab III

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

板配给

首先,我们来看

do_slabs_alloc


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

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


void *slabs_alloc(size_t size, unsigned int id, unsigned int *total_chunks,
unsigned int flags) {
void *ret;

pthread_mutex_lock(&slabs_lock);
ret = do_slabs_alloc(size, id, total_chunks, flags);
pthread_mutex_unlock(&slabs_lock);
return ret;
}

slabs_alloc@slabs.cSourceRead


...
case 't':
settings.num_threads = atoi(optarg);
if (settings.num_threads <= 0) {
fprintf(stderr, "Number of threads must be greater than 0\n");
return 1;
}
/* There're other problems when you get above 64 threads.
* In the future we should portably detect # of cores for the
* default.
*/
if (settings.num_threads > 64) {
fprintf(stderr, "WARNING: Setting a high number of worker"
"threads is not recommended.\n"
" Set this value to the number of cores in"
" your machine or less.\n");
}
break;
...

main@memcached.c:5572SourceRead

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;
}
do_slabs_alloc@slabs.cSourceRead

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

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

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

3)没有空闲内存块时分配新 。这里很容易看到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;
}
do_slabs_alloc@slabs.cSourceRead

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


item *do_item_alloc(char *key, const size_t nkey, const unsigned int flags,
const rel_time_t exptime, const int nbytes,
const uint32_t cur_hv) {
...
unsigned int id = slabs_clsid(ntotal);
if (id == 0)
return 0;
...
it = slabs_alloc(ntotal, id, &total_chunks, 0);
...

do_item_alloc@items.cSourceRead

That's it. Did I make a serious mistake? or miss out on anything important? Or you simply like the read. Link me on -- I'd be chuffed to hear your feedback.