zoukankan      html  css  js  c++  java
  • 系统程序员成长计划-内存管理(三)

    转载时请注明出处和作者联系方式
    文章出处:http://www.limodev.cn/blog
    作者联系方式:李先静 <xianjimli at hotmail dot com>

    内存管理器

    在前面学习共享内存的时候,我们重新实现了循环队列,两个实现的不同之处只是在于内存分配和释放上。对比一下 fifo_ring_create的实现:

    第一种实现用malloc分配内存。

    FifoRing* fifo_ring_create(size_t length)
    {
    FifoRing* thiz = NULL;

    return_val_if_fail(length > 1, NULL);

    thiz = (FifoRing*)malloc(sizeof(FifoRing) + length * sizeof(void*));

    if(thiz != NULL)
    {
    thiz->r_cursor = 0;
    thiz->w_cursor = 0;
    thiz->length = length;
    }

    return thiz;
    }

    第二种实现用 shmem_alloc分配内存。

    FifoRing* fifo_ring_create(size_t length)
    {
    FifoRing* thiz = NULL;

    return_val_if_fail(length > 1, NULL);

    thiz = (FifoRing*)shmem_alloc(sizeof(FifoRing) + length * sizeof(void*));

    if(thiz != NULL)
    {
    if(thiz->inited == 0)
    {
    thiz->r_cursor = 0;
    thiz->w_cursor = 0;
    thiz->length = length;
    thiz->inited = 1;
    }
    }

    return thiz;
    }

    只是一行代码之差,就要把整个队列重写一遍,不符合我们前面倡导的DRY(don’t repeat yourself)原则。这里可以看出,内存管理器有不同的实现,所以我们引入内存管理器这个接口来隔离变化。内存管理器的基本功能有:

    o 分配内存
    o 释放内存
    o 扩展/缩小已经分配的内存
    o 分配清零的内存

    据此,我们定义Allocator接口如下:

    struct  _Allocator;
    typedef struct _Allocator Allocator;

    typedef void* (*AllocatorCallocFunc)(Allocator* thiz, size_t nmemb, size_t size);
    typedef void* (*AllocatorAllocFunc)(Allocator* thiz, size_t size);
    typedef void (*AllocatorFreeFunc)(Allocator* thiz, void *ptr);
    typedef void* (*AllocatorReallocFunc)(Allocator* thiz, void *ptr, size_t size);
    typedef void (*AllocatorDestroyFunc)(Allocator* thiz);

    struct _Allocator
    {
    AllocatorCallocFunc calloc;
    AllocatorAllocFunc alloc;
    AllocatorFreeFunc free;
    AllocatorReallocFunc realloc;
    AllocatorDestroyFunc destroy;

    char priv[0];
    };

    基于malloc系统函数的实现:

    static void*  allocator_normal_calloc(Allocator* thiz, size_t nmemb, size_t size)
    {
    return calloc(nmemb, size);
    }
    ...
    Allocator* allocator_normal_create(void)
    {
    Allocator* thiz = (Allocator*)calloc(1, sizeof(Allocator));

    if(thiz != NULL)
    {
    thiz->calloc = allocator_normal_calloc;
    thiz->alloc = allocator_normal_alloc;
    thiz->realloc = allocator_normal_realloc;
    thiz->free = allocator_normal_free;
    thiz->destroy = allocator_normal_destroy;
    }

    return thiz;
    }

    这里函数与标准C函数一一对应,只需要简单包装就行了。

    对于共享内存,通常的做法是先分配一大块内存,然后进行二次分配,此时需要编写自己的内存管理器。内存分配器是很奇特的,任何初学者都可以设计自己 的内存分配器,但同时任何高手都不敢说自己能设计出最好的内存分配器。为什么内存分配器很难写好呢?因为设计好的内存分配器需要考虑很多因素:

    o 最大化兼容性

    实现内存分配器时,先要定义出分配器的接口函数。接口函数没有必要标新立异,而是要遵循现有标准(如POSIX或者Win32),让使用者可以平滑的过度到新的内存分配器上。

    o 最大化可移植性

    通常情况下,内存分配器要向OS申请内存,然后进行二次分配,这要调用OS提供的函数才行。OS提供的函数则是因平台而异,尽量抽象出平台相关的代码,才能保证内存分配器的可移植性。

    o 浪费最小的空间

    内存分配器要管理内存,必然要使用一些自己的数据结构,这些数据结构本身也要占内存空间。在用户眼中,这些内存空间毫无疑问是浪费掉了,如果浪费在 内存分配器本身的内存太多,显然是不可以接受的。内存碎片也是浪费空间的罪魁祸首,内存碎片是一些不连续的小块内存,它们总量可能很大,但因为其非连续性 而无法使用,这些空间在某种程度上说也是浪费的。

    o 最快的速度

    内存分配/释放是常用的操作。按着2/8原则,常用函数的性能对系统的整体性能影响最大,所以内存分配器的速度越快越好。遗憾的是,最先匹配算法,最优匹配算法和最差匹配算法,谁也不能说谁更快,因为快与慢要依赖于具体的应用环境。

    o 最大化可调性(以适应于不同的情况)

    内存管理算法设计的难点就在于要适应不同的情况。事实上,如果缺乏应用的上下文,是无法评估内存管理算法的好坏的,因为专用算法总是在时/空性能上 有更优的表现。为每种情况都写一套内存管理算法,显然是不太合适的。我们不需要追求最优算法,那样代价太高,能达到次优就行了。设计一套通用内存管理算 法,通过一些参数对它进行配置,可以让它在特定情况也有相当出色的表现,这就是可调性。

    o 最大化调试功能

    作为一个C/C++程序员,内存错误可以说是我们的噩梦,上一次的内存错误一定还让你记忆犹新。内存分配器提供的调试功能,强大易用,特别对于嵌入式环境来说,内存错误检测工具缺乏的情况下,内存分配器提供的调试功能就更不可少了。

    o 最大化适应性

    前面说了最大化可调性,以便让内存分配器适用于不同的情况。但是,对于不同情况都要去调整配置,还是有点麻烦。尽量让内存分配器适用于很广的情况,只有极少情况下才去调整配置,这就是内存分配器的适应性。

    设计是一个多目标优化的过程,而且有些目标之间存在着竞争。如何平衡这些竞争是设计的难点之一。在不同的情况下,这些目标的重要性是不一样的,所以根本不存在一个最好的内存分配器。换句话说,内存分配器的实现是变化的,要根据不同的应用环境而变化。

    下面我们实现一个傻瓜型的内存管理器,按上面的标准来看,它没有什么实际的意义,但它的优点是简单,仅仅200多行代码,就展示了内存管理器的基本原理。

    o 分配过程

    所有空闲的内存块放在一个双向链表中,最初只有一块。分配时使用首次匹配算法,在第一个空闲块上进行分配。

    static void*  allocator_self_manage_alloc(Allocator* thiz, size_t size)
    {
    FreeNode* iter = NULL;
    size_t length = REAL_SIZE(size);
    PrivInfo* priv = (PrivInfo*)thiz->priv;

    /*查找第一个满足条件的空闲块*/
    for(iter = priv->free_list; iter != NULL; iter = iter->next)
    {
    if(iter->length > length)
    {
    break;
    }
    }

    return_val_if_fail(iter != NULL, NULL);

    /*如果找到的空闲块刚好满足需求,就从空闲块链表中移出它*/
    if(iter->length < (length + MIN_SIZE))
    {
    if(priv->free_list == iter)
    {
    priv->free_list = iter->next;
    }

    if(iter->prev != NULL)
    {
    iter->prev->next = iter->next;
    }
    if(iter->next != NULL)
    {
    iter->next->prev = iter->prev;
    }
    }
    else
    {
    /*如果找到的空闲块比较大,就把它拆成两个块,把多余的空闲内存放回去*/
    FreeNode* new_iter = (FreeNode*)((char*)iter + length);

    new_iter->length = iter->length - length;
    new_iter->next = iter->next;
    new_iter->prev = iter->prev;

    if(iter->prev != NULL)
    {
    iter->prev->next = new_iter;
    }
    if(iter->next != NULL)
    {
    iter->next->prev = new_iter;
    }

    if(priv->free_list == iter)
    {
    priv->free_list = new_iter;
    }
    iter->length = length;
    }
    /*返回可用的内存*/
    return (char*)iter + sizeof(size_t);
    }

    这里对空闲块的管理不占用有效内存空间,它只是强制的把空闲块转换成 FreeNode结构,这要求任何空闲块的大小都不小于sizeof(FreeNode)。

    o 释放内存

    释放时把内存块放回空闲链表,然后对相邻居的内存块进行合并。

    static void   allocator_self_manage_free(Allocator* thiz, void *ptr)
    {
    FreeNode* iter = NULL;
    FreeNode* free_iter = NULL;
    PrivInfo* priv = (PrivInfo*)thiz->priv;

    return_if_fail(ptr != NULL);

    free_iter = (FreeNode*)((char*)ptr - sizeof(size_t));

    free_iter->prev = NULL;
    free_iter->next = NULL;

    if(priv->free_list == NULL)
    {
    priv->free_list = free_iter;

    return;
    }
    /*根据内存块地址的大小,把它插入到适当的位置。*/
    for(iter = priv->free_list; iter != NULL; iter = iter->next)
    {
    if((size_t)iter > (size_t)free_iter)
    {
    free_iter->next = iter;
    free_iter->prev = iter->prev;
    if(iter->prev != NULL)
    {
    iter->prev->next = free_iter;
    }
    iter->prev = free_iter;

    if(priv->free_list == iter)
    {
    priv->free_list = free_iter;
    }

    break;
    }

    if(iter->next == NULL)
    {
    iter->next = free_iter;
    free_iter->prev = iter;

    break;
    }
    }

    /*对相邻居的内存进行合并*/
    allocator_self_manage_merge(thiz, free_iter);

    return;
    }

    有了Allocator接口,我们也可以通过装饰模式,为内存管理器加上越界/泄露检查等其它辅助功能。下面的Allocator是检查内存越界的装饰。

    o 实现函数

    static void*  allocator_checkbo_alloc(Allocator* thiz, size_t size)
    {
    char* ptr = NULL;
    size_t total = size + 3 * sizeof(void*);
    PrivInfo* priv = (PrivInfo*)thiz->priv;

    if((ptr = allocator_alloc(priv->real_allocator, total)) != NULL)
    {
    *(size_t*)ptr = size;
    memset(ptr + sizeof(void*), BEGIN_MAGIC, sizeof(void*));
    memset(ptr + 2 * sizeof(void*) + size, END_MAGIC, sizeof(void*));
    }

    return ptr + 2 * sizeof(void*);
    }

    分配内存时:多分配3 * sizeof(void*)个字节,分别用来记录内存块的长度及前后的Magic数据。然后调用实际的(即被装饰的)内存分配器分配内存,记录长度并填充Magic数据,最后返回内存指针给调用者。

    static void   allocator_checkbo_free(Allocator* thiz, void *ptr)
    {
    PrivInfo* priv = (PrivInfo*)thiz->priv;

    if(ptr != NULL)
    {
    char magic[sizeof(void*)];
    char* real_ptr = (char*)ptr - 2 * sizeof(void*);
    size_t size = *(size_t*)(real_ptr);

    memset(magic, BEGIN_MAGIC, sizeof(void*));
    assert(memcmp((char*)ptr - sizeof(void*), magic, sizeof(void*)) == 0);
    memset(magic, END_MAGIC, sizeof(void*));
    assert(memcmp((char*)ptr + size , magic, sizeof(void*)) == 0);

    allocator_free(priv->real_allocator, real_ptr);
    }

    return;
    }

    释放内存时:取得内存块的长度,然后检查前后的magic数据是否被修改,如果被修改则认为存在写越界的错误。

  • 相关阅读:
    AJAX 弹出窗消息类
    正则表达式验证总结
    Coolite 三列布局演示
    Coolite: Button、TextField、DataField、ComBox
    页面返回
    Coolite GridPanel 操作之一:获取gridpanel 选中行的记录信息
    页面上Enter 键禁用
    new、abstract、virtual、override,sealed关键字区别和使用代码示例
    Coolite TreePanel 操作之一:TreePanel 刷新
    调用WCF出现 The remote server returned an error: (401) Unauthorized. 错误
  • 原文地址:https://www.cnblogs.com/zhangyunlin/p/6167538.html
Copyright © 2011-2022 走看看