memcached学习之slabs部分

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;
}

猜你喜欢

转载自blog.csdn.net/fusan2004/article/details/51682532
今日推荐