glib的slab算法实现学习

slab提出来是为了解决内部内存碎片的问题,在linux内核中与buddy system一起来解决内核内存管理。但是要看懂slab在linux内核中的实现当前有些困难,我们不如拿些容易阅读的代码来了解slab算法的运作过程。GLIB库实现非常clear,可以做为slab算法的实现学习的入门。
slab在GLIB中的实现相关文件是gslice.h/c,但是在这个实现文件中,有更复杂的,支持多线程更多的magazine caching算法。

<strong>0. slab算法的运作机理</strong>
关于slab算法的运作机理,可以查看 wiki


1. slab结构体定义
  1. struct _ChunkLink {  
  2. ChunkLink *next;  
  3. ChunkLink *data;  
  4. };  
  5. struct _SlabInfo {  
  6. ChunkLink *chunks;  
  7. guint n_allocated;  
  8. SlabInfo *next, *prev;  
  9. };  



从结构体可以知道,每个slab维护着前面slab的link prev和后面的slab的link prev,可知它们链接起来就是一个双向环形链表(slab ring)。除此之外,还维护着一个chunk指针,用来指向free chunk的单向链表。


2. allocator结构体定义
  1. ...  
  2. /* slab allocator */  
  3. GMutex slab_mutex;  
  4. SlabInfo **slab_stack; /* array of MAX_SLAB_INDEX (allocator) */  
  5. guint color_accu;  
  6. } Allocator;  


从数据结构可知,全局变量allocator会维护着一个指针数组,成员为SlabInfo*指针,指向一个环形slab双向链表,也即前面介绍的slab ring。每个成员的slabinfo*指向的slab ring所管理的chunks,size都必须是相同的,例如第一个slab ring里面的chunk全部都是8 (bytes),第二个slab ring里全部都是8*2 (bytes),以此类推,一直到最大的chunk size。 初始化时,所有的成员都会初始化为null,代表当前slab system没有free list。


3. slab内存请求操作的过程
  1. static gpointer  
  2. slab_allocator_alloc_chunk (gsize chunk_size)  
  3. {  
  4. ChunkLink *chunk;  
  5. guint ix = SLAB_INDEX (allocator, chunk_size); // 找到chunk_size所对应的slab slot  
  6. /* ensure non-empty slab */  
  7. // 如果没有free list,则预先分配一页来填充此slot(一页可能会有很多chunks)  
  8. if (!allocator->slab_stack[ix] || !allocator->slab_stack[ix]->chunks)  
  9. allocator_add_slab (allocator, ix, chunk_size);  
  10. /* allocate chunk */  
  11. // 分配第一个chunk给client,后面的chunks挂接到当前slab上  
  12. chunk = allocator->slab_stack[ix]->chunks;  
  13. allocator->slab_stack[ix]->chunks = chunk->next;  
  14. allocator->slab_stack[ix]->n_allocated++;  
  15. /* rotate empty slabs */  
  16. // 如果当前所有的chunks用完,旋转slab ring,定位到下一个slab  
  17. if (!allocator->slab_stack[ix]->chunks)  
  18. allocator->slab_stack[ix] = allocator->slab_stack[ix]->next;  
  19. return chunk;  
  20. }  


