空间的配置与释放

空间的配置与释放

对象构建前的空间配置和对象析构以后的空间释放,由 <stl_alloc.h> 负责,SGI 的设计哲学包含:

  • 向 system heap 要求空间。
  • 考虑多线程状态。
  • 考虑内存不足时的应变策略。
  • 考虑小型区块过多时造成的内存破碎问题。

考虑到内存破碎的问题,SGI 设计了双层配置器:

  • 第一级配置器直接使用 malloc() 和 free()。
  • 第二级配置器根据实际情况采取不同的策略:
    • 当配置区块超过 128 bytes,视为足够大,呼叫第一级配置器。
    • 当配置区块小于 128 bytes,视为比较小,则不求助第一级配置器。

是否启用第一级配置器,取决于 __USE_MALLOC 是否被定义:

# ifdef __USE_MALLOC

typedef malloc_alloc alloc;  // 令 alloc 为第一级配置器
typedef malloc_alloc single_client_alloc;

# else
...
typedef __default_alloc_template<__NODE_ALLOCATOR_THREADS, 0> alloc;  //@ 令 alloc 为第二级配置器

alloc 不接受任何参数,无论alloc 被定义成第一级或第二级配置器,SGI 还为它封装了一个符合 STL 规格的版本:

//@ 单纯地转调用,调用传递给配置器(第一级或第二级);多一层包装,使 _Alloc 具备标准接口
template<class _Tp, class _Alloc>
class simple_alloc {

public:
    // 配置 n 个元素
    static _Tp* allocate(size_t __n)
      { return 0 == __n ? 0 : (_Tp*) _Alloc::allocate(__n * sizeof (_Tp)); }
    static _Tp* allocate(void)
      { return (_Tp*) _Alloc::allocate(sizeof (_Tp)); }
    static void deallocate(_Tp* __p, size_t __n)
      { if (0 != __n) _Alloc::deallocate(__p, __n * sizeof (_Tp)); }
    static void deallocate(_Tp* __p)
      { _Alloc::deallocate(__p, sizeof (_Tp)); }
};

SGI STL 中的容器都使用这个 simple_alloc 接口,例如:

template <class _Tp, class _Alloc> 
class _Vector_base {
...
protected:
  //@ simple_alloc 是 SGI STL 的空间配置器
  typedef simple_alloc<_Tp, _Alloc> _M_data_allocator;  //@ 以元素大小为配置单位
  _Tp* _M_allocate(size_t __n)
    { return _M_data_allocator::allocate(__n); }
  void _M_deallocate(_Tp* __p, size_t __n) 
    { _M_data_allocator::deallocate(__p, __n); }
};

第一级配置器

  • 以 malloc(),free(), realloc() 等 C 函数执行实际的内存配置、释放、重配置操作。
  • 实现出类似 C++ new-handler 的机制。所谓 C++ new handler 机制是,你可以要求系统在内存配置需求无法被满足时,调用一个你所指定的函数。
  • 第一级配置器内存分配失败一般是由于内存不足 out-of-memory(oom),处理异常时,首先用户自己设计异常处理例程,若用户没有定义,仅仅是打印错误信息并强制退出。
  • allocate() 和 realloc() 都是在调用 malloc() 和 realloc() 不成功后,改调用 oom_malloc() 和oom_realloc()。后两者都有内循环,不断调用“内存不足处理例程”,期望在某次调用之后,获得足够的内存而圆满完成任务。
template <int __inst>
class __malloc_alloc_template {

private:
  
  //@ 以下函数将用来处理内存不足的情况
  static void* _S_oom_malloc(size_t);
  static void* _S_oom_realloc(void*, size_t);

#ifndef __STL_STATIC_TEMPLATE_MEMBER_BUG
  static void (* __malloc_alloc_oom_handler)();
#endif

public:

  //@ 第一级配置器直接调用 malloc()
  static void* allocate(size_t __n)
  {
    void* __result = malloc(__n);
    // 以下无法满足需求时,改用 _S_oom_malloc()
    if (0 == __result) __result = _S_oom_malloc(__n);
    return __result;
  }

  //@ 第一级配置器直接调用 free()
  static void deallocate(void* __p, size_t /* __n */)
  {
    free(__p);
  }
  
  //@ 第一级配置器直接调用 realloc()
  static void* reallocate(void* __p, size_t /* old_sz */, size_t __new_sz)
  {
    void* __result = realloc(__p, __new_sz);
    //@ 以下无法满足需求时,改用 _S_oom_realloc()
    if (0 == __result) __result = _S_oom_realloc(__p, __new_sz);
    return __result;
  }

