SGI STL内存池源码剖析

SGI STL内存池

SGI STL包含了一级空间配置器和二级空间配置器,其中一级空间配置器allocator采用malloc和free来管理内存,和C++标准库中提供的allocator是一样的,但其二级空间配置器allocator采用了基于freelist自由链表原理的内存池机制实现内存管理。

一级空间配置器:

allocate:负责给容器开辟内存空间 => malloc
deallocate:负责释放容器的内存空间 => free
construct:负责给容器构造一个对象 => 定位new实现,在指定的内存上构造处对象(拷贝构造)
destroy:负责析构容器的对象 => p->~T()

二级空间配置器:

内存池:主要采用链表数组的方式。
在这里插入图片描述

重要类型和变量定义

// 内存池的粒度信息
//chunk块的最小单位
enum {_ALIGN = 8};
//chunk块的最大单位
enum {_MAX_BYTES = 128};
//16个chunk块类型,从8,16,24...128共16个
enum {_NFREELISTS = 16};
// 每一个内存chunk块的信息,第一个变量指针指向邻接的下一chunk地址
union _Obj {
    union _Obj* _M_free_list_link;
    char _M_client_data[1]; /* The client sees this. */
};
// 组织所有自由链表的数组,数组的每一个元素的类型是_Obj*,全部初始化为0
static _Obj* __STL_VOLATILE _S_free_list[_NFREELISTS];
// Chunk allocation state. 记录内存chunk块的分配情况
//指向可用内存空间的起始地址
static char* _S_start_free;
//指向可用内存空间的末尾
static char* _S_end_free;
//目前分配的总内存大小
static size_t _S_heap_size;
template char* __default_alloc_template<__threads, __inst>::_S_start_free = 0;
template char* __default_alloc_template<__threads, __inst>::_S_end_free = 0;
template size_t __default_alloc_template<__threads, __inst>::_S_heap_size = 0;

重要的辅助函数

/*将 __bytes 上调至最邻近的 8 的倍数*/
static size_t _S_round_up(size_t __bytes){ 
    return (((__bytes) + (size_t) _ALIGN-1) & ~((size_t) _ALIGN - 1)); 
}
/*返回 __bytes 大小的chunk块位于 free-list 中的编号,例如8字节大小的chunk块就在静态链表的0,16在1以此类推*/
static size_t _S_freelist_index(size_t __bytes) {
    return (((__bytes) + (size_t)_ALIGN-1)/(size_t)_ALIGN - 1); 
}

内存池的管理函数

// 分配内存的入口函数

static void* allocate(size_t __n)

如果需要分配的内存空间大于128个字节,就直接使用malloc函数,不再使用内存池。

// 分配内存的入口函数
static void* allocate(size_t __n){
    void* __ret = 0;

    if (__n > (size_t) _MAX_BYTES) {
      __ret = malloc_alloc::allocate(__n);
    }
    else {
      _Obj* __STL_VOLATILE* __my_free_list
          = _S_free_list + _S_freelist_index(__n);
      // Acquire the lock here with a constructor call.
      // This ensures that it is released in exit or during stack
      // unwinding.
#     ifndef _NOTHREADS
      /*REFERENCED*/
      _Lock __lock_instance;
#     endif
      _Obj* __RESTRICT __result = *__my_free_list;
      if (__result == 0)
        __ret = _S_refill(_S_round_up(__n));
      else {
        *__my_free_list = __result -> _M_free_list_link;
        __ret = __result;
      }
    }

    return __ret;
  };

// 负责把分配好的chunk块进行连接,添加到自由链表当中

static void* _S_refill(size_t __n)
// 负责把分配好的chunk块进行连接,添加到自由链表当中
static void* _S_refill(size_t __n)
{
    int __nobjs = 20;
    char* __chunk = _S_chunk_alloc(__n, __nobjs);
    _Obj* __STL_VOLATILE* __my_free_list;
    _Obj* __result;
    _Obj* __current_obj;
    _Obj* __next_obj;
    int __i;
    //如果只剩下一个chunk块,就无需chunk块的连接了

    if (1 == __nobjs) return(__chunk);
    __my_free_list = _S_free_list + _S_freelist_index(__n);

    /* Build free list in chunk */
      __result = (_Obj*)__chunk;
      *__my_free_list = __next_obj = (_Obj*)(__chunk + __n);
      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);
}

