整体的设计:使用分离存储的方案,维护一个存储空闲链表的数组free_block_list,每个大小类一个空闲链表,根据2的幂来划分大小,块的设计采用书本上介绍的显示空闲链表的方案。
当可供使用的空间不够时,就会扩展堆。在extend_heap中会增加堆顶的大小,并将新的空闲块挂载到free_block_list上。为了减少内存碎片,会根据新申请的块的前一个块是否空闲来决定是否与其合并。
void *extend_heap(size_t size)
{
void *ptr;
// align
size = ALIGN(size);
// 增加堆的大小
if ((ptr = mem_sbrk(size)) == (void *)-1)
return NULL;
// 初始化新申请的空闲块
PUT(HDRP(ptr), PACK(size, 0));
PUT(FTRP(ptr), PACK(size, 0));
/* 注意这个块是堆的结尾,所以还要设置一下结尾 */
PUT(HDRP(NEXT_BLKP(ptr)), PACK(0, 1));
// 将新申请的空闲块挂到free_block_list上
insert_node(ptr, size);
// 合并空闲块
return merge_free_block(ptr);
}
insert_node实现了将一个空闲块插入到free_block_list的操作,free_block_list中一共有16条链表,下标由0到15升序排列,free_block_list中的元素指向队列的队尾
。将ptr插入block_free_list,链表从大到小排序,在insert_node中逆序遍历链表查找到对应的位置,将空闲块插入。需要注意的是,当链表为空时free_block_list中对应的元素为NULL。
void insert_node(void *ptr, size_t size)
{
int idx = 0;
void *next_node = NULL;
void *pre_node = NULL;
// 找到相应的桶
for (;(idx < FREE_LIST_LENGTH - 1) && (size > 1); size >>= 1)
idx++;
next_node = free_block_list[idx];
// next_node为pre_node的前一个结点
for (; (next_node != NULL) && (size > GET_SIZE(HDRP(next_node))); next_node = GET_PRED_NODE(next_node))
pre_node = next_node;
if (next_node != NULL && pre_node != NULL) { // case 1
ASSIGN(GET_PRED_ADDR(ptr), next_node);
ASSIGN(GET_NEXT_ADDR(next_node), ptr);
ASSIGN(GET_NEXT_ADDR(ptr), pre_node);
ASSIGN(GET_PRED_ADDR(pre_node), ptr);
}
else if (next_node != NULL && pre_node == NULL) { // case 2
ASSIGN(GET_PRED_ADDR(ptr), next_node);
ASSIGN(GET_NEXT_ADDR(next_node), ptr);
ASSIGN(GET_NEXT_ADDR(ptr), NULL);
free_block_list[idx] = ptr;
}
else if (next_node == NULL && pre_node != NULL) { // case 3
ASSIGN(GET_PRED_ADDR(ptr), NULL);
ASSIGN(GET_NEXT_ADDR(ptr), pre_node);
ASSIGN(GET_PRED_ADDR(pre_node), ptr);
}
else if (next_node == NULL && pre_node == NULL) { // case 4
ASSIGN(GET_PRED_ADDR(ptr), NULL);
ASSIGN(GET_NEXT_ADDR(ptr), NULL);
free_block_list[idx] = ptr;
}
}
delete_node实现了从free_block_list中取走一个空闲块的操作,寻找对应块的操作与insert_node中的类似。
// 从空闲链表中取出一个块
void delete_node(void *ptr)
{
int idx = 0;
size_t size = GET_SIZE(HDRP(ptr));
// 找到对应的桶
for (; (idx < FREE_LIST_LENGTH - 1) && (size > 1); size >>= 1)
idx++;
if (GET_PRED_NODE(ptr) != NULL && GET_NEXT_NODE(ptr) != NULL) { // case 1
ASSIGN(GET_NEXT_ADDR(GET_PRED_NODE(ptr)), GET_NEXT_NODE(ptr));
ASSIGN(GET_PRED_ADDR(GET_NEXT_NODE(ptr)), GET_PRED_NODE(ptr));
}
else if (GET_PRED_NODE(ptr) != NULL && GET_NEXT_NODE(ptr) == NULL) { // case 2
ASSIGN(GET_NEXT_ADDR(GET_PRED_NODE(ptr)), NULL);
free_block_list[idx] = GET_PRED_NODE(ptr);
}
else if (GET_PRED_NODE(ptr) == NULL && GET_NEXT_NODE(ptr) != NULL) { // case 3
ASSIGN(GET_PRED_ADDR(GET_NEXT_NODE(ptr)), NULL);
}
else if (GET_PRED_NODE(ptr) == NULL && GET_NEXT_NODE(ptr) == NULL) { // case 4
free_block_list[idx] = NULL;
}
}
merge_free_block实现的是合并相邻块的操作,这个函数只会实现与ptr相邻的空闲块的合并,合并的流程为:确认相邻的空闲块,计算连续空闲块的大小,确认新的空闲块的头部的位置,重置新的空闲块头部中的大小信息,最后将空闲块插入free_block_list。
void *merge_free_block(void *ptr)
{
unsigned short prev_allocated = GET_ALLOC(HDRP(PREV_BLKP(ptr)));
unsigned short next_allocated = GET_ALLOC(HDRP(NEXT_BLKP(ptr)));
size_t size = GET_SIZE(HDRP(ptr));
// 将相邻的空闲块合并
if (prev_allocated && next_allocated) // case 1
return ptr;
else if (prev_allocated && !next_allocated) { // case 2
delete_node(ptr);
delete_node(NEXT_BLKP(ptr));
size += GET_SIZE(HDRP(NEXT_BLKP(ptr)));
PUT(HDRP(ptr), PACK(size, 0));
PUT(FTRP(ptr), PACK(size, 0));
}
else if (!prev_allocated && next_allocated) { // case 3
delete_node(ptr);
delete_node(PREV_BLKP(ptr));
size += GET_SIZE(HDRP(PREV_BLKP(ptr)));
PUT(FTRP(ptr), PACK(size, 0));
PUT(HDRP(PREV_BLKP(ptr)), PACK(size, 0));
ptr = PREV_BLKP(ptr);
}
else { // case 4
delete_node(ptr);
delete_node(PREV_BLKP(ptr));
delete_node(NEXT_BLKP(ptr));
size += GET_SIZE(HDRP(PREV_BLKP(ptr))) + GET_SIZE(HDRP(NEXT_BLKP(ptr)));
PUT(HDRP(PREV_BLKP(ptr)), PACK(size, 0));
PUT(FTRP(NEXT_BLKP(ptr)), PACK(size, 0));
ptr = PREV_BLKP(ptr);
}
// 将合并好的空闲块加入到空闲链接表中
insert_node(ptr, size);
return ptr;
}
place中实现的是对ptr所指向的空闲块的使用,首先要从空闲链表中将对应的空闲块删除掉。为了充分利用空间,会将空闲块中未使用的部分与使用的部分分割开,并将未使用的部分成为一个新的空闲块挂载到free_block_list上,但是如果剩余部分无法容纳空闲块的信息就会放弃分割的策略。根据一些参考资料,发现将较小的空闲块放在同一边有利于减少碎片率,所以分割时对于小于256B的块会统一放在前面。
void *place(void *ptr, size_t size)
{
size_t ptr_size = GET_SIZE(HDRP(ptr));
// 剩余块的大小
size_t unused_size = ptr_size - size;
// 从空闲链表中删除该结点
delete_node(ptr);
// 若分离出来的空间不足以容纳header和footer和指向前后块的指针
if (unused_size < 16) {
PUT(HDRP(ptr), PACK(ptr_size, 1));
PUT(FTRP(ptr), PACK(ptr_size, 1));
}
else if (size >= 256) {
// 剩余的空间放在前面
PUT(HDRP(ptr), PACK(unused_size, 0));
PUT(FTRP(ptr), PACK(unused_size, 0));
PUT(HDRP(NEXT_BLKP(ptr)), PACK(size, 1));
PUT(FTRP(NEXT_BLKP(ptr)), PACK(size, 1));
insert_node(ptr, unused_size);
return NEXT_BLKP(ptr);
}
else {
PUT(HDRP(ptr), PACK(size, 1));
PUT(FTRP(ptr), PACK(size, 1));
// 剩余空间放在后面
PUT(HDRP(NEXT_BLKP(ptr)), PACK(unused_size, 0));
PUT(FTRP(NEXT_BLKP(ptr)), PACK(unused_size, 0));
insert_node(NEXT_BLKP(ptr), unused_size);
}
return ptr;
}
mm_malloc实现了从可用堆空间中分配size B给调用者的操作,实现的流程为先在free_block_list中寻找到合适的空闲块,如果free_block_list中不存在足够大的空闲块,通过extend_heap函数来扩展堆的大小,将新的空闲块分配给调用者。
void *mm_malloc(size_t size)
{
if (size == 0)
return NULL;
// align
if (size <= DSIZE)
size = 2 * DSIZE;
else
size = ALIGN(size + DSIZE);
int idx = 0;
size_t searchsize = size;
void *ptr = NULL;
// 寻找空闲块
for (idx = 0; idx < FREE_LIST_LENGTH; idx++) {
if (((searchsize <= 1) && (free_block_list[idx] != NULL))) {
// 在桶中寻找空闲块
for (ptr = free_block_list[idx]; (ptr != NULL) && ((size > GET_SIZE(HDRP(ptr)))); )
ptr = GET_PRED_NODE(ptr);
// 找到可用的空闲块
if (ptr != NULL)
break;
}
searchsize >>= 1;
}
// 找不到合适的空闲块,扩展堆的容量
if (ptr == NULL) {
if ((ptr = extend_heap(MAX(size, CHUNKSIZE))) == NULL)
return NULL;
}
// 使用空闲块
ptr = place(ptr, size);
return ptr;
}
mm_free实现了回收一个已分配块的操作,流程为将该块的头部和脚部中的分配信息设置为未分配,然后插入到free_block_list中。为了减少内存碎片,在最后会检查是否有可合并的相邻块,如果有则合并。
void mm_free(void *ptr)
{
size_t size = GET_SIZE(HDRP(ptr));
PUT(HDRP(ptr), PACK(size, 0));
PUT(FTRP(ptr), PACK(size, 0));
// 将回收的块插入free_block_list
insert_node(ptr, size);
// 合并相邻的块
merge_free_block(ptr);
}
mm_realloc实现了为ptr中的内容移到一个大小至少为size的块,如果原来的块的大小大于size,直接返回原来的ptr。内存拷贝是非常影响分配器性能的一个操作,为了避免内存拷贝,检查ptr的下一个块是否为空闲,如果是空闲的则计算两个块加起来的大小是否大于size,如果大于size在mm_realloc函数中直接合并两个块,并返回原来的ptr。如果以上的方法行不通,调用malloc分配一个新的块,并将原来块中的内容拷贝过去,返回新的块的地址。
void *mm_realloc(void *ptr, size_t size)
{
void *new_block = ptr;
int unused_size;
if (size == 0)
return NULL;
// align
if (size <= DSIZE)
size = 8;
else
size = ALIGN(size + DSIZE);
// 如果当前的块够用
if (GET_SIZE(HDRP(ptr)) >= size)
return ptr;
else if (!GET_ALLOC(HDRP(NEXT_BLKP(ptr))) && GEdeT_SIZE(HDRP(NEXT_BLKP(ptr))) + GET_SIZE(HDRP(ptr)) >= size) { // 检查是否下一个相邻块是否可用
// 若下一个相邻块可以提供足够的空间,直接合并这两个块
delete_node(NEXT_BLKP(ptr));
unused_size = GET_SIZE(HDRP(NEXT_BLKP(ptr)));
PUT(HDRP(ptr), PACK(GET_SIZE(HDRP(ptr)) + unused_size, 1));
PUT(FTRP(ptr), PACK(GET_SIZE(HDRP(ptr)) + unused_size, 1));
}
else {
// 申请与之前不连续的块,并将之前的内容拷贝过去
new_block = mm_malloc(size);
memcpy(new_block, ptr, GET_SIZE(HDRP(ptr)));
mm_free(ptr);
}
return new_block;
}
本文档描述了实现自定义内存分配器的过程,采用分离存储的策略,维护16个大小类别的空闲链表。当需要扩展堆时,会检查新块是否能与前一块合并以减少碎片。malloc通过调整请求大小并对其对齐来工作,free则从空闲链表中删除相应块。realloc负责重新分配内存,如果剩余空间足够,则将其分割成新的空闲块,优化内存利用率。

7660

被折叠的 条评论
为什么被折叠?