  //@ 以下仿照 C++ 的 set_new_handler(),可以通过它指定自己的 out-of-memory handler
  //@ 为什么不使用 C++ new-handler 机制,因为第一级配置器并没有 ::operator new 来配置内存
  static void (* __set_malloc_handler(void (*__f)()))()
  {
    void (* __old)() = __malloc_alloc_oom_handler;
    __malloc_alloc_oom_handler = __f;
    return(__old);
  }
};

// malloc_alloc out-of-memory handling
#ifndef __STL_STATIC_TEMPLATE_MEMBER_BUG
// 初值为0,由客户自行设定
template <int __inst>
void (* __malloc_alloc_template<__inst>::__malloc_alloc_oom_handler)() = 0;
#endif

template <int __inst>
void*
__malloc_alloc_template<__inst>::_S_oom_malloc(size_t __n)
{
    void (* __my_malloc_handler)();
    void* __result;

    // 不断尝试释放、配置
    for (;;) {
        __my_malloc_handler = __malloc_alloc_oom_handler;
        if (0 == __my_malloc_handler) { __THROW_BAD_ALLOC; }
        (*__my_malloc_handler)();  //@ 调用处理例程,企图释放内存
        __result = malloc(__n);   //@ 再次尝试配置内存
        if (__result) return(__result);
    }
}

template <int __inst>
void* __malloc_alloc_template<__inst>::_S_oom_realloc(void* __p, size_t __n)
{
    void (* __my_malloc_handler)();
    void* __result;

    //@  给一个已经分配了地址的指针重新分配空间,参数 __p 为原有的空间地址,__n 是重新申请的地址长度
    for (;;) {
	//@ 当 "内存不足处理例程" 并未被客户设定,便调用 __THROW_BAD_ALLOC,丢出 bad_alloc 异常信息
        __my_malloc_handler = __malloc_alloc_oom_handler;
        if (0 == __my_malloc_handler) { __THROW_BAD_ALLOC; }
        (*__my_malloc_handler)();   //@ 调用处理例程,企图释放内存
        __result = realloc(__p, __n);  //@ 再次尝试配置内存,扩大内存大小
        if (__result) return(__result);
    }
}

//@ 直接将参数 __inst 指定为0
typedef __malloc_alloc_template<0> malloc_alloc;

第二级配置器

  • 如果区块足够大,超过 128 bytes,就交给第一级配置器处理。

  • 当区块小于 128 bytes,则使用 memory bool 管理,这种方法又称为 sub-allocation:

    • 每次配置一大块内存,并维护 free-list。
    • 下一次若有相同大小的内存需求,则直接从 free-list 中拨出。
    • 如果客户端释还小区块,就由配置器回收到 free-list 中。
    • 为了方便管理,SGI 第二级配置器会主动将任何小的区块的内存需求量上调到8的倍数,例如客户端需要 30 bytes,就会自动调整到 32 bytes。第二级配置器维护了16个 free-list,各自管理大小分别为:8,16,24,32,40,48,56,64,72,80,88,96,104,112,120,128 bytes 大小的区块。

    free -list 的结构如下:

//@ free-list 的节点结构,降低维护链表 list 带来的额外负担
union _Obj {
    union _Obj* _M_free_list_link;  //@ 利用联合体特点
    char _M_client_data[1];    /* The client sees this.        */
};

_Obj :

  • 第一字段指向相同形式的另一个 _Obj 。
  • 第二个字段指向实际的区块。
enum {_ALIGN = 8};  //@ 小型区块的上调边界
enum {_MAX_BYTES = 128}; //@ 小区块的上限
enum {_NFREELISTS = 16}; //@ _MAX_BYTES/_ALIGN  free-list 的个数

