Memcache源码阅读(4)---内存管理
我看的源码版本是1.2.4
memcached的存储
memcached拥有一个内存池,内存池中的内存分成多种大小的chunk,chunk的大小有一个基础大小(最小的chunk大小,base chunk size),然后后面大小的是以settings.factor
为因子不断的增大。比如说base chunk size为64byte, setting.factor = 1.5
,那么memcached拥有的chunk size有64byte
, 64 * 1.5 = 96byte
, 96 * 1.5 = 144byte
, … 一直到页的大小的1/2。
需要存储数据的时候,从slab中找一个最小匹配的chunk来存储数据。比如说,我现在有个数据大小是112byte,那么memcached就会找一个最小的、能容下这个数据的chunk来存储这个数据(144byte)。
这样做虽然有内存浪费,但是好处是不需要管理内存碎片的问题。
slabclass_t的简图
注:这个图是简化版的slabclass,我将slab_list看为一个管理着chunk的数组,事实上,他只是管理着多个slab,slab再管理着chunk而已。
如上图,用户向slabclass请求内存,memcached先会到对应大小到的slabclass_t中查看slot是否有空闲chunk,如果有的话,就从空闲数组中分配;没有,则会从slab中分配一个没用过的。当用户释放slab分配的chunk时,则会将它放到slabclass_t的slot里。
slab的结构
slab的结构体
typedef struct {
unsigned int size; /* sizes of items */ //就是chunk的大小
unsigned int perslab; /* how many items per slab */ //每个slab中有多少个chunk,它会管理着一个slab_list,slab_list里面有多个slab,每个slab管理着perslab个chunk
void **slots; /* list of item ptrs */ //指向空闲数组,回收释放的chunk
unsigned int sl_total; /* size of previous array */ //空闲数组的大小
unsigned int sl_curr; /* first free slot */ //指向第一个没有回收chunk的空闲数组。
void *end_page_ptr; /* pointer to next free item at end of page, or 0 */
unsigned int end_page_free; /* number of items remaining at end of last alloced page */
unsigned int slabs; /* how many slabs were allocated for this class */
void **slab_list; /* array of slab pointers */
unsigned int list_size; /* size of prev array */
unsigned int killing; /* index+1 of dying slab, or zero if none */
} slabclass_t;
这里蓝色的表示已经被用户使用过的chunk,即使它被用户释放,它只会放到slot中,end_page_ptr并不会管理那些申请了的后来被用户释放的chunk,end_page_ptr指向第一个还没被用过的chunk。
slabs初始化
slabs_init(settings.maxbytes, settings.factor);
#define POWER_SMALLEST 1
#define POWER_LARGEST 200
#define POWER_BLOCK 1048576 //1 M
#define CHUNK_ALIGN_BYTES (sizeof(void *))
static slabclass_t slabclass[POWER_LARGEST + 1];
static size_t mem_limit = 0;
static size_t mem_malloced = 0;
static int power_largest;
void slabs_init(const size_t limit, const double factor) {
int i = POWER_SMALLEST - 1;
unsigned int size = sizeof(item) + settings.chunk_size; //这里是对用户设定的大小加大一个item结构体的大小,因为存储数据的时候,用户数据是存在item结构体之后,item结构体记录着这个数据的一些信息。
/* Factor of 2.0 means use the default memcached behavior */
if (factor == 2.0 && size < 128)
size = 128;
mem_limit = limit;
memset(slabclass, 0, sizeof(slabclass));
while (++i < POWER_LARGEST && size <= POWER_BLOCK / 2) {
/* 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[i].perslab = POWER_BLOCK / slabclass[i].size;
size *= factor;
}
power_largest = i;
slabclass[power_largest].size = POWER_BLOCK;
slabclass[power_largest].perslab = 1;
}
默认情况下这样就初始化完了,设置好了slabclass的信息,没有给slabs真正开辟存储空间。当用户向slabclass申请空间的时候,memcached发现slab没有可用空间再向系统申请空间。那我们看看初始化时到底是如何给slabs分配内存空间的,memcached发现slab没有可用空间时申请内存空间的方式跟这个也类似。
初始化时预先分配slabs
//为每个chunk size大小的slab_class分配空间
static void slabs_preallocate (const unsigned int maxslabs) {
int i;
unsigned int prealloc = 0;
for (i = POWER_SMALLEST; i <= POWER_LARGEST; i++) {
if (++prealloc > maxslabs)
return;
do_slabs_newslab(i);
}
}
//为第i个slab_class分配空间,分配失败的时候返回0,成功返回1
static int do_slabs_newslab(const unsigned int id) {
slabclass_t *p = &slabclass[id];
int len = p->size * p->perslab;
char *ptr;
if (mem_limit && mem_malloced + len > mem_limit && p->slabs > 0)
return 0;
//grow slab list返回0的时候是失败
if (grow_slab_list(id) == 0) return 0;
ptr = malloc((size_t)len);
if (ptr == 0) return 0;
memset(ptr, 0, (size_t)len);
//指向第一个没有分配的chunk,slab分配chunk是从slab_list里顺序分配的。
p->end_page_ptr = ptr;
p->end_page_free = p->perslab;
//为每个slab分配一个1M的page
p->slab_list[p->slabs++] = ptr;
mem_malloced += len;
return 1;
}
//为slab_list添加一个slab,需要看看slab_list是否已满。
//如果满了,就需要分配更大的slab_list来装下slabs。如果满了,重新分配空间失败返回0。
static int grow_slab_list (const unsigned int id) {
slabclass_t *p = &slabclass[id];
if (p->slabs == p->list_size) {
size_t new_size = (p->list_size != 0) ? p->list_size * 2 : 16;
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;
}
为用户从slab中找个合适大小的chunk
void *do_slabs_alloc(const size_t size) {
slabclass_t *p;
//找到最小的能装下size的slab
unsigned int id = slabs_clsid(size);
p = &slabclass[id];
//如果slot中又空闲的chunk,那就从slot中取一个
/* return off our freelist, if we have one */
if (p->sl_curr != 0)
return p->slots[--p->sl_curr];
//否则我们就从slab_list中取一个
/* if we recently allocated a whole page, return from that */
if (p->end_page_ptr) {
void *ptr = p->end_page_ptr;
if (--p->end_page_free != 0) {
p->end_page_ptr += p->size;
} else {
p->end_page_ptr = 0;
}
return ptr;
}
return NULL; /* shouldn't ever get here */
}
用户释放chunk
void do_slabs_free(void *ptr, const size_t size) {
unsigned char id = slabs_clsid(size);
slabclass_t *p;
p = &slabclass[id];
//如果slot中已经装满了空闲chunk,那么就得给slot分配一个更大的数组,否则直接将空闲chunk放到slot中
if (p->sl_curr == p->sl_total) { /* need more space on the free list */
int new_size = (p->sl_total != 0) ? p->sl_total * 2 : 16; /* 16 is arbitrary */
void **new_slots = realloc(p->slots, new_size * sizeof(void *));
if (new_slots == 0)
return;
p->slots = new_slots;
p->sl_total = new_size;
}
p->slots[p->sl_curr++] = ptr;
return;
}
还没有评论,来说两句吧...