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