Memcached内存管理

slabs.{h,c}

slab的数据结构如下:

  1. typedef struct {  
  2.     unsigned int size;      /* sizes of items   每个item的大小*/  
  3.     unsigned int perslab;   /* how many items per slab   每个slabs中能容纳多少个item*/  
  4.   
  5.     void *slots;           /* list of item ptrs   空闲列表*/  
  6.     unsigned int sl_curr;   /* total free items in list  空闲列表空闲item个数*/  
  7.   
  8.     unsigned int slabs;     /* how many slabs were allocated for this class  已经分配来多少个slabs*/  
  9.   
  10.     void **slab_list;       /* array of slab pointers 指向一个void *数组,该数组指向实际分配的内存*/  
  11.     unsigned int list_size; /* size of prev array 最大允许slabs个数*/  
  12.   
  13.     unsigned int killing;  /* index+1 of dying slab, or zero if none */  
  14.     size_t requested; /* The number of requested bytes   这个slabclass中已经分配使用了多少bytes的内存*/  
  15. } slabclass_t;  

s

下面是网上找的一张图,从上面字段可以看出,已经没有来end_page_ptr这个指针

Memcached内存管理

slabs.c中几个重要的全局变量

  1. static slabclass_t slabclass[MAX_NUMBER_OF_SLAB_CLASSES];       //slabs数组   
  2. static size_t mem_limit = 0;                                                                   //最大的内存使用,超过就不分配内存   
  3. static size_t mem_malloced = 0;                                                          //当前已经从系统分配来多少内存  
下面看几个主要的函数

init函数

  1. /** 
  2.  * Determines the chunk sizes and initializes the slab class descriptors 
  3.  * accordingly. 
  4.  */  
  5. void slabs_init(const size_t limit, const double factor, const bool prealloc) {  
  6.     int i = POWER_SMALLEST - 1;  
  7.     unsigned int size = sizeof(item) + settings.chunk_size;  
  8.   
  9.     mem_limit = limit;  
  10.   
  11.     memset(slabclass, 0, sizeof(slabclass));  
  12.   
  13.     while (++i < POWER_LARGEST && size <= settings.item_size_max / factor) {  
  14.         /* Make sure items are always n-byte aligned */  
  15.         if (size % CHUNK_ALIGN_BYTES)  
  16.             size += CHUNK_ALIGN_BYTES - (size % CHUNK_ALIGN_BYTES);  
  17.   
  18.         slabclass[i].size = size;  
  19.         slabclass[i].perslab = settings.item_size_max / slabclass[i].size;  
  20.         size *= factor;  
  21.         if (settings.verbose > 1) {  
  22.             fprintf(stderr, "slab class %3d: chunk size %9u perslab %7u\n",  
  23.                     i, slabclass[i].size, slabclass[i].perslab);  
  24.         }  
  25.     }  
  26.   
  27.     power_largest = i;  
  28.     slabclass[power_largest].size = settings.item_size_max;  
  29.     slabclass[power_largest].perslab = 1;  
  30.     if (settings.verbose > 1) {  
  31.         fprintf(stderr, "slab class %3d: chunk size %9u perslab %7u\n",  
  32.                 i, slabclass[i].size, slabclass[i].perslab);  
  33.     }  
  34.   
  35.     /* for the test suite:  faking of how much we've already malloc'd */  
  36.     {  
  37.         char *t_initial_malloc = getenv("T_MEMD_INITIAL_MALLOC");  
  38.         if (t_initial_malloc) {  
  39.             mem_malloced = (size_t)atol(t_initial_malloc);  
  40.         }  
  41.   
  42.     }  
  43. }  
为了不影响看主要的逻辑,我已经将prealloc(预分配)部分的代码删除了。

limit是最大允许分配的空间,factor是增长因子,即每个slabclass的item size比上一个大 factor倍,不考虑对齐的话。

初始值

  1. unsigned int size = sizeof(item) + settings.chunk_size;  
item是个struct稍后看item的时候再解释,主要是作为链表元素使用,同时放实际的数据。

init函数主要就是设置来mem_limit, 每个slabclass的item大小和item数量,这里并没有使用prealloc,所有没有分配内存,每个slabclass的list_size和sl_curr都为0

alloc函数:

