python画樱桃小丸子的程序_系统程序员成长计划-内存管理(三)

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

内存管理器

在前面学习共享内存的时候,我们重新实现了循环队列,两个实现的不同之处只是在于内存分配和释放上。对比一下 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 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数据是否被修改,如果被修改则认为存在写越界的错误。

你可能感兴趣的:(python画樱桃小丸子的程序)