// 分配相应内存字节大小的chunk块,并且给下面三个成员变量初始化

static char* _S_chunk_alloc(size_t __size, int& __nobjs)
// 分配相应内存字节大小的chunk块,并且给下面三个成员变量初始化
static char* _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); 
    //如果备用的内存空间不够分配,判断是否够一个chunk块,如果够就能分配几个就分配几个                 
    } 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 {
    /*
    如果备用的内存空间连所需的一个chunk块都分配不了,就先将备用剩余的内存空间以头插法的方式给正好那么大的chunk链表
    然后重新去开辟内存空间,每次新分配的内存空间会越来越大。
    */
        size_t __bytes_to_get = 
  2 * __total_bytes + _S_round_up(_S_heap_size >> 4);
        // Try to make use of the left-over piece.
        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;
        }
        _S_start_free = (char*)malloc(__bytes_to_get);
        //如果内存分配失败,就遍历静态链表,去更大的chunk块处借。
        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.
                }
            }
            /*如果更大的chunk块也没有了,只能尝试再次申请看是否有其他线程释放内存,
            还是申请不到的话就去找是否有设内存释放回调函数,如果没有就抛异常,
            有的话就死循环调用内存释放的回调函数,直到有可用的内存空间
            */
    _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));
    }
}

// 把chunk块归还到内存池

static void deallocate(void* __p, size_t __n);
  // 把chunk块归还到内存池
static void deallocate(void* __p, size_t __n);
  {
    if (__n > (size_t) _MAX_BYTES)
      malloc_alloc::deallocate(__p, __n);
    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 */
      //头插法的方式将不用的chunk块放到对应链表的头上,供下一次申请时使用。
      __q -> _M_free_list_link = *__my_free_list;
      *__my_free_list = __q;
      // lock is released here
    }
  }

// 内存池扩容函数

template void*__default_alloc_template::reallocate(void* __p,size_t __old_sz,size_t __new_sz);
// 内存池扩容函数
template void*__default_alloc_template::reallocate(void* __p,size_t __old_sz,size_t __new_sz);
{
    void* __result;
    size_t __copy_sz;

    if (__old_sz > (size_t) _MAX_BYTES && __new_sz > (size_t) _MAX_BYTES) {
        return(realloc(__p, __new_sz));
    }
    if (_S_round_up(__old_sz) == _S_round_up(__new_sz)) return(__p);
    __result = allocate(__new_sz);
    __copy_sz = __new_sz > __old_sz? __old_sz : __new_sz;
    memcpy(__result, __p, __copy_sz);
    deallocate(__p, __old_sz);
    return(__result);

内存池的优点:

防止小块内存频繁的分配,释放,造成内存很多的碎片出来,内存没有更多的连续的大内存块。所以应用对于小块内存的操作,一般都会使用内存池来进行管理。
SGI STL二级空间配置器内存池的实现优点:
1.对于每一个字节数的chunk块分配,都是给出一部分进行使用,另一部分作为备用,这个备用可以给当前字节数使用,也可以给其它字节数使用。
2.对于备用内存池划分完chunk块以后,如果还有剩余的很小的内存块,再次分配的时候,会把这些小的内存块再次分配出去,备用内存池使用的干干净净!
3.当指定字节数内存分配失败以后,有一个异常处理的过程,遍历bytes - 128字节所有的chunk块进行查看,如果哪个字节数有空闲的chunk块,直接借一个出去,
如果上面操作失败,还会调用oom_malloc这么一个预先设置好的malloc内存分配失败以后的回调函数,没设置抛异常malloc throw bad_alloc
设置了就死循环调用回调函数,直到有可以用内存空间来分配(*oom_malloc_handler)();malloc

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值