SGI STL学习笔记(1):空间配置器(allocator)

SGI STL设计了双层级配置器来完成空间的配置与释放。当配置区块超过128bytes时,调用第一级配置器;当配置区块小于128bytes时,调用第二级配置器。下面通过源码来详细说明。

第一级配置器 __malloc_alloc_template

第一级配置器是对malloc、realloc以及free的封装,当调用malloc和realloc申请不到内存空间的时候,会改调用oom_malloc()和oom_realloc(),这两个函数会反复调用用户传递过来的out of memory handler处理函数,直到能用malloc或者realloc申请到内存为止。如果用户没有传递__malloc_alloc_oom_handler,__malloc_alloc_template会抛出__THROW_BAD_ALLOC异常。

#if 0
#   include <new>
#   define __THROW_BAD_ALLOC throw bad_alloc
#elif !defined(__THROW_BAD_ALLOC)
#   include <iostream.h>
#   define __THROW_BAD_ALLOC cerr << "out of memory" << endl; exit(1)
#endif

template <int inst>
class __malloc_alloc_template {

private:
    static void* oom_malloc(size_t);
    static void* oom_realloc(void*, size_t);
    static void (* __malloc_alloc_oom_handler)();

public:

    static void* allocate(size_t n)
    {
        // 第一级配置器直接使用malloc()
        void* result = malloc(n);

        // 无法满足需求时,改用oom_malloc()
        if(0 == result) result = oom_malloc(n);
        return result;
    }

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

    static void* reallocate(void* p, size_t /* old_sz */, size_t new_sz)
    {
        // 第一级配置器直接使用realloc()
        void* result = realloc(p, new_sz);

        // 无法满足需求时,改用oom_realloc()
        if(0 == result) result = oom_realloc(p, new_sz);
        return result;
    }

    // 指定自己的out-of-memory handler
    static void (* set_malloc_handler(void (*f)()))()
    {
        void (* old)() = __malloc_alloc_oom_handler;
        __malloc_alloc_oom_handler = f;
        return(old);
    }

};

// 初值为0。有待客端设定
template <int inst>
void (* __malloc_alloc_template::__malloc_alloc_oom_handler)() = 0;

// oom_malloc和oom_realloc都有内循环,不断调用“内存不足处理例程”
// 期望在某次调用之后,获得足够的内存而圆满完成任务
// 如果尚未设定“内存不足处理例程”,则会丢出异常,或利用exit(1)硬生生中止程序
template <int inst>
void* __malloc_alloc_template<inst>::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>::oom_realloc(void* p, 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 = realloc(p, n);
        if(result) return(result);
    }
}

// 直接将inst指定为0
typedef __malloc_alloc_template<0> malloc_alloc;

第二级配置器 __default_alloc_template

enum { __ALIGN = 8; }  // 小型区块的上调边界
enum { __MAX_BYTES = 128; }  // 小型区块的上限
enum { __NFREELISTS = __MAX_BYTES/__ALIGN; }  // free list个数,16

// 第一参数用于多线程环境,暂不讨论
template <bool threads, int inst>
class __default_alloc_template {

private:
    // ROUND_UP()将bytes上调至8的倍数
    static size_t ROUND_UP(size_t bytes) {
        return (((bytes) + __ALIGN - 1) & ~(__ALIGN - 1));
    }

private:
    // free list的节点构造
    union obj {
        union obj* free_list_link;  // 指向相同形式的另一个obj
        char client_data[1];  // 指向实际的区块
    };

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重新填充空间
    static void* refill(size_t n);

    // 配置一大块空间,可容纳nobjs个大小为size的区块,nobjs根据实际情况可能会降低
    static char* chunk_alloc(size_t size, int &nobjs);

    static char* start_free;  // 内存池起始位置
    static char* end_free;  // 内存池结束位置
    static size_t heap_size;  // 内存池大小

public:
    static void* allocate(size_t n);
    static void deallocate(void *p, size_t n);
    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};

第二级配置器每次配置一大块内存,都需要维护对应的free list,free lists的构造如下图:
这里写图片描述

第二级配置器的内存分配过程如下:
1.如果申请的内存空间大于128bytes, 则交由第一个分配器处理

2.分配器首先将申请内存的大小上调至8的倍数n(ROUND_UP函数),并根据n找出其对应的空闲链表地址my_free_list

3.如果该空闲链表中有可用的空闲块,则将此空闲块返回并更新my_free_list,否则转到4

4.到这一步,说明my_free_list中没有空闲块可用了,分配器会按照下面的步骤处理:

a) 试着调用chunk_alloc()申请大小为n*20的内存空间,注意的是,此时不一定能申请到n*20大小的内存空间

b) 如果只申请到大小为n的内存空间,则返回给用户,否则到c)

c) 将申请到的n*x(a中说了,不一定是n*20)内存块取出一个返回给用户,其余的内存块链到空闲链表my_free_list中

// 空间配置函数allocate()
static void* allocate(size_t n)
{
    obj* volatile* my_free_list;
    obj* result;

    // 判断区块大小,如果大于128就调用第一级配置器
    if(n > (size_t)__MAX_BYTES){
        return(malloc_alloc::allocate(n));
    }

    // 小于128就检查对应的free lists,如果有可用区块就直接拿来用
    // 如果没有可用区块,就将区块大小上调至8倍数边界,然后调用refill()为free lists重新填充空间
    my_free_list = free_list + FREELIST_INDEX(n);
    result = *my_free_list;
    if(result == 0) {
        void* r = refill(ROUND_UP(n));
        return r;
    }

    *my_free_list = result->free_list_link;
    return (result);
}

