slab内存管理源代码分析

学习计算机原理,最好是实践或看高手写的源代码,在一定程度上就不再会感到原理的抽象。关于slab一些原理资料,可以在这里下载或到网站有更多的信息和资料。Slab内存管理机制已被广泛使用,要找到使用slab管理内存的开源代码也不难,如一些OS内核中的内存管理。既然要分析理解slab,最好还是选择复杂度和代码量都不要太大的,在这里我选取了glib-2.12.9的gslice.c实现的slab机制相关代码作为分析对象。注意Glib库是针对用户级的而非OS内核级别的。

  gslice.c中实现了三种内存分配机制:一是slab;二是比slab更适合于多CPU/多线程的magazine;三是只使用纯粹的malloc。本文章只针对slab相关的源代码进行分析。

  在分析代码时主要从以下几个方面入手:先从分配器总体数据结构的关系进行描述;二是看分配器allocator是如何初始化的;接下来是分析分配器如何分配和回收内存(chunk)。

  Allocator分配器总体结构:

  下面先来看一些重要的数据结构和变量:

  …………. //点代表省略的代码
  130 typedef struct _ChunkLink   ChunkLink;
  131 typedef struct _SlabInfo    SlabInfo;
  132 typedef struct _CachedMagazine CachedMagazine;
  // 这个结构也表明了一个Chunk的最小值是两个指针大小
  133 struct _ChunkLink {
  134  ChunkLink *next;
  135  ChunkLink *data; //这字段在slab中未被使用
  136 };
  137 struct _SlabInfo {
  138  ChunkLink *chunks;
  139  guint n_allocated;
  140  SlabInfo *next, *prev;
  141 };
  ………….
  150 typedef struct {
  151  gboolean always_malloc; // 为TRUE表示使用纯粹的malloc
  152  gboolean bypass_magazines; // 为TRUE表示使用slab
  153  gsize  working_set_msecs;
  154  guint  color_increment;
  155 } SliceConfig;
  156 typedef struct {
  157  /* const after initialization */
  158  gsize     min_page_size, max_page_size;
  159  SliceConfig  config;
  ……………
  168  /* slab allocator */
  169  GMutex    *slab_mutex;
  // SlabInfo指针数组,最大值为MAX_SLAB_INDEX (allocator)
  170  SlabInfo  **slab_stack; /* array of MAX_SLAB_INDEX (allocator) */
  171  guint    color_accu;
  172 } Allocator;
  ………….
  // 这个变量如果是0说明allocator还未被初始化,如果是大于0的数说明allocator
  // 已被初始化,并且它的值就是系统页面值的大小
  189 static gsize    sys_page_size = 0;
  190 static Allocator  allocator[1] = { { 0, }, }; // 内存分配器
   // 在变量slice_config中配置选取用那种分配机制,由以下值可知默认情况
  // 是使用magazine分配机制
  191 static SliceConfig slice_config = {
  192  FALSE,   /* always_malloc */
  193  FALSE,   /* bypass_magazines */ // 把这个值设为TRUE,才真正使用slab
  194  15 * 1000,  /* working_set_msecs */
  195  1,      /* color increment, alt: 0x7fffffff */
  196 };
  …………

根据以上的数据结构和程序的逻辑实现,可以把它们的关系用如下的图表示:

slab内存管理源代码分析

  Allocator有个SlabInfo指针数组slab_stack成员,stab_stack的每个成员或者是空指针或是一个指向SlabInfo双向循环链表。双向循环链表中的每个成员有个指针指向chunk链表的表头。而chunk链表中的每个成员就是调用接口g_slice_alloc时被分配的空间。

  当调用接口g_slice_alloc申请空间时,根据申请的空间大小通过宏SLAB_INDEX找到对应指针数组slab_stack的正确下标,找到对应的slab_stack数组下标就要以找到相应的SlabInfo双向循环链表,也就可以找到Chunk链表并从Chunk链表取出一个节点作为被申请的空间返回。即实际分配内存要先找到SlabInfo双向循环链表,然后再通过它分配内存。

  要注意,在下面的分析中会经常用到上图中的几个名词。这些名词有SlabInfo指针数组(allocator->slab_stack)、SlabInfo双向循环链表、每个SlabInfo管理的Chunk链表。还有在下面分析时会把allocator->slab_stack[ix]叫当前的SlabInfo。

  allocator分配器初始化:

  初始化的调用关系是:g_slice_alloc--->allocator_categorize--->g_slice_init_nomessage---> slice_config_init。

  // 以下相关代码是初始化190行定义的allocator变量
  751 g_slice_alloc (gsize mem_size)
  752 {
  ……………// 点代表省略的代码
  755  guint acat;
  ……………
  757  acat = allocator_categorize (chunk_size);
  ……………
  779 }
  // 这个函数的作用是获取allocator分配器的分配机制。
  // 返回值:0表示使用纯粹的malloc;1表示使用magazine;2表示使用slab
  335 static inline guint
  336 allocator_categorize (gsize aligned_chunk_size)
  337 {
  ……………
  346  if (!sys_page_size)
  347   g_slice_init_nomessage ();
  ……………
  357 }
  281 g_slice_init_nomessage (void)
  282 {
  ……………
  // 获取系统页面大小,并把值赋给sys_page_size变量
  287 #ifdef G_OS_WIN32
  288  {
  289   SYSTEM_INFO system_info;
  290   GetSystemInfo (&system_info);
  291   sys_page_size = system_info.dwPageSize;
  292  }
  293 #else
  294  sys_page_size = sysconf (_SC_PAGESIZE); /* = sysconf (_SC_PAGE_SIZE); = getpagesize(); */
  295 #endif
  ……………
  298  slice_config_init (&allocator->config);
  299  allocator->min_page_size = sys_page_size;
  ……………
  // 建立SlabInfo指针数组(allocator->slab_stack),
  // 数组里每个指针值都初始化成NULL值
  323  allocator->slab_stack = g_new0 (SlabInfo*, MAX_SLAB_INDEX (allocator));
  ……………
  <!--[if !supportLists]-->333    }<!--[endif]-->
  264 slice_config_init (SliceConfig *config)
  265 {
  ……………
  // 通过使用191行代码(代码在总揽中已给出)定义的slice_config初始化
  // allocator中的config,以此决定了使用哪种分配机制。
  273  *config = slice_config;
  ……………

 allocator分配器分配内存chunk:

  在分析主要代码之前有必要先了解操作chunk_size字节对齐和求SlabInfo指针数组(allocator->slab_stack)下标的几个宏定义。

  chunk_size的字节对齐是通过宏P2ALIGN来实现,P2ALIGN是以P2ALIGNMENT字节对齐的。

  // gsize和下文的GLIB_SIZEOF_SIZE_T是同等意义的,它等于一个指针的字节数。可见
  // P2ALIGNMENT为两个指针字节数,也即在总揽给出的代码133行中声明的Chunk的最
  //小字节数。我们一般假设在32位机器中,一个指针的字节数为4,
  // 那么P2ALIGNMENT的值为8。
  // 在下面的分析中,如果有假设数据字节数,就认为P2ALIGNMENT的值为8
  103 #define P2ALIGNMENT  (2 * sizeof (gsize)) /* fits 2 pointers (assumed to be 2 * GLIB_SIZEOF_SIZE_T below) */
  // ALIGN功能是求size以base字节对齐的数据。这是一种常用的方法,如果看不明白
  // 可以假设一些真实的数据进去运算,当然假设数据时base值最好是2的n次方。
  104 #define ALIGN(size, base)  ((base) * (gsize) (((size) + (base) - 1) / (base)))
  // 下面的P2ALIGN也是以一定的字节数对齐的操作,它用的了一些二进制的技巧
  // 可以参考本人写的另外一篇文章讲二进制技巧那部分或有关这方面知识的其它资料。
  116 /* optimized version of ALIGN (size, P2ALIGNMENT) */
  117 #if   GLIB_SIZEOF_SIZE_T * 2 == 8 /* P2ALIGNMENT */
  118 #define P2ALIGN(size)  (((size) + 0x7) & ~(gsize) 0x7) // 以8字节对齐
  119 #elif  GLIB_SIZEOF_SIZE_T * 2 == 16 /* P2ALIGNMENT */
  120 #define P2ALIGN(size)  (((size) + 0xf) & ~(gsize) 0xf) // 以16字节对齐
  121 #else
  122 #define P2ALIGN(size)  ALIGN (size, P2ALIGNMENT)
  123 #endif

求SlabInfo指针数组(allocator->slab_stack)下标的宏是代码112行的SLAB_INDEX(al, asize),从代码981行可知宏SLAB_INDEX里的asize就是chunk_size。代码981行的chunk_size是从代码756行调用P2ALIGN获得的,可见传给宏SLAB_INDEX的asize已是P2ALIGNMENT字节对齐的了。下图更直接明了地说明了chunk_size和SlabInfo指针数组(allocator->slab_stack)下标的关系。可见SlabInfo指针数组(allocator->slab_stack)下标从小到大的成员所指向的SlabInfo双向循环链表(此链表见总揽图)的chunk_size是从小到大的P2ALIGNMENT整数倍数。

slab内存管理源代码分析

  // 通过asize求SlabInfo指针数组(allocator->slab_stack)的下标,
  // asize以P2ALIGNMENT字节对齐
  112 #define SLAB_INDEX(al, asize) ((asize) / P2ALIGNMENT - 1)           /* asize must be P2ALIGNMENT aligned */
  750 gpointer
  751 g_slice_alloc (gsize mem_size)
  752 {
  753  gsize chunk_size;
  754  gpointer mem;
  755  guint acat;
  // 对要分配的内存大小通过宏P2ALIGN变为以P2ALIGNMENT字节对齐的大小
  // 并把它赋给chunk_size
  756  chunk_size = P2ALIGN (mem_size);
  ……………
  772    g_mutex_lock (allocator->slab_mutex);
  773    mem = slab_allocator_alloc_chunk (chunk_size);
  774    g_mutex_unlock (allocator->slab_mutex);
  ……………
  778  return mem;
  779 }
  977 static gpointer
  978 slab_allocator_alloc_chunk (gsize chunk_size)
  979 {
  980  ChunkLink *chunk;
   // 求SlabInfo指针数组(allocator->slab_stack)下标,
  // 也即找到chunk_size对应的SlabInfo双向循环链表
  981  guint ix = SLAB_INDEX (allocator, chunk_size);
  982  /* ensure non-empty slab */
   // 判断chunk_size对应的SlabInfo双向循环链表的循环是否还未建立或是
  // 当前的SlabInfo中的chunk是否已被分配完。
  // 如果两者的任何一个成立,那么就重新建立一个新的SlabInfo,并把
  // 当前SlabInfo指针allocator->slab_stack[ix]指向新建的SlabInfo,新建
  // 的SlabInfo包含了新分配的chunk链表,这功能在allocator_add_slab函数完成。
  983  if (!allocator->slab_stack[ix] || !allocator->slab_stack[ix]->chunks)
  984   allocator_add_slab (allocator, ix, chunk_size);
  985  /* allocate chunk */
  986  chunk = allocator->slab_stack[ix]->chunks;
   // 让被分配的chunk脱离chunk链表
  987  allocator->slab_stack[ix]->chunks = chunk->next;
  988  allocator->slab_stack[ix]->n_allocated++;
  989  /* rotate empty slabs */
  // 如果当前的SlabInfo的chunk已分配完,就让当前的SlabInfo指针指
  // 向下一个SlabInfo。
  990  if (!allocator->slab_stack[ix]->chunks)
  991   allocator->slab_stack[ix] = allocator->slab_stack[ix]->next;
  992  return chunk;
  993 }
  // 这函数的代码分析也可以直接看下面图A解释或是两者结合起来理解。
  926 static void
  927 allocator_add_slab (Allocator *allocator,
  928           guint   ix,
  929           gsize   chunk_size)
  930 {
  931  ChunkLink *chunk;
  932  SlabInfo *sinfo;
  933  gsize addr, padding, n_chunks, color = 0;
  934 gsize page_size = allocator_aligned_page_size (allocator, SLAB_BPAGE_SIZE (allocator, chunk_size));
  935  /* allocate 1 page for the chunks and the slab */
   /* 分配一页内存给slab和chunk链表 */
  936  gpointer aligned_memory = allocator_memalign (page_size, page_size - NATIVE_MALLOC_PADDING);
  937  guint8 *mem = aligned_memory;
  938  guint i;
  ……………
  952  /* basic slab info setup */
   // 把SlabInfo结构信息放在刚分配的一页内存的高地址处
  953  sinfo = (SlabInfo*) (mem + page_size - SLAB_INFO_SIZE);
  954  sinfo->n_allocated = 0;
  955  sinfo->chunks = NULL;
  956  /* figure cache colorization */
   // 计算这一页内存能够划分成多少(n_chunks)个chunk。
  957  n_chunks = ((guint8*) sinfo - mem) / chunk_size;
   // 再判断是否还有剩余的空间padding,如果有另作他用。
  958  padding = ((guint8*) sinfo - mem) - n_chunks * chunk_size;
  959  if (padding)
  960   {
  961    color = (allocator->color_accu * P2ALIGNMENT) % padding;
  962    allocator->color_accu += allocator->config.color_increment;
  963   }
  964  /* add chunks to free list */
  // 找出chunk链表的表头
  965  chunk = (ChunkLink*) (mem + color);
  966  sinfo->chunks = chunk;
   // 循环构建chunk链表:把地址相邻的chunk链接起来
  967  for (i = 0; i < n_chunks - 1; i++)
  968   {
   // 当前chunk指向下一个chunk,
   // (chunk + chunk_size)是下一个chunk的起始地址。
  969    chunk->next = (ChunkLink*) ((guint8*) chunk + chunk_size);
  970    chunk = chunk->next;
  971   }
   // 最后一个chunk指向NULL
  972  chunk->next = NULL;  /* last chunk */
  973  /* add slab to slab ring */
  974  allocator_slab_stack_push (allocator, ix, sinfo);
  975 }
  // 函数功能是根据SlabInfo指针数组(allocator->slab_stack)下标ix
  // 把新建的SlabInfo链入对应的SlabInfo双向循环链表,并把当前SlabInfo指针
  // allocator->slab_stack[ix]指向新建的SlabInfo。
  896 allocator_slab_stack_push (Allocator *allocator,
  897               guint   ix,
  898               SlabInfo *sinfo)
  899 {
  900  /* insert slab at slab ring head */
  901  if (!allocator->slab_stack[ix])
  902   {
  903    sinfo->next = sinfo;
  904    sinfo->prev = sinfo;
  905   }
  906  else
  907   {
  908    SlabInfo *next = allocator->slab_stack[ix], *prev = next->prev;
  909    next->prev = sinfo;
  910    prev->next = sinfo;
  911    sinfo->next = next;
  912    sinfo->prev = prev;
  913   }
  914  allocator->slab_stack[ix] = sinfo;
  915 }

  对于以上的代码,重点对allocator_add_slab函数进行更为详细的分析,它的功能主要是申请一页内存,用这一内存新建立一个SlabInfo,并把它链入对应的SlabInfo双向循环链表。对于新建立的SlabInfo,几乎所有跟它相关的内部信息都在申请的那页内存上:

slab内存管理源代码分析

  现在结合上图展开说明。代码936、937行申请一页内存,并把起始地址给mem变量。代码953行把页面的高地址分给了SlabInfo结构。如果有padding的话,代码958到963是把padding另作它用。而上图color的大小和空白的大小相加就是padding的值了,这点细节也可以不用太多关注它。chunk链表的起始地址,即链表表头在代码的965行确定的,而上图SlabInfo有个指向chunk链表头的指针是在代码966行实现的。图中chunk链表的建立是在代码967到972实现的。

  allocator分配器回收内存chunk:

  分配器对内存的分配和回收就很简单了,通过函数g_slice_free1调用了函数slab_allocator_free_chunk,下面仅对slab_allocator_free_chunk函数分析:

  // 参数中的mem就是要释放回收的内存chunk
  996 slab_allocator_free_chunk (gsize  chunk_size,
  997               gpointer mem)
  998 {
  999  ChunkLink *chunk;
  1000  gboolean was_empty;
  1001  guint ix = SLAB_INDEX (allocator, chunk_size);
  1002  gsize page_size = allocator_aligned_page_size (allocator, SLAB_BPAGE_SIZE (allocator, chunk_size));
  // 这是求mem所在的页面的起始地址。因地址在程序逻辑中是扁平线性的,
  // 所以(mem / page_size)就是mem所属的是第几个页面,那么它乘上page_size就是
  // mem所在的页面的起始地址。
  1003  gsize addr = ((gsize) mem / page_size) * page_size;
  1004  /* mask page adress */
  1005  guint8 *page = (guint8*) addr;
  // 获取管理mem的SlabInfo的指针。在上面已提到过SlabInfo是放在一个页面
  // 的高地址处。
  1006  SlabInfo *sinfo = (SlabInfo*) (page + page_size - SLAB_INFO_SIZE);
  1007  /* assert valid chunk count */
  1008  mem_assert (sinfo->n_allocated > 0);
  1009  /* add chunk to free list */
  1010  was_empty = sinfo->chunks == NULL;
  1011  chunk = (ChunkLink*) mem;
  // 把要回收的chunk链入SlabInfo管理的chunk链表的表头
  1012  chunk->next = sinfo->chunks;
  1013  sinfo->chunks = chunk;
  1014  sinfo->n_allocated--;
  1015  /* keep slab ring partially sorted, empty slabs at end */
  // was_empty为TRUE,表明管理要回收的chunk的SlabInfo所在的SlabInfo双向循环链表
  // 中的每一个SlabInfo都可能已把它自己的chunk分配完,即它们都没有空间可分配了。
  // 那么就应该把当前的SlabInfo改为这次回收了内存chunk的SlabInfo,以备下次分配用。
  1016  if (was_empty)
  1017   {
  1018    /* unlink slab */
  1019    SlabInfo *next = sinfo->next, *prev = sinfo->prev;
  1020    next->prev = prev;
  1021    prev->next = next;
  1022    if (allocator->slab_stack[ix] == sinfo)
  1023     allocator->slab_stack[ix] = next == sinfo ? NULL : next;
  1024    /* insert slab at head */
  // 重新把SlabInfo链入SlabInfo双向循环链表,为的是把当
  // 前SlabInfo(allocator->slab_stack[ix])改为这次回收了内存chunk的SlabInfo
  1025    allocator_slab_stack_push (allocator, ix, sinfo);
  1026   }
  1027  /* eagerly free complete unused slabs */
  1028  if (!sinfo->n_allocated)
  1029   {
  1030    /* unlink slab */
  1031    SlabInfo *next = sinfo->next, *prev = sinfo->prev;
  1032    next->prev = prev;
  1033    prev->next = next;
  1034    if (allocator->slab_stack[ix] == sinfo)
  1035     allocator->slab_stack[ix] = next == sinfo ? NULL : next;
  1036    /* free slab */
   // allocator_memfree函数功能是系统回收内存空间
  1037    allocator_memfree (page_size, page);
  1038   }
  1039 }

 

  到此已把slab相关的代码已分析完,比slab更适合于多CPU/多线程的magazine内存管理机制比slab更复杂但也更有用更有意思,我将在下一篇给出。
  278 }

  从以上的代码可知,如果只看初始化相关的代码,这一过程极其的简单!它主要做了三样事情:一是获得系统页面大小sys_page_size;二是初始化config,以此决定了allocator分配器使用的分配机制;三是建立了SlabInfo指针数组。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值