当client首次请求一定量的的内存时,slab system首先会将请求的size up align to 8的倍数,作为chunk size,然后再分配一页内存。去除slab info 结构体和color(padding),之后那一页内剩下的共有n个chunk size。拿出1个chunk返回给client,剩下的(n-1)个chunk size挂接到当前的slab节点上。
allocator_add_slab函数
  1. static void  
  2. allocator_add_slab (Allocator *allocator,  
  3. guint ix,  
  4. gsize chunk_size)  
  5. {  
  6. ChunkLink *chunk;  
  7. SlabInfo *sinfo;  
  8. gsize addr, padding, n_chunks, color = 0;  
  9. // 计算出当前系统一页的内存大小  
  10. gsize page_size = allocator_aligned_page_size (allocator, SLAB_BPAGE_SIZE (allocator, chunk_size));  
  11. /* allocate 1 page for the chunks and the slab */  
  12. gpointer aligned_memory = allocator_memalign (page_size, page_size - NATIVE_MALLOC_PADDING);  
  13. guint8 *mem = aligned_memory; // 对齐到page 的虚拟内存地址  
  14. guint i;  
  15. if (!mem)  
  16. {  
  17. const gchar *syserr = "unknown error";  
  18. #if HAVE_STRERROR  
  19. syserr = strerror (errno);  
  20. #endif  
  21. mem_error ("failed to allocate %u bytes (alignment: %u): %s\n",  
  22. (guint) (page_size - NATIVE_MALLOC_PADDING), (guint) page_size, syserr);  
  23. }  
  24. /* mask page address */  
  25. addr = ((gsize) mem / page_size) * page_size;  
  26. /* assert alignment */  
  27. mem_assert (aligned_memory == (gpointer) addr);  
  28. /* basic slab info setup */  
  29. // 从下面知道每次分配一页时,slab_info总是在那个内存页的末尾  
  30. sinfo = (SlabInfo*) (mem + page_size - SLAB_INFO_SIZE);  
  31. sinfo->n_allocated = 0;  
  32. sinfo->chunks = NULL;  
  33. /* figure cache colorization */  
  34. n_chunks = ((guint8*) sinfo - mem) / chunk_size;  
  35. padding = ((guint8*) sinfo - mem) - n_chunks * chunk_size;  
  36. if (padding)  
  37. {  
  38. color = (allocator->color_accu * P2ALIGNMENT) % padding;  
  39. allocator->color_accu += allocator->config.color_increment;  
  40. }  
  41. /* add chunks to free list */  
  42. // 将第一个free chunk的内存地址定为跳过mem之后color (padding)的地址  
  43. chunk = (ChunkLink*) (mem + color);  
  44. sinfo->chunks = chunk;  
  45. // 将连续内存空间,用单向链表链接起来  
  46. for (i = 0; i < n_chunks - 1; i++)  
  47. {  
  48. chunk->next = (ChunkLink*) ((guint8*) chunk + chunk_size);  
  49. chunk = chunk->next;  
  50. }  
  51. chunk->next = NULL; /* last chunk */  
  52. /* add slab to slab ring */  
  53. allocator_slab_stack_push (allocator, ix, sinfo);  
  54. }  


一些有意思的MACRO定义:
  1. /* optimized version of ALIGN (size, P2ALIGNMENT) */  
  2. #if     GLIB_SIZEOF_SIZE_T * 2 == 8  /* P2ALIGNMENT */  
  3. #define P2ALIGN(size)   (((size) + 0x7) & ~(gsize) 0x7)  
  4. #elif   GLIB_SIZEOF_SIZE_T * 2 == 16 /* P2ALIGNMENT */  
  5. #define P2ALIGN(size)   (((size) + 0xf) & ~(gsize) 0xf)  
  6. #else  
  7. #define P2ALIGN(size)   ALIGN (size, P2ALIGNMENT)  
  8. #endif  
  9.   
  10.   
  11. #define P2ALIGNMENT             (2 * sizeof (gsize))   
  12. #define NATIVE_MALLOC_PADDING   P2ALIGNMENT            /* per-page padding left for native malloc(3) see [1] */  
  13. #define SLAB_INFO_SIZE          P2ALIGN (sizeof (SlabInfo) + NATIVE_MALLOC_PADDING)  
  14. #define SLAB_BPAGE_SIZE(al,csz) (8 * (csz) + SLAB_INFO_SIZE)  

