深度解析--SGI STL 空间配置与释放

欢迎大家来访二笙的小房子,一同学习分享生活!

1.SGI 空间配置与释放

  • 对象构造前的空间配置与对象析构后的空间释放,由<stl_alloc.h>负责,SGI主要考虑以下四个步骤:
    1.向system heap要求空间
    2.考虑多线程状态
    3.考虑内存不足时的应对措施
    4.考虑过多“小型区块”可能造成的内存碎片问题
  • SGI以malloc()和free()完成内存的配置与释放,但考虑到小型区块可能造成的内存破碎问题,SGI设置了双层级配置器:
    1.第一级配置器直接使用malloc()和free()
    2.第二级配置器视情况采用不同的策略:
  • 配置区块超过128bytes时,视之为足够大,便采用第一级配置器
  • 配置区块小于128bytes时,视之为过小,采用memory pool整理方式

2. 两级配置器

  • SGI封装了一个接口,名为simple_alloc使配置器的接口符合STL规格),无论alloc被定义为第一级亦或第二级配置器,都采用该接口进行空间配置:
template <class T, class Alloc>
class simple_alloc {
public:
	static T *allocate(size_ t n)      //单纯的转调用函数,调用传递给配置器的成员函数
					{return 0  == n? 0 : (T*) Alloc::allocate(n * szieof(T)); }
	static T *allocate(void) 
	              { return (T*) Alloc::allocate(sizeof (T)) ; }
   static void deallocate(T *p, size_t n)
                 { if (0 != n)   Alloc::deallocate(p, n * sizeof(T)); } 
   sattic void deallocate(T *p)
                { Alloc::deallocate(p, sizeof(T)); }
 };

2.1 一、二级配置器的关系

  • 在学习两级配置器之前首先通过下图了解一下一、二级配置器是如何实现的以及两者之间的关系:
    第一级配置器与第二级配置器

2.2 第一级配置器 __malloc_alloc_template

  • 主要以malloc()、free()、realloc()等C函数执行实际的内存配置、释放、重配置操作,并实现出C++ new-handler机制:
#include <iostream>
#define __throwBadAlloc  cerr << "out of memory" << endl;  exit(1);

template <int inst>
class __MallocAllocTemplate {
	typedef void(*OOM_HANDLE)();
private:
	//以下函数用来处理内存不足的情况,oom:out  of  memory
	static void  *oomMalloc(size_t);
	static void  *oomRealloc(void *, size_t);
	static OOM_HANDLE OOM_Handle;

public:
	static void * allocate(size_t n) {
		void *result = malloc(n);      //第一级配置器直接使用malloc
		//如果内存不足则调用oomMalloc处理
		if (0 == result)   result = oomMalloc(n);
		return result;
	}

	static void deallocate(void *p, size_t m) {
		free(n);    //第一级配置器直接调用free
	}

	static void * reallocate(void *p, size_t oldSz, size_t newSz) {
		void *result = realloc(p, newSz);   //第一级配置器直接调用realloc()
		//内存不足调用oomRealloc
		if (0 == result)  result = oomRealloc(p, newSz);
		return result;
	}

	//static void (* set_malloc_handle (void (*f)())) ()
	//参数与返回值都是void (*)()
	static OOM_HANDLE  setMallocHandele(OOM_HANDLE f) {
		OOM_HANDLE old = OOM_Handle;
		OOM_Handle = f;
		return old;
	}
};

//设定初始指针为空
template <int inst>
void(*__MallocAllocTemplate<inst>::OOM_Handle)() = NULL;

//内存不足尝试再次申请
template <int inst >
void * __MallocAllocTemplate<inst>::oomMalloc(size_t n) {
	void(*myMallocHandle)();
	void *result;
	for (;;) {
		myMallocHandle = OOM_Handle;    //不断尝试释放、配置、再释放、再配置...
		if (0 == myMallocHandle) { __throwBadAlloc;  }
		(*myMallocHandle)();         //调用处理例程,企图释放内存
		result = malloc(n);          //再次尝试配置内存
		if (result)  return(result);
	}
}

template <int inst >
void * __MallocAllocTemplate<inst>::oomRealloc(void *p, size_t n) {
	void(*myMallocHandle)();
	void *result;
	for (;;) {
		myMallocHandle = OOM_Handle;    //不断尝试释放、配置、再释放、再配置...
		if (0 == myMallocHandle) { __throwBadAlloc; }
		(*myMallocHandle)();         //调用处理例程,企图释放内存
		result = malloc(n);          //再次尝试配置内存
		if (result)  return(result);
	}
}
  • 若内存不足处理函数oomMalloc与oomRealloc未设定内存不足处理例程,则直接丢出bad_alloc异常信息

