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  };

………….


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


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));

……………

333        }

 

264  slice_config_init (SliceConfig *config)

265  {

……………

        // 通过使用191行代码(代码在总揽中已给出)定义的slice_config初始化

        // allocator中的config,以此决定了使用哪种分配机制。

273    *config = slice_config;

……………

278  }

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

 

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整数倍数。



// 通过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,几乎所有跟它相关的内部信息都在申请的那页内存上:


现在结合上图展开说明。代码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更复杂但也更有用更有意思,我将在下一篇给出。
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值