如果要求分配8个字节的内存,csz=8,所以每次加载chunks,分配的内存必须能够包含8个chunks,加上SLAB_INFO_SIZE的大小。

  1. static void  
  2. allocator_slab_stack_push (Allocator *allocator,  
  3.                            guint      ix,  
  4.                            SlabInfo  *sinfo)  
  5. {  
  6.   /* insert slab at slab ring head */  
  7.   if (!allocator->slab_stack[ix])  
  8.     {  
  9.       sinfo->next = sinfo;  
  10.       sinfo->prev = sinfo;  
  11.     }  
  12.   else  
  13.     {  
  14.       SlabInfo *next = allocator->slab_stack[ix], *prev = next->prev;  
  15.       next->prev = sinfo;  
  16.       prev->next = sinfo;  
  17.       sinfo->next = next;  
  18.       sinfo->prev = prev;  
  19.     }  
  20.   allocator->slab_stack[ix] = sinfo;  
  21. }  
  22.   
  23.   
  24. static gsize  
  25. allocator_aligned_page_size (Allocator *allocator,  
  26.                              gsize      n_bytes)  
  27. {  
  28.   gsize val = 1 << g_bit_storage (n_bytes - 1);  
  29.   val = MAX (val, allocator->min_page_size);  
  30.   return val;  
  31. }  


[code lang="c"]gsize val = 1 << g_bit_storage (n_bytes - 1);[/code]用来将n_bytes表示的数字up align到2的n次幂的倍数。如果up align之后的数比min_page_size(4k=2^12)要大,那么函数返回的值就是val。也就是说allocator_aligned_page_size并不一定返回4k page size,或者说只要chunk size大于等于512,8 * 512 + 16 + 8 > 4096 (order >= 64)。
然而从以下的MACRO定义可知:
  1. #define MAX_SLAB_CHUNK_SIZE(al) (((al)->max_page_size - SLAB_INFO_SIZE) /8 )  
  2. #define MAX_SLAB_INDEX(al)      (SLAB_INDEX (al, MAX_SLAB_CHUNK_SIZE (al)) + 1)  

allocator中的slabinfo slot个数是不会超过64的(只有63,而且chunk size=504)。也就是说allocator_aligned_page_size返回的永远会是一个page的size(4k),而且一个page里最少会有8个chunks。


但是如何分配那一页内存呢?
  1. static gpointer  
  2. allocator_memalign (gsize alignment,  
  3.                     gsize memsize)  
  4. {  
  5.   gpointer aligned_memory = NULL;  
  6.   gint err = ENOMEM;  
  7. #if     HAVE_COMPLIANT_POSIX_MEMALIGN  
  8.   err = posix_memalign (&aligned_memory, alignment, memsize);  
  9. #elif   HAVE_MEMALIGN  
  10.   errno = 0;  
  11.   aligned_memory = memalign (alignment, memsize);  
  12.   err = errno;  
  13. #elif   HAVE_VALLOC  
  14.   errno = 0;  
  15.   aligned_memory = valloc (memsize);  
  16.   err = errno;  
  17. #else  
  18.   /* simplistic non-freeing page allocator */  
  19.   mem_assert (alignment == sys_page_size);  
  20.   mem_assert (memsize <= sys_page_size);  
  21.   if (!compat_valloc_trash)  
  22.     {  
  23.       const guint n_pages = 16;  
  24.       guint8 *mem = malloc (n_pages * sys_page_size);  
  25.       err = errno;  
  26.       if (mem)  
  27.         {  
  28.           gint i = n_pages;  
  29.           guint8 *amem = (guint8*) ALIGN ((gsize) mem, sys_page_size);  
  30.           if (amem != mem)  
  31.             i--;        /* mem wasn't page aligned */  
  32.           while (--i >= 0)  
  33.             g_trash_stack_push (&compat_valloc_trash, amem + i * sys_page_size);  
  34.         }  
  35.     }  
  36.   aligned_memory = g_trash_stack_pop (&compat_valloc_trash);  
  37. #endif  
  38.   if (!aligned_memory)  
  39.     errno = err;  
  40.   return aligned_memory;  
  41. }  


假如程序走到最后一个else语句,那么这个slab系统就有点复杂了。compat_valloc_trash是一个全局的静态指针static GTrashStack *compat_valloc_trash = NULL; 这个函数首先会向系统一次性请求分配16 * page_size(4k)大小的连续内存空间,然后将这些内存用GTrashStack串联起来,(要知道GTrashStack结构知道一个next域指向下一个区域,本身并不消耗内存),串联的单元就是一个页。这个就是g_trash_stack_push干的事情。
之后调用g_trash_stack_pop将第一个页的内存分配出去。


