slabs部分是memcached的数据仓库,主要是负责memcached的内存空间的分配,提取以及释放,同时,实际上对这部分的操作是缓存系统的核心,整体上memcached的大部分代码都是围绕这部分来进行的,包括slabs_maintanance_thread,LRU算法,item的增删改查等等。在这里,我并不打算介绍其他部分,是因为后面会有单独章节来介绍item的行为,以及最最关键的LRU算法。另外有一点需要说明,整个系列都没有介绍slabs的maintanance的线程,这里面的功能主要是针对memcached的一个极端情况来处理的,那就是可能出现某个slabclass占据的内存从来没有数据来存储,而有些slabclass却一直分配不到内存空间,这时候需要将没有数据存储的slabclass内存转移给分配不到内存空间的slabclass,这部分由于时间问题,暂时不涉及。
slabs部分和assoc部分有点不同,对于memcached申请的内存来说,不再是增删改查的操作了,因为对于内存来说,只有使用和释放这种说法,不过这里的释放仅仅是说将该item对应的内存原来数据给清空,然后放到空闲列表中去,而不是真正地释放掉,这部分内存仍然是要被memcached保持和使用的,因此主要的操作实际上只有两个,分别是slabs_new和slabs_free操作。
typedef struct {
//chunk_size, 单个数据块的大小
unsigned int size; /* sizes of items */
//单个slab中包含的item数量,其实也就是chunk的数目
unsigned int perslab; /* how many items per slab */
//item指针的数组
void *slots; /* list of item ptrs */
//当前空闲的item数目
unsigned int sl_curr; /* total free items in list */
//当前层包含slab的数量
unsigned int slabs; /* how many slabs were allocated for this class */
//slab的指针数组
void **slab_list; /* array of slab pointers */
//slab的指针数组的大小
unsigned int list_size; /* size of prev array */
//当前请求的内存大小
size_t requested; /* The number of requested bytes */
} slabclass_t;
1、全局静态变量slabclass,上述slabclass_t的一个数组,也是memcached的数据仓库中心;
2、整型mem_limit, 设定的memcached使用的内存限制大小;
3、整型mem_malloced,已分配的内存大小;
4、布尔类型mem_limit_reached,默认是false, 这是一个信号,用于当内存限制大小被突破时启动LRU的maintenance线程;
5、整型power_largest,实际使用到的slabclass的最大层级;
6、指针mem_base,分配内存的基地址;
7、指针mem_current, 分配内存当前使用的地址;
8、整型mem_avail,当前分配内存的剩余可用大小;
9、互斥锁slabs_lock,用于进入分配slab的互斥锁;
1、slabs_init函数,确定slabclass中每层chunk的大小,初始化slabclass每层的配置,并根据参数确定是否预分配内存;
void slabs_init(const size_t limit, const double factor, const bool prealloc) {
int i = POWER_SMALLEST - 1; //初始化的起始层,默认是0
unsigned int size = sizeof(item) + settings.chunk_size; //获取最小层中配置的chunk的大小,为item结构体大小和chunk_size之和
mem_limit = limit; //设置内存限制
if (prealloc) { //如果预分配
/* Allocate everything in a big chunk with malloc */
mem_base = malloc(mem_limit); //一次性开辟mem_limit内存
if (mem_base != NULL) {
mem_current = mem_base; //尚未有数据存储,mem_current等于mem_base
mem_avail = mem_limit; //尚未有数据存储,可用空间即为设置的内存大小
} else {
fprintf(stderr, "Warning: Failed to allocate requested memory in"
" one large chunk.\nWill allocate in smaller chunks\n");
}
}
memset(slabclass, 0, sizeof(slabclass)); //初始化slabclass
//下面是根据配置情况对slabclass中每一层配置对应的数据
while (++i < MAX_NUMBER_OF_SLAB_CLASSES-1 && size <= settings.item_size_max / factor) {
/* Make sure items are always n-byte aligned */
if (size % CHUNK_ALIGN_BYTES)
size += CHUNK_ALIGN_BYTES - (size % CHUNK_ALIGN_BYTES); //不足凑齐
slabclass[i].size = size; //当前slabclass层中每个chunk的大小
slabclass[i].perslab = settings.item_size_max / slabclass[i].size; //每一个slab中chunk的数目
size *= factor; //每一层chunk的大小以factor倍数递增
if (settings.verbose > 1) {
fprintf(stderr, "slab class %3d: chunk size %9u perslab %7u\n",
i, slabclass[i].size, slabclass[i].perslab);
}
}
power_largest = i; //最大层,不一定是声明时的最大
slabclass[power_largest].size = settings.item_size_max; // 1M
slabclass[power_largest].perslab = 1; //只有一个chunk
if (settings.verbose > 1) {
fprintf(stderr, "slab class %3d: chunk size %9u perslab %7u\n",
i, slabclass[i].size, slabclass[i].perslab);
}
/* for the test suite: faking of how much we've already malloc'd */
{
char *t_initial_malloc = getenv("T_MEMD_INITIAL_MALLOC");
if (t_initial_malloc) {
mem_malloced = (size_t)atol(t_initial_malloc);
}
}
if (prealloc) {
slabs_preallocate(power_largest); //前面只申请了内存,并没有分别配置给slabclass里面的slab,调用这个函数实现真正分配内存
}
}
2、slabs_preallocate函数,主要用于为slabclass中每一层分配1M的存储空间;
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; i < MAX_NUMBER_OF_SLAB_CLASSES; i++) {
if (++prealloc > maxslabs) //分配到slabclass的实际最大层数即退出
return;
if (do_slabs_newslab(i) == 0) { //为给定的层级执行分配内存,每个slabclass包含 16个slab
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);
}
}
}
3、do_slabs_newslab函数;
static int do_slabs_newslab(const unsigned int id) {
slabclass_t *p = &slabclass[id];
slabclass_t *g = &slabclass[SLAB_GLOBAL_PAGE_POOL];
int len = settings.slab_reassign ? settings.item_size_max
: p->size * p->perslab; //计算需要的内存大小
char *ptr;
if ((mem_limit && mem_malloced + len > mem_limit && p->slabs > 0
&& g->slabs == 0)) {
mem_limit_reached = true; //超过内存限制
MEMCACHED_SLABS_SLABCLASS_ALLOCATE_FAILED(id);
return 0;
}
//下面的几个函数包含内容较多
//首先是grow_slab_list函数,是在该层slab全部被用或者初始化时扩充slab,初始化时是16个,扩充时是原来的2倍,扩充失败返回0,成功返回1,需要注意的是,这里面并不真正添加内存,添加内存空间主要是下面的两个函数完成
//其次是get_page_from_global_pool函数,这是从全局的内存池中获取一部分内存,这个内存获取失败返回NULL
//最后调用memory_allocate函数从之前预分配的内存空间去截取一段内存,注意,如果没有预分配的话,立即申请
if ((grow_slab_list(id) == 0) ||
(((ptr = get_page_from_global_pool()) == NULL) &&
((ptr = memory_allocate((size_t)len)) == 0))) {
MEMCACHED_SLABS_SLABCLASS_ALLOCATE_FAILED(id);
return 0;
}
memset(ptr, 0, (size_t)len); //初始化该部分内存
split_slab_page_into_freelist(ptr, id); //将该内存空间按照当前slabclass中chunk大小去释放情况
p->slab_list[p->slabs++] = ptr; //将该部分内存添加到 slab_list中去
MEMCACHED_SLABS_SLABCLASS_ALLOCATE(id);
return 1;
}
4、grow_slab_list函数,用于增长slab_list;
static int grow_slab_list (const unsigned int id) {
slabclass_t *p = &slabclass[id]; //获取对应层的slabclass的指针
if (p->slabs == p->list_size) { //如果分配的,等于使用的slab数目,重新分配
size_t new_size = (p->list_size != 0) ? p->list_size * 2 : 16; //初始化为16,否则为以前2倍
void *new_list = realloc(p->slab_list, new_size * sizeof(void *)); //重新申请,之前的不变
if (new_list == 0) return 0;
p->list_size = new_size;
p->slab_list = new_list;
}
return 1;
}
5、split_slab_page_into_freelist函数,将某部分内存按照item大小切分并存放到空闲列表中;
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对应的item清空,并添加到空闲列表slots中
ptr += p->size;
}
}
6、get_page_from_global_pool函数,从全局内存池中获取内存页;
static void *get_page_from_global_pool(void) {
slabclass_t *p = &slabclass[SLAB_GLOBAL_PAGE_POOL];
if (p->slabs < 1) { //已空,返回NULL
return NULL;
}
char *ret = p->slab_list[p->slabs - 1];
p->slabs--;
return ret;
}
7、do_slabs_alloc,分配一个内存空间给一个item;
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;
if (id < POWER_SMALLEST || id > power_largest) {
MEMCACHED_SLABS_ALLOCATE_FAILED(size, 0);
return NULL;
}
p = &slabclass[id];
assert(p->sl_curr == 0 || ((item *)p->slots)->slabs_clsid == 0);
if (total_chunks != NULL) {
*total_chunks = p->slabs * p->perslab; //计算当前slabclass层包含chunk的数量
}
/* 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) { //如果当前空闲的item为0,表示已经用完了内存空间
do_slabs_newslab(id); //申请加空间
}
if (p->sl_curr != 0) {
/* return off our freelist */
it = (item *)p->slots; //从slots中获取一个item
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;
it->refcount = 1; //添加引用次数
p->sl_curr--; // 可用item数目减1
ret = (void *)it;
} else {
ret = NULL;
}
if (ret) {
p->requested += size; //size固定
MEMCACHED_SLABS_ALLOCATE(size, id, p->size, ret);
} else {
MEMCACHED_SLABS_ALLOCATE_FAILED(size, id);
}
return ret;
}
8、do_slabs_free函数,释放当前指针的内存空间,并添加到空闲列表;
static void do_slabs_free(void *ptr, const size_t size, unsigned int id) {
slabclass_t *p;
item *it;
assert(id >= POWER_SMALLEST && id <= power_largest);
if (id < POWER_SMALLEST || id > power_largest)
return;
MEMCACHED_SLABS_FREE(size, id, ptr);
p = &slabclass[id];
it = (item *)ptr;
it->it_flags = ITEM_SLABBED;
it->slabs_clsid = 0;
it->prev = 0;
it->next = p->slots;
if (it->next) it->next->prev = it;
p->slots = it; //添加到空闲列表
p->sl_curr++; //可用加1
p->requested -= size;
return;
}
9、memory_allocate函数,分配size大小内存,可能是从预分配内存中获取;
static void *memory_allocate(size_t size) {
void *ret;
if (mem_base == NULL) { //没有预分配
/* We are not using a preallocated large memory chunk */
ret = malloc(size);
} else {
ret = mem_current;
if (size > mem_avail) {
return NULL;
}
/* mem_current pointer _must_ be aligned!!! */
if (size % CHUNK_ALIGN_BYTES) {
size += CHUNK_ALIGN_BYTES - (size % CHUNK_ALIGN_BYTES);
}
mem_current = ((char*)mem_current) + size; //预分配中获取
if (size < mem_avail) {
mem_avail -= size;
} else {
mem_avail = 0;
}
}
mem_malloced += size;
return ret;
}
10、slabs_alloc函数,从slabs中分配内存入口;
void *slabs_alloc(size_t size, unsigned int id, unsigned int *total_chunks,
unsigned int flags) {
void *ret;
pthread_mutex_lock(&slabs_lock); //这里开始加锁,全局,slabs仅此一个,可能有性能问题
ret = do_slabs_alloc(size, id, total_chunks, flags);
pthread_mutex_unlock(&slabs_lock);
return ret;
}
11、slabs_free函数,释放空间;
void slabs_free(void *ptr, size_t size, unsigned int id) {
pthread_mutex_lock(&slabs_lock);
do_slabs_free(ptr, size, id);
pthread_mutex_unlock(&slabs_lock);
}
12、slabs_clsid函数,根据待存储数据的大小,找到可以存储数据的slabclass的层;
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;
}