幻想小说网 酷文学 深夜书屋 叮当小说网 找小说网 无限小说网 红尘小说网

系统程序员成长计划-内存管理(三)

转载时请注明出处和作者联系方式
文章出处: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数据是否被修改,如果被修改则认为存在写越界的错误。

posted on 2009-04-24 08:46  张云临  阅读(141)  评论(0编辑  收藏  举报

导航