4. slab内存释放的操作
slab内存释放的操作由slab_allocator_free_chunk来完成,chunk_size是up allign到8的倍数的内存大小。
  1. static void  
  2. slab_allocator_free_chunk (gsize    chunk_size,  
  3.                            gpointer mem)  
  4. {  
  5.   ChunkLink *chunk;  
  6.   gboolean was_empty;  
  7.   guint ix = SLAB_INDEX (allocator, chunk_size); // 找到chunk size所属的slab slot序号  
  8.   // 计算chunk size所决定的page的大小(从slab分配出去的chunk都有所属的page)  
  9.   gsize page_size = allocator_aligned_page_size (allocator, SLAB_BPAGE_SIZE (allocator, chunk_size));  
  10.   // 计算释放chunk的地址所在page的内存首地址  
  11.   gsize addr = ((gsize) mem / page_size) * page_size;  
  12.   /* mask page address */  
  13.   guint8 *page = (guint8*) addr;  
  14.   // slab info结构体在一个page的最后24字节  
  15.   SlabInfo *sinfo = (SlabInfo*) (page + page_size - SLAB_INFO_SIZE);  
  16.   /* assert valid chunk count */  
  17.   // 这个slab应该有chunks被分配出去,不然这个slab应该被回收给系统,稍后知道为什么。  
  18.   mem_assert (sinfo->n_allocated > 0);  
  19.   /* add chunk to free list */  
  20.   was_empty = sinfo->chunks == NULL;  
  21.   // 把这个chunk插入到free chunk list头  
  22.   chunk = (ChunkLink*) mem;  
  23.   chunk->next = sinfo->chunks;  
  24.   sinfo->chunks = chunk;  
  25.   sinfo->n_allocated--;  
  26.   /* keep slab ring partially sorted, empty slabs at end */  
  27.   // 如果之前所有的chunks都分配出去了,这时有了free chunk,就应该让slab slot知道  
  28.   if (was_empty)  
  29.     {  
  30.       /* unlink slab */  
  31.       SlabInfo *next = sinfo->next, *prev = sinfo->prev;  
  32.       next->prev = prev;  
  33.       prev->next = next;  
  34.       if (allocator->slab_stack[ix] == sinfo)  
  35.         allocator->slab_stack[ix] = next == sinfo ? NULL : next;  
  36.       /* insert slab at head */  
  37.       // 让slab slot直接指向这个有free chunk的slab,以方便下次分配这样的chunk时,直接获取。  
  38.       allocator_slab_stack_push (allocator, ix, sinfo);  
  39.     }  
  40.   /* eagerly free complete unused slabs */  
  41.   // 以下操作就是为什么要做mem_assert (sinfo->n_allocated > 0);原因  
  42.   // 当这个slab所有的chunk都free时,就可以将这个slab所在页面返回给系统,或者TrashStack内存池。  
  43.   if (!sinfo->n_allocated)  
  44.     {  
  45.       /* unlink slab */  
  46.       SlabInfo *next = sinfo->next, *prev = sinfo->prev;  
  47.       next->prev = prev;  
  48.       prev->next = next;  
  49.       if (allocator->slab_stack[ix] == sinfo)  
  50.         allocator->slab_stack[ix] = next == sinfo ? NULL : next;  
  51.       /* free slab */  
  52.       allocator_memfree (page_size, page);  // 返回给系统或者内存池  
  53.     }  
  54. }  


将所有chunk都free的slab返回给系统或者内存池:
  1. static void  
  2. allocator_memfree (gsize    memsize,  
  3.                    gpointer mem)  
  4. {  
  5. #if     HAVE_COMPLIANT_POSIX_MEMALIGN || HAVE_MEMALIGN || HAVE_VALLOC  
  6.   free (mem);  
  7. #else  
  8.   mem_assert (memsize <= sys_page_size);  
  9.   g_trash_stack_push (&compat_valloc_trash, mem);  
  10. #endif  
  11. }  
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值