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

19 篇文章 0 订阅
10 篇文章 0 订阅

我看的源码版本是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,成功返回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;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值