SGISTL源码探究-第二级配置器
2017-09-06 19:28
417 查看
前言
在本小节中我们将主要分析第二级配置器申请/释放内存的做法,它的主要实现机制是靠free_lists以及内存池来实现。内存池放在单独的一小节进行讲解。第二级配置器(__default_alloc_template)
引入
第二级配置器要比第一级复杂的多。它的做法是,如果要求申请的空间大于128bytes,则让第一级配置器去做,如果小于128bytes,则由内存池提供,而第二级配置器只负责维护16个free-list,各自管理的大小分别为8,16,24,32,40,48,56,64,72,80,88,96,104,112,120,128,当申请/释放空间时,大小进行调整为8的倍数,直接操作合适的free-list就行了。比如申请13bytes,自动调整为16bytes,然后从管理16bytes的free-list中取一个给用户,释放时,直接再挂在free-list上就行了。这样做有益于管理。深入源码
下面直接看代码:enum {__ALIGN = 8};//上调的边界,即每过8bytes上调一个free-list enum {__MAX_BYTES} = 128}; //分配的空间大小上限 enum {__NFREELISTS = __MAX_BYTES/__ALIGN} //free-lists的个数 template<int inst> class __default_alloc_template { private: //该函数的作用是将申请的空间上调至8的倍数 static size_t ROUND_UP(size_t bytes) { //这种写法很巧妙,先加上8,然后将结果的二进制中最后3位(1,2,4)抹去,这样剩下的(8,16,32...)都是8的倍数。 //进而如果我们想要4的倍数,那么就(a+4)&(~(4 - 1))。get到了新技能。 return (((bytes) + __ALIGN) & ~(__ALIGN - 1)); } private: union obj { union obj * free_list_link;//用于连接free-list中的结点 char client_data[1]; //这里有一个char类型的指针,它的作用最初让我很疑惑,因为空间配置器的源码里面并没有用到它,后面在网上查阅了一下,大概明白了它的意图:由于联合体的特性(成员共用内存空间),client_data的地址就是obj的地址,不同的是指向的对象的类型不一样,一个是obj,一个是char,而client_data是给用户使用的,这样就可以避免强制转换了。 }; private: //16个free_lists static obj *volatile free_list[__NFREELISTS]; //根据申请空间的大小决定使用第n号free_list,n从0开始 static size_t FREELIST_INDEX(size_t bytes) { return (((bytes) + __ALIGN - 1) / __ALIGN- 1); } //重新填充free lists,返回一个大小为n的对象。 static void *refill(size_t n); //分配size*nobjs大小的空间,如果nobjs太大了,可能会降低,所以传入引用 static char *chunk_alloc(size_t size, int &nobjs); //内存池起始位置,只在chunk_alloc()中变化 static char *start_free; //内存池结束位置,只在chunk_alloc()中变化 static char *end_free; static size_t heap_size; public:
[b]allocate[/b]
static void *allocate(size_t n) { obj * volatile *my_free_list; obj *result; //若大于128bytes,交给一级配置器处理 if(n > (size_t) __MAX_BYTES) { return (malloc_alloc::allocate(n)); } //找到n对应的free_list my_free_list = free_list + FREELIST_INDEX(n); result = *my_free_list; //该free_list为空,重新将其填充 if(result == 0) { void *r = refill(ROUND_UP(n)); return r; } //调整free_list,类似链表的head=head->next操作,将撤出链表的空间块返回给用户 *my_free_list = result->free_list_link; return (result); }
[b]deallocate[/b]
static void deallocate(void *p, size_t n) { obj *q = (obj *)p; obj * volatile * my_free_list; //若大于128bytes,交给一级配置器处理 if(n > (size_t) __MAX_BYTES) { malloc_alloc::deallocate(p, n); return; } //找到n对应的free_list my_free_list = free_list + FREELIST_INDEX(n); //将该块空间重新加入free_list(链表的头插法) q->free_list_link = *my_free_list; *my_free_list = q; }
static void * reallocate(void *p, size_t old_sz, size_t new_sz); }; //定义并初始化静态成员变量 template <bool threads, int inst> char *__default_alloc_template<threads, inst>::start_free = 0; template <bool threads, int inst> char *__default_alloc_template<threads, inst>::end_free = 0; template <bool threads, int inst> size_t __default_alloc_template<threads, inst>::heap_size = 0; template <bool threads, int inst> __default_alloc_template<threads, inst>::obj * volatile __default_alloc_template<threads, inst>::free_list [__NFREELISTS] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
在第二级配置器中的
allocate函数中,当n对应的free_list并没有空间时,调用了
refill函数,该函数从内存池中取空间,是连接free_list和内存池之间的枢纽。我们先看看
refill函数的源码。
[b]refill[/b]
该函数用于填充free_list,前面调用的时候只传入了需要分配的大小n,然后返回了。看起来似乎和free_list没什么关系,但是其实并不是。源码如下:
template <bool threads, int inst> void* __default_alloc_template<threads, inst>::refill(size_t n) { int nobjs = 20; //默认申请20个新的节点 //chunk_alloc函数是从内存池里面取空间出来返回 //nobjs传的是引用,会根据内存池满不满足要求而改变(比如只够4个节点的,那nobjs会改成4) char * chunk = chunk_alloc(n, nobjs); obj * volatile * my_free_list; obj * result; obj * current_obj, * next_obj; int i; //如果内存池中只够1个节点,那返回直接给用户 if (1 == nobjs) return (chunk); //根据n找到对应的free_list my_free_list = free_list + FREELIST_INDEX(n); /* result指向从内存池中申请的空间的首地址 * my_free_list以及next_obj指向下一个节点 * 这是因为会拿一个节点给用户,剩下的节点,加入到free_list中 */ result = (obj *)chunk; *my_free_list = next_obj = (obj*)(chunk + n); //将剩余的节点依次加入到free_list中去 for(i = 1; ; i++) { current_obj = next_obj; next_obj = (obj *)((char *)next_obj + n); if(nobjs - 1 == i) { current_obj->free_list_link = 0; break; } else { current_obj->free_list_link = next_obj; } } return (result); }
该函数的主要逻辑分2步(取-分):
通过
chunk_alloc函数向内存池中取空间,默认取20个大小为n的节点,如果不够,能取多少取多少。
如果只取了1个节点,直接分给用户
如果多于1个,分给用户一个,将剩余的节点加入到n对应的free_list中去
小结
在本小节中我们介绍了第二级配置器申请与释放内存的实现机制。将128以下的n分成16个free_list进行管理,申请与释放操作都在free_list上进行,当free_list没有节点可取时,通过内存池填充free_list,本节中只介绍到了填充函数,获取内存池空间的函数放在下一节详细介绍。相关文章推荐
- SGISTL源码探究-默认使用的配置器
- SGISTL源码探究-第一级配置器
- SGISTL源码探究-STL中的hashtable(上)
- SGISTL源码探究-大根堆heap
- SGISTL源码探究-关联式容器:set
- SGISTL源码探究-仿函数
- Azkaban的Web Server源码探究系列15:使用过程中几个需注意的配置&3.0中丢失的文件
- SGISTL源码探究-pair的实现
- SGISTL源码探究-关联式容器:multiset
- SGISTL源码探究-关联式容器:hash_multiset
- SGISTL源码探究-stl_numeric.h中的数值算法
- SGISTL源码探究-关联式容器:map
- SGISTL源码探究-list容器(下)
- SGISTL源码探究-deque容器(上)
- SGISTL源码探究-deque容器(下)
- SGISTL源码探究-stl_algobase.h中的算法
- SGISTL源码探究-vector容器(上)
- SGISTL源码探究-关联式容器:hash_map
- SGISTL源码探究-空间配置器
- SGISTL源码探究-stack配接器