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

拼搏现实的明天。 2022-07-13 01:13 278阅读 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的结构体

  1. typedef struct {
  2. unsigned int size; /* sizes of items */ //就是chunk的大小
  3. unsigned int perslab; /* how many items per slab */ //每个slab中有多少个chunk,它会管理着一个slab_list,slab_list里面有多个slab,每个slab管理着perslab个chunk
  4. void **slots; /* list of item ptrs */ //指向空闲数组,回收释放的chunk
  5. unsigned int sl_total; /* size of previous array */ //空闲数组的大小
  6. unsigned int sl_curr; /* first free slot */ //指向第一个没有回收chunk的空闲数组。
  7. void *end_page_ptr; /* pointer to next free item at end of page, or 0 */
  8. unsigned int end_page_free; /* number of items remaining at end of last alloced page */
  9. unsigned int slabs; /* how many slabs were allocated for this class */
  10. void **slab_list; /* array of slab pointers */
  11. unsigned int list_size; /* size of prev array */
  12. unsigned int killing; /* index+1 of dying slab, or zero if none */
  13. } slabclass_t;

memcached\_slabclass\_detail
这里蓝色的表示已经被用户使用过的chunk,即使它被用户释放,它只会放到slot中,end_page_ptr并不会管理那些申请了的后来被用户释放的chunk,end_page_ptr指向第一个还没被用过的chunk。

slabs初始化

  1. slabs_init(settings.maxbytes, settings.factor);
  2. #define POWER_SMALLEST 1
  3. #define POWER_LARGEST 200
  4. #define POWER_BLOCK 1048576 //1 M
  5. #define CHUNK_ALIGN_BYTES (sizeof(void *))
  6. static slabclass_t slabclass[POWER_LARGEST + 1];
  7. static size_t mem_limit = 0;
  8. static size_t mem_malloced = 0;
  9. static int power_largest;
  10. void slabs_init(const size_t limit, const double factor) {
  11. int i = POWER_SMALLEST - 1;
  12. unsigned int size = sizeof(item) + settings.chunk_size; //这里是对用户设定的大小加大一个item结构体的大小,因为存储数据的时候,用户数据是存在item结构体之后,item结构体记录着这个数据的一些信息。
  13. /* Factor of 2.0 means use the default memcached behavior */
  14. if (factor == 2.0 && size < 128)
  15. size = 128;
  16. mem_limit = limit;
  17. memset(slabclass, 0, sizeof(slabclass));
  18. while (++i < POWER_LARGEST && size <= POWER_BLOCK / 2) {
  19. /* Make sure items are always n-byte aligned */
  20. //进行字节对齐
  21. if (size % CHUNK_ALIGN_BYTES)
  22. size += CHUNK_ALIGN_BYTES - (size % CHUNK_ALIGN_BYTES);
  23. slabclass[i].size = size;
  24. slabclass[i].perslab = POWER_BLOCK / slabclass[i].size;
  25. size *= factor;
  26. }
  27. power_largest = i;
  28. slabclass[power_largest].size = POWER_BLOCK;
  29. slabclass[power_largest].perslab = 1;
  30. }

默认情况下这样就初始化完了,设置好了slabclass的信息,没有给slabs真正开辟存储空间。当用户向slabclass申请空间的时候,memcached发现slab没有可用空间再向系统申请空间。那我们看看初始化时到底是如何给slabs分配内存空间的,memcached发现slab没有可用空间时申请内存空间的方式跟这个也类似。

初始化时预先分配slabs

  1. //为每个chunk size大小的slab_class分配空间
  2. static void slabs_preallocate (const unsigned int maxslabs) {
  3. int i;
  4. unsigned int prealloc = 0;
  5. for (i = POWER_SMALLEST; i <= POWER_LARGEST; i++) {
  6. if (++prealloc > maxslabs)
  7. return;
  8. do_slabs_newslab(i);
  9. }
  10. }
  11. //为第i个slab_class分配空间,分配失败的时候返回0,成功返回1
  12. static int do_slabs_newslab(const unsigned int id) {
  13. slabclass_t *p = &slabclass[id];
  14. int len = p->size * p->perslab;
  15. char *ptr;
  16. if (mem_limit && mem_malloced + len > mem_limit && p->slabs > 0)
  17. return 0;
  18. //grow slab list返回0的时候是失败
  19. if (grow_slab_list(id) == 0) return 0;
  20. ptr = malloc((size_t)len);
  21. if (ptr == 0) return 0;
  22. memset(ptr, 0, (size_t)len);
  23. //指向第一个没有分配的chunk,slab分配chunk是从slab_list里顺序分配的。
  24. p->end_page_ptr = ptr;
  25. p->end_page_free = p->perslab;
  26. //为每个slab分配一个1M的page
  27. p->slab_list[p->slabs++] = ptr;
  28. mem_malloced += len;
  29. return 1;
  30. }
  31. //为slab_list添加一个slab,需要看看slab_list是否已满。
  32. //如果满了,就需要分配更大的slab_list来装下slabs。如果满了,重新分配空间失败返回0。
  33. static int grow_slab_list (const unsigned int id) {
  34. slabclass_t *p = &slabclass[id];
  35. if (p->slabs == p->list_size) {
  36. size_t new_size = (p->list_size != 0) ? p->list_size * 2 : 16;
  37. void *new_list = realloc(p->slab_list, new_size * sizeof(void *));
  38. if (new_list == 0) return 0;
  39. p->list_size = new_size;
  40. p->slab_list = new_list;
  41. }
  42. return 1;
  43. }

为用户从slab中找个合适大小的chunk

  1. void *do_slabs_alloc(const size_t size) {
  2. slabclass_t *p;
  3. //找到最小的能装下size的slab
  4. unsigned int id = slabs_clsid(size);
  5. p = &slabclass[id];
  6. //如果slot中又空闲的chunk,那就从slot中取一个
  7. /* return off our freelist, if we have one */
  8. if (p->sl_curr != 0)
  9. return p->slots[--p->sl_curr];
  10. //否则我们就从slab_list中取一个
  11. /* if we recently allocated a whole page, return from that */
  12. if (p->end_page_ptr) {
  13. void *ptr = p->end_page_ptr;
  14. if (--p->end_page_free != 0) {
  15. p->end_page_ptr += p->size;
  16. } else {
  17. p->end_page_ptr = 0;
  18. }
  19. return ptr;
  20. }
  21. return NULL; /* shouldn't ever get here */
  22. }

用户释放chunk

  1. void do_slabs_free(void *ptr, const size_t size) {
  2. unsigned char id = slabs_clsid(size);
  3. slabclass_t *p;
  4. p = &slabclass[id];
  5. //如果slot中已经装满了空闲chunk,那么就得给slot分配一个更大的数组,否则直接将空闲chunk放到slot中
  6. if (p->sl_curr == p->sl_total) { /* need more space on the free list */
  7. int new_size = (p->sl_total != 0) ? p->sl_total * 2 : 16; /* 16 is arbitrary */
  8. void **new_slots = realloc(p->slots, new_size * sizeof(void *));
  9. if (new_slots == 0)
  10. return;
  11. p->slots = new_slots;
  12. p->sl_total = new_size;
  13. }
  14. p->slots[p->sl_curr++] = ptr;
  15. return;
  16. }

发表评论

表情:
评论列表 (有 0 条评论,278人围观)

还没有评论,来说两句吧...

相关阅读