在slabclass[id]上分配size个字节

  1. void *slabs_alloc(size_t size, unsigned int id) {  
  2.     void *ret;  
  3.   
  4.     pthread_mutex_lock(&slabs_lock);  
  5.     ret = do_slabs_alloc(size, id);  
  6.     pthread_mutex_unlock(&slabs_lock);  
  7.     return ret;  
  8. }  
直接调用的do_slabs_alloc函数,是do_slabs_alloc的线程安全版吧,因为它操作的slabclass ,mem_limit都是全局static的变量
  1. static void *do_slabs_alloc(const size_t size, unsigned int id) {  
  2.     slabclass_t *p;  
  3.     void *ret = NULL;  
  4.     item *it = NULL;  
  5.   
  6.     //确保id在有效范围   
  7.     if (id < POWER_SMALLEST || id > power_largest) {  
  8.         MEMCACHED_SLABS_ALLOCATE_FAILED(size, 0);  
  9.         return NULL;  
  10.     }  
  11.   
  12.     p = &slabclass[id];  
  13.     assert(p->sl_curr == 0 || ((item *)p->slots)->slabs_clsid == 0);                 //要么sl_curr空闲列表为空,不为空时slots指向的头一个item的cslid值必须为0   
  14.   
  15.     /* fail unless we have space at the end of a recently allocated page, 
  16.        we have something on our freelist, or we could allocate a new page */  
  17.   
  18.      //如果空闲列表不为空,直接从空闲列表中分配   
  19.      //如果空闲列表为空,则do_slabs_newslab来增加该slabclass的slabs   
  20.      //    如果do_slabs_newslab失败则返回null     
  21.    if (! (p->sl_curr != 0 || do_slabs_newslab(id) != 0)) {  
  22.         /* We don't have more memory available */  
  23.         ret = NULL;  
  24.     } else if (p->sl_curr != 0) {  
  25.         /* return off our freelist */  
  26.         //返回slots,slots指向下一个item   
  27.         it = (item *)p->slots;  
  28.         p->slots = it->next;  
  29.         if (it->next) it->next->prev = 0;  
  30.         p->sl_curr--;  
  31.         ret = (void *)it;  
  32.     }  
  33.   
  34.     if (ret) {  
  35.         p->requested += size;  
  36.         MEMCACHED_SLABS_ALLOCATE(size, id, p->size, ret);  
  37.     } else {  
  38.         MEMCACHED_SLABS_ALLOCATE_FAILED(size, id);  
  39.     }  
  40.   
  41.     return ret;  
  42. }  
函数的逻辑在代码注释中也解释得差不多,就是返回slots指向的item,并使slots指向下一个item,,可以看出其实slots指向的是一个item为元素的双向链表

如果slots为空,则需要调用do_slabs_newslab来分配一个新的slab

  1. static int do_slabs_newslab(const unsigned int id) {  
  2.     slabclass_t *p = &slabclass[id];  
  3.     int len = settings.slab_reassign ? settings.item_size_max  
  4.         : p->size * p->perslab;  
  5.     char *ptr;  
  6.       
  7.       
  8.       
  9.     //成功:如果没有超出mem_limit,并且grow_slab_list返回1,则调用memory_allocate分配内存   
  10.     //其余情况似失败,返回0   
  11.     if ((mem_limit && mem_malloced + len > mem_limit && p->slabs > 0) ||  
  12.         (grow_slab_list(id) == 0) ||  
  13.         ((ptr = memory_allocate((size_t)len)) == 0)) {  
  14.   
  15.         MEMCACHED_SLABS_SLABCLASS_ALLOCATE_FAILED(id);  
  16.         return 0;  
  17.     }  
  18.   
  19.     //将新分配的内存全部置0,通过split_slab_page_into_freelist,将新分配的内存放到slots中   
  20.     //这就是为什么取消来end_page_ptr的原因了   
  21.     memset(ptr, 0, (size_t)len);  
  22.     split_slab_page_into_freelist(ptr, id);  
  23.   
  24.     //slabs加1   
  25.     p->slab_list[p->slabs++] = ptr;  
  26.     mem_malloced += len;  
  27.     MEMCACHED_SLABS_SLABCLASS_ALLOCATE(id);  
  28.   
  29.     return 1;  
  30. }  