内存的释放过程比较简单,它接受两个参数,一个是指向要释放的内存块的指针p,另外一个表示要释放的内存块的大小n。分配器首先判断n,如果n>128bytes,则交由第一级配置器去处理,否则将该内存块加到相应的空闲链表中。

// 空间释放函数deallocate()
static void deallocate(void* p, size_t n)
{
    obj* q = (obj*)p;
    obj* volatile* my_free_list;

    // 判断区块大小,如果大于128就调用第一级配置器
    if(n > (size_t)__MAX_BYTES){
        malloc_alloc::deallocate(p, n);
        return;
    }

    // 小于128就找出对应的free lists,将区块回收
    my_free_list = free_list + FREELIST_INDEX(n);
    q->free_list_link = *my_free_list;
    *my_free_list = q;
}
// 重新填充的新的空间取自内存池
// 缺省取得20个新区块,但万一内存池空间不足,获得的区块数可能小于20
template<bool threads, int inst>
void* __default_alloc_template<threads, inst>::refill(size_t n)
{
    int nobjs = 20;

    // 调用chunk_alloc(),尝试取得nobjs个区块作为free list的新节点
    // 这里的参数nobjs是按引用传递的(pass by reference)
    char* chunk = chunk_alloc(n, nobjs);

    obj* volatile* my_free_list;

    obj* result;
    obj* current_obj, * next_obj;
    int i;

    // 如果只获得一个区块,这个区块就分配给调用者用,free list无新节点
    if(1 == nobjs) return(chunk);

    //否则准备调整free list,纳入新节点
    my_free_list = free_list + FREELIST_INDEX(n);

    // 这一块准备返回给客端
    result = (obj*)chunk;

    // 以下导引free list指向新配置的空间
    *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->free_list_link = 0;
            break;
        } else {
            current_obj->free_list_link = next_obj;
        }
    }
    return(result);
}

chunk_alloc()的具体过程如下:
1.如果start_free和end_free之间的空间足够分配size*20大小的内存空间,则从这个空间中取出size*20大小的内存空间,更新start_free并返回申请到的内存空间的起始地址,否则转到2

2.如果start_free和end_free之间的空间足够分配大于size的内存空间,则分配整数倍于size的内存空间,更新start_free,由nobj返回这个整数,并返回申请到的内存空间的起始地址,否则转到3

3.到这一步,说明内存池中连一块大小为size的内存都没有了,此时如果内存池中还有一些内存(这些内存大小肯定小于size),则将这些内存插入到其对应大小的空闲分区链中

4.调用malloc申请大小为(2*n*20 + 附加量)的内存空间, 如果申请成功,更新start_free, end_free和heap_size,并重新调用chunk_alloc(),否则转到5

5.到这一步,说明4中调用malloc失败,这时配置器依次遍历16个空闲分区链,只要有一个空闲链,就释放该链中的一个节点,重新调用chunk_alloc()

// chunk_alloc()从内存池取空间给free list使用
// 假设size已经适当上调至8的倍数
// 参数nobjs是按引用传递
template <bool threads, int inst>
char*
__default_alloc_template<threads, inst>::
chunk_alloc(size_t size, int& nobjs)
{
    char* result;
    size_t total_bytes = size * nobjs;
    size_t bytes_left = end_free - start_free;

    // 内存池剩余空间完全满足需求量,就直接调出20个区块返回给free list
    if(bytes_left >= total_bytes) {
        result = start_free;
        start_free += total_bytes;
        return(result);
    } else if(bytes_left >= size) {
        // 内存池剩余空间不能完全满足需求量,但足够供应一个(含)以上的区块,就拨出这些空间出去
        // 此时按引用传递的nobjs被修改为实际能够供应的区块数
        nobjs = bytes_left / size;
        total_bytes = size * nobjs;
        result = start_free;
        start_free += total_bytes;
        return(result);
    } else {
        // 内存池剩余空间连一个区块的大小也无法提供,就需要利用malloc从heap中配置内存
        // 新水量的大小为需求量的两倍,再加上一个随着配置次数增加而愈来愈大的附加量
        size_t bytes_to_get = 2 * total_bytes + ROUND_UP(heap_size >> 4);

        // 让内存池残余的零头还有利用价值
        if(bytes_left > 0) {
            obj* volatile* my_free_list = free_list + FREELIST_INDEX(bytes_left);
            ((obj*)start_free)->free_list_link = *my_free_list;
            *my_free_list = (obj*)start_free;
        }

        start_free = (char*)malloc(bytes_to_get);

        // heap空间不足,malloc失败
        if(0 == start_free) {
            int i;
            obj* volatile* my_free_list, *p;

            // 遍历空闲分区链,如果尚有未用区块,就释放该区块,然后递归调用chunk_alloc()
            for(i = size; i <= __MAX_BYTES; i += __ALIGN) {
                my_free_list = free_list + FREELIST_INDEX(i);
                p = *my_free_list;

                if(0 != p) {
                    *my_free_list = p->free_list_link;
                    start_free = (char*)p;
                    end_free = start_free + i;
                    return(chunk_alloc(size, nobjs));
                }
            }
            // 如果到处都没内存可用了,就调用第一级配置器,看看oom机制能否有效
            // 这会导致抛出异常,或内存不足的情况得到改善
            end_free = 0;
            start_free = (char*)malloc_alloc::allocate(bytes_to_get);
        }
        heap_size += bytes_to_get;
        end_free = start_free + bytes_to_get;

        // 递归调用自己
        return(chunk_alloc(size, nobjs));
    }
}

参考资料:
1.《STL源码剖析》
2.STL源码学习—-内存管理. http://www.cnblogs.com/cobbliu/archive/2012/04/05/2431804.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值