2.3 第二级配置器

  • 第二级配置器多了一些机制,避免小额区块造成内存的碎片,减少配置时的额外负担
  • 具体实现:
    1.如果区块够大,超过128bytes,移交第一级配置器处理
    2.当区块小于128bytes,则以内存池管理
  1. 每次配置一块内存,维护对应的自由链表;
  2. 下次收到相同大小的请求时,则直接从自由链表中拔出;
  3. 客端释还小额区块,就回收到free-lists中
  • 第二级配置器主动将任何小额区块的内存需求上调至8的倍数(ROUND_UP函数实现)
  • 以下是部分函数实现:
//提升函数,将bytes提升至8的倍数
static size_t ROUND_UP(size_t bytes) {
       return (((bytes) + __ALIGN-1) & ~(__ALIGN - 1));
}
//16个free-lists
static obj * volatile free_list[__NFREELISTS];
//填充free_list
static size_t FREELIST_INDEX(size_t  bytes) {
     return (((bytes)  + __ALIGN - 1)/__ALIGN  - 1);
}

以下重点理解

2.3.1 空间配置函数alllocate()

  • 步骤:
    1.判断区块大小,大于128bytes调用第一级配置器
    2.小于128bytes检查对应的free_list,如果有可用区块,直接拿来用;否则上调至8倍数边界,调用refill为free_list重新填充

    在这里插入图片描述
static void * allocate(size_t n) {
  obj * volatile * my_free_list;
  obj * result;
  if (n > (size_t) __MAX_BYTES) {   //大于128调用第一级
    return(malloc_alloc::allocate(n));
  }
  my_free_list = free_list + FREELIST_INDEX(n);  //定位下标
  result = *my_free_list;
  if (result == 0) {
    void *r = refill(ROUND_UP(n));  //没找到可用的free_list,重新填充free_list
    return r;
  }
  //调整free_list
  *my_free_list = result->free_list_link;
  return (result); 
};

2.3.2 空间释放函数 deallocate()

  • 步骤:
    1.判断区块大小,大于128bytes调用第一级配置器
    2.小于128bytes则找出对应的free_list,将区块回收

在这里插入图片描述

static void deallocate(void *p, size_t n) {
  obj *q = (obj *)p;
  obj * volatile * my_free_list;
  if (n > (size_t) __MAX_BYTES) {    //大于128调用第一级配置器
    malloc_alloc::deallocate(p, n);
    return ;
  }
  my_free_list = free_list + FREELIST_INDEX(n);
  q-> free_list_link = *my_free_list;  //调整free_list,回收区块
  *my_free_list = q;
}

2.3.3 重新装填free_list

  • 步骤:
    1.free list中无可用区块,调用refill(),为free list填充空间
    2.新空间取自内存池(由chunk_alloc())完成,默认取20个新节点
    3.如果只申请到一块,则分配给调用者;否则在chunk空间建立free list
template <bool threads, int inst>
void * __default_alloc_template<threads, inst>::refill(size_t n) {
	int nobjs = 20;  //默认取20块新节点
	char * chunk = chunk_alloc(n, nobjs);   //在内存池中申请
	if (1 == nobjs)   return(chunk);  //只获得一块区块,给调用者使用
	//否则跳整free list/,纳入新节点
	obj * my_free_list = free_list + FREELIST_INDDEX(n);
	obj * result = (obj*)chunk; //返回一块给客端
	obj * next = (obj*)(chunk + n); //指向下一块内存
	obj * cur = next;
	*my_free_list = cur;
	//在chunk空间建立free list连接
	for (int i = 1; i < nobjs; ++i) {
		next = (obj*)((char*)next + n);
		cur->free_list_link = next;
		cur = next;
	}
	cur->free_list_link = NULL;
	return result;
}

2.3.4 内存池取空间(chunk_alloc实现)

  • 步骤(主要分以下四个步骤进行):
    1. 内存池剩余空间完全满足需求,则直接分配给用户
    2. 若剩余空间不能完全满足,但足够提供一个以上的区块,则将这些区块分配给用户
    3. 若剩余空间一个都满足不了,则将残余空间编入free list,然后配置heap空间:

1.向系统heap申请空间
2. 分配成功则返回
3. 分配失败则检索自己的free list查看是否有可用区块
4. 到处都无内存使用则调用第一级配置器