再看看grow_slab_list
  1. static int grow_slab_list (const unsigned int id) {  
  2.     slabclass_t *p = &slabclass[id];  
  3.       
  4.    //slabs == list_size表明已经到了分配的最大slabs个数来,需要realloc来重新分配空间   
  5.     if (p->slabs == p->list_size) {  
  6.   
  7.         //*2倍增长,初始为16   
  8.        //即初始时,slab_list指向一个函数16个void *的数组   
  9.         size_t new_size =  (p->list_size != 0) ? p->list_size * 2 : 16;  
  10.         void *new_list = realloc(p->slab_list, new_size * sizeof(void *));  
  11.         if (new_list == 0) return 0;  
  12.         p->list_size = new_size;  
  13.         p->slab_list = new_list;  
  14.     }  
  15.     return 1;  
  16. }  
1表示成功,0表示重新分配slab_list指向的void *数组失败,则list_size不能增长。这个分配的是指向实际chunk块的void *数组(slabs),并不是真正分配供item使用的内存的地方。可以参考上面的图片。

memory_allocate这个函数最简单来,就是调用malloc来分配内存

  1. static void *memory_allocate(size_t size) {  
  2.     void *ret;  
  3.   
  4.     if (mem_base == NULL) {  
  5.         /* We are not using a preallocated large memory chunk */  
  6.         ret = malloc(size);  
  7.     } else {  
  8.         ret = mem_current;  
  9.   
  10.         if (size > mem_avail) {  
  11.             return NULL;  
  12.         }  
  13.   
  14.         /* mem_current pointer _must_ be aligned!!! */  
  15.         if (size % CHUNK_ALIGN_BYTES) {  
  16.             size += CHUNK_ALIGN_BYTES - (size % CHUNK_ALIGN_BYTES);  
  17.         }  
  18.   
  19.         mem_current = ((char*)mem_current) + size;  
  20.         if (size < mem_avail) {  
  21.             mem_avail -= size;  
  22.         } else {  
  23.             mem_avail = 0;  
  24.         }  
  25.     }  
  26.   
  27.     return ret;  
  28. }  
没有prealloc的情况下,实际执行的只是
  1. ret = malloc(size);  
  2.   
  3. return ret;  

分配的最后一个函数,将新分配的内存直接放到slots中

  1. static void split_slab_page_into_freelist(char *ptr, const unsigned int id) {  
  2.     slabclass_t *p = &slabclass[id];  
  3.     int x;  
  4.     for (x = 0; x < p->perslab; x++) {  
  5.         do_slabs_free(ptr, 0, id);  
  6.         ptr += p->size;  
  7.     }  
  8. }  
就是每p->size个字节为一个item放到slots中,通过调用do_slabs_free来实现。这个函数在后面解释。

到此为止,slab分配全部完成。

free函数

free并不是将内存返回给系统,而是将不使用的item占用的空间重新放到相对应的slabclass的slots中

  1. /** Free previously allocated object */  
  2. void slabs_free(void *ptr, size_t size, unsigned int id) {  
  3.     pthread_mutex_lock(&slabs_lock);  
  4.     do_slabs_free(ptr, size, id);  
  5.     pthread_mutex_unlock(&slabs_lock);  
  6. }  
跟slabs_alloc一样,调用do_slabs_free来完成,是线程安全的。
  1. static void do_slabs_free(void *ptr, const size_t size, unsigned int id) {  
  2.     slabclass_t *p;  
  3.     item *it;  
  4.   
  5.     assert(((item *)ptr)->slabs_clsid == 0);  
  6.     assert(id >= POWER_SMALLEST && id <= power_largest);  
  7.     if (id < POWER_SMALLEST || id > power_largest)  
  8.         return;  
  9.   
  10.     MEMCACHED_SLABS_FREE(size, id, ptr);  
  11.     p = &slabclass[id];  
  12.   
  13.     it = (item *)ptr;  
  14.     it->it_flags |= ITEM_SLABBED;  
  15.     it->prev = 0;  
  16.     it->next = p->slots;  
  17.     if (it->next) it->next->prev = it;  
  18.     p->slots = it;  
  19.   
  20.     p->sl_curr++;  
  21.     p->requested -= size;  
  22.     return;  
  23. }  

可以看出,是将ptr指向的内存空间加到slots的头,slots指向这个ptr,即ptr变成slots的头item。修改相应的属性如iitem->it_flags |= ITEM_SLABBED

sl_curr加1等。

这就是大概memcached的内存池吧。

相关推荐