//@ SGI STL 第二级配置器,GCC 默认使用第二级配置器,其作用是避免太多小额区块造成内存的碎片
//@ 无 “template 类型参数”,且第二参数也没有用,其中第一参数用于多线程环境下
template <bool threads, int inst>
class __default_alloc_template {
...
//@ 将任何小额区块的内存需求量上调至 8 的倍数
static size_t
_S_round_up(size_t __bytes) 
{ return (((__bytes) + (size_t) _ALIGN-1) & ~((size_t) _ALIGN - 1)); }
...

//@ 维护 16 个空闲链表(free list),初始化为0,即每个链表中都没有空闲数据块
static _Obj* __STL_VOLATILE _S_free_list[_NFREELISTS]; 

//@ 根据申请数据块大小找到相应空闲链表的下标,n 从 0 起算
static  size_t _S_freelist_index(size_t __bytes) {
return (((__bytes) + (size_t)_ALIGN-1)/(size_t)_ALIGN - 1);
}

//@ 传贵一个大小为 n 的对象,并可能加入到大小为 n 的其它区块到 free-list
static void* _S_refill(size_t __n);

//@ 配置一个大块空间,可容纳 __nobjs 个大小为 __size 的区块
//@ 如果配置 __nobjs 个区块有所不便,__nobjs 会降低
static char* _S_chunk_alloc(size_t __size, int& __nobjs);

// Chunk allocation state.
static char* _S_start_free;  //@ 内存池起始位置。只在 _S_chunk_alloc() 中变化
static char* _S_end_free;    //@ 内存池结束位置。只在 _S_chunk_alloc() 中变化
static size_t _S_heap_size;
  
};

静态成员变量的定义与初值设定:

template <bool __threads, int __inst>
char* __default_alloc_template<__threads, __inst>::_S_start_free = 0;

template <bool __threads, int __inst>
char* __default_alloc_template<__threads, __inst>::_S_end_free = 0;

template <bool __threads, int __inst>
size_t __default_alloc_template<__threads, __inst>::_S_heap_size = 0;

template <bool __threads, int __inst>
typename __default_alloc_template<__threads, __inst>::_Obj* __STL_VOLATILE
__default_alloc_template<__threads, __inst> ::_S_free_list[
# if defined(__SUNPRO_CC) || defined(__GNUC__) || defined(__HP_aCC)
    _NFREELISTS
# else
    __default_alloc_template<__threads, __inst>::_NFREELISTS
# endif
] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, };

空间配置函数

扫描二维码关注公众号,回复: 10162675 查看本文章
  //@ 申请大小为n的数据块,返回该数据块的起始地址 
  static void* allocate(size_t __n)
  {
    void* __ret = 0;

    //@ 如果需求区块大于 128 bytes,就转调用第一级配置
    if (__n > (size_t) _MAX_BYTES) {
      __ret = malloc_alloc::allocate(__n);
    }
    else {
      //@ 根据申请空间的大小寻找相应的空闲链表(16个空闲链表中的一个)
      _Obj* __STL_VOLATILE* __my_free_list
          = _S_free_list + _S_freelist_index(__n);   
#     ifndef _NOTHREADS
      /*REFERENCED*/
      _Lock __lock_instance;
#     endif
      _Obj* __RESTRICT __result = *__my_free_list;
      //@ 空闲链表没有可用数据块,就将区块大小先调整至 8 倍数边界,然后调用 _S_refill() 重新填充
      if (__result == 0)
        __ret = _S_refill(_S_round_up(__n));
      else {
        //@ 如果空闲链表中有空闲数据块,则取出一个,并把空闲链表的指针指向下一个数据块  
        *__my_free_list = __result -> _M_free_list_link;
        __ret = __result;
      }
    }

    return __ret;
  };

空间释放函数

  //@ 空间释放函数 deallocate()
  static void deallocate(void* __p, size_t __n)
  {
    if (__n > (size_t) _MAX_BYTES)   
      malloc_alloc::deallocate(__p, __n);   //@ 大于 128 bytes,就调用第一级配置器的释放
    else {
    //@ 否则将空间回收到相应空闲链表(由释放块的大小决定)中  
      _Obj* __STL_VOLATILE*  __my_free_list
          = _S_free_list + _S_freelist_index(__n);   
      _Obj* __q = (_Obj*)__p;

      // acquire lock
#       ifndef _NOTHREADS
      /*REFERENCED*/
      _Lock __lock_instance;
#       endif /* _NOTHREADS */
      __q -> _M_free_list_link = *__my_free_list;   //@ 调整空闲链表,回收数据块
      *__my_free_list = __q;
      // lock is released here
    }
  }

重新充填

allocate() 中发现 free-list 没有可用的区块时,就调用 _S_refill 准备为 free-list 重新填充空间,新的空间是从 memory pool 中取得的。预设取得20个新区块,如果 memory pool 中的空间不足,则可获得的区块数可能小于20。

