Memcache源码阅读(4)---内存管理

来源:互联网 发布:淘宝买家诈骗怎么办 编辑:程序博客网 时间:2024/05/21 07:10

我看的源码版本是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的简图

memcached_slabclass_simple
注:这个图是简化版的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;

memcached_slabclass_detail
这里蓝色的表示已经被用户使用过的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,成功返回1static 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;}
0 0
原创粉丝点击