template <bool threads, int inst>
char *
__default_alloc_template<threads, inst>::
	chunk_alloc(size_t size, int& nobjs) {
		char *result;
		size_t totalbytes = size * nobjs;  //需求量 
		size_t bytesleft = end_free - start_free;  //内存池剩余量 
		//如果剩余大于需求 
		if (bytesleft >= totalbytes) {
			result = start_free;
			start_free += totalbytes;
			resturn result;
		}
		//剩余只能满足一个或以上的区块,不能全部满足 
		else if(bytesleft >= size) {
			nobjs = bytesleft/size;
			totalbytes = size * nobjs;
			result = start_free;
			start_free += totalbytes;
			return result;
		}
		//剩余空间一个区块都不能满足
		else {
			//内存池中还有些许零头 ,将其分配给用户 
			if (bytesleft >0) {
				//寻找适当的free list 
				obj * volatile *myfreelist = free_list + FREELIST_INDEX(bytesleft);
				((obj *)start_free)->free_list_link = *myfreelist;
				*myfreelist = (obj *)start_free;
			}
			size_t bytesToGet = 2 * totalbytes + ROUND_UP(heap_size>>4); //配置heap空间,初始量设为需求的两倍加上一个随配置次数增大的附加量 
			start_free = (char *)malloc(bytesToGet); 
			if (0 == start_free) {  
				//heap空间不足,malloc失败
				 int i;
				 obj * volatile * myfreelist, *p;
				 //搜寻free list,查找是否还有未用的区块
				 for (i = size; i <= _MAX_BYTES; i += _ALIGN) {
				 	myfreelist = free_list + FREELIST_INDEX(i);
				 	p = *myfreelist;
				 	if (0 != p) {
				 		//调整free list释放未用区块 
				 		myfreelist = p -> free_list_link;
				 		start_free = (char*)p;
				 		end_free = start_free + i;
					 	return (chunk_alloc(size, nobjs));  //递归调用,修正nobjs 
					 }
				 }
				//所有地方都无内存可用,则调用第一级配置器,使用out of memory机制 
				end_free = 0;  
				start_free = (char *)malloc_alloc::allocate(bytesToGet);  
			}
			//分配成功则返回 
			heap_size = bytesToGet;
			end_free = start_free + bytesToGet;
			return (chunk_alloc(size, nobjs));   //递归修正nobjs 
		} 	
	}

3. C++ new-handler机制

本章在讲解第一级配置器时初次提及C++ new-handler机制,于是就去《Effective C++》进一步了解了有关new-handler机制,日后再次遇到时也能迅速回忆起。

  • 1.“当operator new抛出异常以反映一个未获满足的内存需求之前,它会先调用一个客户指定的错误处理函数,一个所谓的new-handler”,这是Effective C++中的原话,用以引出new-handler,从这句话我们大概能知道new-handler是用来处理内存不足的一个机制
  • 2.为了指定这个“用以处理内存不足”的函数,客户需要调用set_new_handler,定义于< new>的一个标准程序库函数:
namespace std {
	typedef void (*new_handler)();
	//返回值和参数都是new_handler
	new_handler set_new_handler(new_handler p) throw();
}
  • 3.set_new_handler的参数是一个指针,指向operator new无法分配足够内存时需要调用的函数:
void outOfMem()
{
	std::cerr << "Unable to satisfy request for memory\n";
	std::abort();
}
int main()
{
	std::set_new_handler(outOfMem); //当内存不足时,调用处理函数outOfMem
	int* pBigDataArrry = new int[100000000L];
	...
}
  • 4.设计一个良好的new-handler:

4.1 让更多内存可被使用:程序一开始执行就分配一大块内存,而后当new-handler第一次被调用,将它们释放还给程序
4.2 安装另一个new-handler:令new-handler修改一些会影响new-handler行为的数据
4.3 卸载new-handler将NULL指针传给set_new_handler
4.4 抛出bad_alloc的异常
4.5 不返回:调用abort或exit

  • 5.基于以上设计,你可以定制你自己的new-handle机制

4. free list的节点结构

在这里插入图片描述

  • 通过上图可以了解到STL通过指针来维护自由链表,有人可能会想每个节点都需要额外的指针,会不会造成额外的负担呢?答案是会的,那么STL的设计精妙之处在哪呢?我们可以先来看一看它的节点结构:
union obj {
	union obj  * free_list_link;
	char client_data[1];
}
  • "上述obj所用的union,由于union之故,从其第一字段观之,obj可被视为一个指针,指向相同形式的另一个obj,从其第二字段观之,obj可被视为一个指针,指向实际区块“,这段话前半部分很好理解,因为free_list_link本身就是一个obj的指针,但后半部分obj可被视为一个指针,指向实际区块,这一句话又怎么理解呢?
    1.首先我们回顾下union的性质,一个union类任意时刻只允许其一个数据成员有值,且数据成员公用内存
    2.由上,该free list节点结构union中的数据成员free_list_link和client_data公用一块内存,前者是指向下一节点的指针,后者是指向实际内存的指针,无论什么情况,二者只用其一,不分配的时候,节点就放在空闲链表之内,节点内容是指向下一节点的地址,如果被分配了,那么节点指向的就是实际的内存
  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值