template <bool __threads, int __inst>
void*
__default_alloc_template<__threads, __inst>::_S_refill(size_t __n)
{
    int __nobjs = 20;
    //@ 调用 _S_chunk_alloc(),缺省取 20 个区块作为 free list 的新节点
    //@ __nobjs 是引用传参
    char* __chunk = _S_chunk_alloc(__n, __nobjs);
    _Obj* __STL_VOLATILE* __my_free_list;
    _Obj* __result;
    _Obj* __current_obj;
    _Obj* __next_obj;
    int __i;

    //@ 如果只获得一个数据块,那么这个数据块就直接分给调用者,空闲链表中不会增加新节点
    if (1 == __nobjs) return(__chunk);
    
    //@ 否则根据申请数据块的大小找到相应空闲链表  
    __my_free_list = _S_free_list + _S_freelist_index(__n);  

    /* Build free list in chunk */
      __result = (_Obj*)__chunk;
      //@ 第0个数据块给调用者,地址访问即 chunk~chunk + n - 1  
      *__my_free_list = __next_obj = (_Obj*)(__chunk + __n);
      //@ 将free-list 的各个节点串联起来
      //@ 从1 开始,因为第0个传回给客户端
      for (__i = 1; ; __i++) {
        __current_obj = __next_obj;
        __next_obj = (_Obj*)((char*)__next_obj + __n);
        if (__nobjs - 1 == __i) {
            __current_obj -> _M_free_list_link = 0;
            break;
        } else {
            __current_obj -> _M_free_list_link = __next_obj;
        }
      }
    return(__result);
}

内存池

从内存池中取空间给 free-list 使用,是 _S_chunk_alloc 的工作:

template <bool __threads, int __inst>
char*
__default_alloc_template<__threads, __inst>::_S_chunk_alloc(size_t __size, 
                                                            int& __nobjs)
{
    char* __result;
    size_t __total_bytes = __size * __nobjs;  //@ 需要申请空间的大小 
    size_t __bytes_left = _S_end_free - _S_start_free;  //@ 计算内存池剩余空间

    if (__bytes_left >= __total_bytes) //@ 内存池剩余空间完全满足申请
    {  
        __result = _S_start_free;
        _S_start_free += __total_bytes;
        return(__result);
    } else if (__bytes_left >= __size) //@ 内存池剩余空间不能满足申请,提供一个以上的区块
    {  
        __nobjs = (int)(__bytes_left/__size);
        __total_bytes = __size * __nobjs;
        __result = _S_start_free;
        _S_start_free += __total_bytes;
        return(__result);
    } else 	//@ 内存池剩余空间连一个区块的大小都无法提供    
    {                                               
        size_t __bytes_to_get = 
	  2 * __total_bytes + _S_round_up(_S_heap_size >> 4);
        //@ 尝试让 memory pool 中的残余空间利用起来 
	   //@ 内存池的剩余空间分给合适的空闲链表
        if (__bytes_left > 0) {
            _Obj* __STL_VOLATILE* __my_free_list =
                        _S_free_list + _S_freelist_index(__bytes_left);

            ((_Obj*)_S_start_free) -> _M_free_list_link = *__my_free_list;
            *__my_free_list = (_Obj*)_S_start_free;
        }
        
        //@ 配置 heap 空间,用来补充内存池
        _S_start_free = (char*)malloc(__bytes_to_get);  
		//@ heap 空间不足,malloc() 失败
        if (0 == _S_start_free) { 
            size_t __i;
            _Obj* __STL_VOLATILE* __my_free_list;
	    _Obj* __p;
            // Try to make do with what we have.  That can't
            // hurt.  We do not try smaller requests, since that tends
            // to result in disaster on multi-process machines.
            for (__i = __size;
                 __i <= (size_t) _MAX_BYTES;
                 __i += (size_t) _ALIGN) {
                __my_free_list = _S_free_list + _S_freelist_index(__i);
                __p = *__my_free_list;
                if (0 != __p) {
                    *__my_free_list = __p -> _M_free_list_link;
                    _S_start_free = (char*)__p;
                    _S_end_free = _S_start_free + __i;
                    return(_S_chunk_alloc(__size, __nobjs));
                    // Any leftover piece will eventually make it to the
                    // right free list.
                }
            }
	    _S_end_free = 0;	// In case of exception.
            _S_start_free = (char*)malloc_alloc::allocate(__bytes_to_get);  //@ 调用第一级配置器
            // This should either throw an
            // exception or remedy the situation.  Thus we assume it succeeded.
        }
        _S_heap_size += __bytes_to_get;
        _S_end_free = _S_start_free + __bytes_to_get;
        return(_S_chunk_alloc(__size, __nobjs));  //@ 递归调用自己
    }
}

猜你喜欢

转载自www.cnblogs.com/xiaojianliu/p/12568654.html