memcached学习之slabs部分

来源:互联网 发布:怎么样注册淘宝网 编辑:程序博客网 时间:2024/05/01 05:03

slabs部分的功能介绍

slabs部分是memcached的数据仓库,主要是负责memcached的内存空间的分配,提取以及释放,同时,实际上对这部分的操作是缓存系统的核心,整体上memcached的大部分代码都是围绕这部分来进行的,包括slabs_maintanance_thread,LRU算法,item的增删改查等等。在这里,我并不打算介绍其他部分,是因为后面会有单独章节来介绍item的行为,以及最最关键的LRU算法。另外有一点需要说明,整个系列都没有介绍slabs的maintanance的线程,这里面的功能主要是针对memcached的一个极端情况来处理的,那就是可能出现某个slabclass占据的内存从来没有数据来存储,而有些slabclass却一直分配不到内存空间,这时候需要将没有数据存储的slabclass内存转移给分配不到内存空间的slabclass,这部分由于时间问题,暂时不涉及。

slabs部分的整体逻辑

内部存储结构图

这里写图片描述

slabs部分操作介绍

slabs部分和assoc部分有点不同,对于memcached申请的内存来说,不再是增删改查的操作了,因为对于内存来说,只有使用和释放这种说法,不过这里的释放仅仅是说将该item对应的内存原来数据给清空,然后放到空闲列表中去,而不是真正地释放掉,这部分内存仍然是要被memcached保持和使用的,因此主要的操作实际上只有两个,分别是slabs_new和slabs_free操作。

slabs部分的代码分解

自定义类型

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;}
0 0
原创粉丝点击