目录
一、题目回顾
本实验的主要任务,是实现一个堆上的动态内存分配器,达到最高性能。 原理提示:可以把“堆”看成是一个线性的数组(地址0、1、2、3、4、5等)。malloc的目的,是从堆的空闲空间中,分配指定大小的空间,并返回一个指向该分配空间的指针。free的目的,是释放某个指针指向的空间。请参考教材9.9。 你需要修改mm.c,替换其中的malloc、free、realloc函数,并对trace进行运行评分。
【拓展提高】建议考虑实现教材上的“伙伴系统”
二、源代码
附到资源里面啦
三、思路
总共四个主要函数,init、 malloc、 free、 realloc 根据课本和题目指示,利用空闲链表法隐式管理空闲的堆,注意利用好mem_sbrk()函数(⚠️:mem_sbrk()函数是一个用于动态内存分配的系统调用,它的作用是扩展或缩小进程的数据段的大小。具体来说,mem_sbrk()函数接收一个整数参数,表示需要增加或减少的内存大小,然后相应地调整进程的数据段边界。如果成功,mem_sbrk()会返回原来的数据段边界;如果失败,通常会返回-1,并且设置errno为ENOMEM,表示内存不足。)。根据老师提供的一些宏定义去补充新的宏定义,然后补充一些辅助函数(基础操作:比如拓展堆,这个很重要,堆的初始化和堆空间无法分配时需要调用;添加空闲节点;删除空闲节点;合并空闲节点;放置函数)其实大多数都是链表的操作。最后根据这些宏定义和辅助函数实现主要函数。
四、理解分析与实现
1、关于一些宏定义
懒得码字了,自己理解一下啵
/* 单字 (4) 还是双字 (8) 边界对齐 */
#define ALIGNMENT 8
/* 舍入到最近的ALIGNMENT边界上 */
#define ALIGN(size) (((size) + (ALIGNMENT-1)) & ~0x7)
#define SIZE_T_SIZE (ALIGN(sizeof(size_t)))
// My additional Macros
#define WSIZE 4 // word and header/footer size (bytes)
#define DSIZE 8 // double word size (bytes)
#define INITCHUNKSIZE 48 //针对最后一个数据做出的优化
#define CHUNKSIZE (1<<12) //mm_malloc、realloc 每次向堆中申请内存的最低限制 !!
#define LISTLIMIT 20
#define MAX(x, y) ((x) > (y) ? (x) : (y))
#define MIN(x, y) ((x) < (y) ? (x) : (y))
/* Pack a size and allocated bit into a word */
#define PACK(size, alloc) ((size) | (alloc))
/* Read and write a word at address p */
#define GET(p) (*(unsigned int *)(p))
#define PUT(p, val) (*(unsigned int *)(p)=(val))
/* Read the size and allocated fields from address p */
#define GET_SIZE(p) (GET(p) & ~0x7)
#define GET_ALLOC(p) (GET(p) & 0x1)
/* Given block ptr bp, compute address of its header and footer */
#define HDRP(bp) ((char *)(bp)- WSIZE)
#define FTRP(bp) ((char *)(bp)+ GET_SIZE(HDRP(bp))- DSIZE)
/* Given block ptr bp, compute address of next and previous blocks */
#define NEXT_BLKP(bp) ((char *)(bp)+ GET_SIZE(((char *)(bp)- WSIZE)))
#define PREV_BLKP(bp) ((char *)(bp)- GET_SIZE(((char *)(bp)- DSIZE)))
#define SET_PTR(p, ptr) (*(unsigned int *)(p) = (unsigned int)(ptr)) // p 指针指向的[值]为 ptr 指针
// Read the size and allocation bit from address p
#define GET_SIZE(p) (GET(p) & ~0x7)
#define GET_ALLOC(p) (GET(p) & 0x1)
#define GET_TAG(p) (GET(p) & 0x2)
#define SET_RATAG(p) (GET(p) |= 0x2)
#define REMOVE_RATAG(p) (GET(p) &= ~0x2)
// Address of free block's predecessor and successor entries
#define PRED_PTR(ptr) ((char *)(ptr))
#define SUCC_PTR(ptr) ((char *)(ptr) + WSIZE)
// Address of free block's predecessor and successor on the segregated list
#define PRED(ptr) (*(char **)(ptr))
#define SUCC(ptr) (*(char **)(SUCC_PTR(ptr)))
2、辅助函数的实现
0️⃣辅助函数概览与声明
/* 全局变量 分离空闲链表 */
void* segregated_free_lists[LISTLIMIT];
static void print_block(int request_id, int payload);
static void* extend_heap(size_t size);
static void* coalesce(void* ptr);
static void* place(void* ptr, size_t asize);
static void insert_node(void* ptr, size_t size);
static void delete_node(void* ptr);
1️⃣拓展堆大小的函数
/* 拓展堆的大小size */
static void* extend_heap(size_t size)
{
void* ptr;
size_t asize; // Adjusted size
asize = ALIGN(size); // 8字节对齐
if ((ptr = mem_sbrk(asize)) == (void*)-1)
return NULL;
// 设置 header 和 footer
PUT(HDRP(ptr), PACK(asize, 0));
PUT(FTRP(ptr), PACK(asize, 0));
PUT(HDRP(NEXT_BLKP(ptr)), PACK(0, 1)); //设置 终点block
insert_node(ptr, asize);
void* ans = coalesce(ptr);
return ans;
}
首先,mem_sbrk 是模拟 sbrk 系统调用的自定义函数,用于增加堆的大小。(void*)-1 是一个特殊的返回值,通常表示错误。如果 mem_sbrk 失败,ptr 将被设置为 (void*)-1,函数将返回 NULL。
然后又利用宏定义里面的put设置header和footer,并设置结束block表明堆结束
插入、合并
注:extend_heap 在两种情况下被调用:1)当堆被初始化时;2)当mm_malloc 不能找到一个合适的匹配块时
2️⃣添加空闲节点
/* 添加空闲节点 */
static void insert_node(void* ptr, size_t size) {
int list = 0;
void* search_ptr = ptr;
void* insert_ptr = NULL;
// 选择适合大小的分离空闲链表
while ((list < LISTLIMIT - 1) && (size > 1)) {
size >>= 1;
list++;
}
// 按照链表中block size的递增序 查找合适的插入位置
search_ptr = segregated_free_lists[list];
while ((search_ptr != NULL) && (size > GET_SIZE(HDRP(search_ptr)))) {
insert_ptr = search_ptr;
search_ptr = PRED(search_ptr);
}
// 将 ptr 指向的空闲 block 在 search_ptr(前驱)和insert_ptr(后继)之间插入
if (search_ptr != NULL) { //前驱不为NULL
if (insert_ptr != NULL) { //后继不为NULL
SET_PTR(PRED_PTR(ptr), search_ptr); //设置前驱 [链表基本操作]
SET_PTR(SUCC_PTR(search_ptr), ptr); //设置前驱的后继
SET_PTR(SUCC_PTR(ptr), insert_ptr); //设置后继
SET_PTR(PRED_PTR(insert_ptr), ptr); //设置后继的前驱
}
else { //后继为NULl
SET_PTR(PRED_PTR(ptr), search_ptr);
SET_PTR(SUCC_PTR(search_ptr), ptr);
SET_PTR(SUCC_PTR(ptr), NULL);
segregated_free_lists[list] = ptr; //此时需要改变 链表的头指针
}
}
else { //前驱为NULL
if (insert_ptr != NULL) { //后继不为NULL
SET_PTR(PRED_PTR(ptr), NULL);
SET_PTR(SUCC_PTR(ptr), insert_ptr);
SET_PTR(PRED_PTR(insert_ptr), ptr);、
}
else { //后继为NULL
SET_PTR(PRED_PTR(ptr), NULL);
SET_PTR(SUCC_PTR(ptr), NULL);
segregated_free_lists[list] = ptr; //此时需要改变 链表的头指针
}
}
return;
}
空闲链表是按照2的几次方排列的,最多2的LISTLIMIT大小,首先遍历找到大致空间。
进行链表的插入!!!注意修改前驱和后继
3️⃣删除空闲节点
/* 删除空闲节点 */
static void delete_node(void* ptr) {
int list = 0;
size_t size = GET_SIZE(HDRP(ptr));
// 选择合适大小的分离空闲链表
while ((list < LISTLIMIT - 1) && (size > 1)) {
size >>= 1;
list++;
}
//链表操作
if (PRED(ptr) != NULL) {
if (SUCC(ptr) != NULL) {
SET_PTR(SUCC_PTR(PRED(ptr)), SUCC(ptr));
SET_PTR(PRED_PTR(SUCC(ptr)), PRED(ptr));
}
else {
SET_PTR(SUCC_PTR(PRED(ptr)), NULL);
segregated_free_lists[list] = PRED(ptr); //没有后继 需要改变头指针为前驱
}
}
else {
if (SUCC(ptr) != NULL) {
SET_PTR(PRED_PTR(SUCC(ptr)), NULL);
}
else {
segregated_free_lists[list] = NULL; //前驱后继都是NULL 改变头指针为NULL
}
}
return;
}
已经找到合适节点,把它分配出去,需要把它从空闲链表中删除
4️⃣合并block
/* 合并 block */
static void* coalesce(void* ptr)
{
size_t prev_alloc = GET_ALLOC(HDRP(PREV_BLKP(ptr)));
size_t next_alloc = GET_ALLOC(HDRP(NEXT_BLKP(ptr)));
size_t size = GET_SIZE(HDRP(ptr));
if (prev_alloc && next_alloc) { // Case 1 前后都已分配
return ptr;
}
else if (prev_alloc && !next_alloc) { // Case 2 前分配 后未分配 合并后继
delete_node(ptr); // 在链表中删除原来两个node,将两个空闲块合并
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_alloc && next_alloc) { // Case 3 前未分配 后已分配 合并前驱
delete_node(ptr); // 在链表中删除原来两个node,将两个空闲块合并
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); // 在链表中删除原来三个node,将三个空闲块合并
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); //将合并之后的新block 作为node插入 分离空闲链表
return ptr;
}
合并的实现:
就是这几种情况的实现,找到相邻的空闲块,删除,合并(这里的合并直接用PUT宏定义实现就可以),插入
5️⃣在ptr指向的block中 放置asize大小的数据
static void* place(void* ptr, size_t asize)
{
// 计算ptr指向的内存块的大小
size_t ptr_size = GET_SIZE(HDRP(ptr));
// 计算剩余空间大小
size_t remainder = ptr_size - asize;
// 从链表中删除当前节点
delete_node(ptr);
// 如果剩余大小不超过16B,则最多只能放下free block的附加信息,则不切分
if (remainder <= DSIZE * 2) {
// 标记内存块为已占用,大小为ptr_size
PUT(HDRP(ptr), PACK(ptr_size, 1));
PUT(FTRP(ptr), PACK(ptr_size, 1));
}
// 如果请求的空间大小大于等于110字节
else if (asize >= 110) {
// 将内存块的后半部分分配给新请求
PUT(HDRP(ptr), PACK(remainder, 0));
PUT(FTRP(ptr), PACK(remainder, 0));
PUT(HDRP(NEXT_BLKP(ptr)), PACK(asize, 1));
PUT(FTRP(NEXT_BLKP(ptr)), PACK(asize, 1));
// 将剩余部分插入链表
insert_node(ptr, remainder);
// 返回新分配的内存块的起始地址
return NEXT_BLKP(ptr);
}
// 如果请求的空间大小小于110字节
else {
// 将内存块的前半部分分配给新请求
PUT(HDRP(ptr), PACK(asize, 1));
PUT(FTRP(ptr), PACK(asize, 1));
PUT(HDRP(NEXT_BLKP(ptr)), PACK(remainder, 0));
PUT(FTRP(NEXT_BLKP(ptr)), PACK(remainder, 0));
// 将剩余部分插入链表
insert_node(NEXT_BLKP(ptr), remainder);
}
// 返回原内存块的起始地址
return ptr;
}
具体含义在注释里都写了,直接看注释就好
3、主要函数实现
1️⃣初始化
/*
* mm_init - 初始化malloc系统,此函数,在整个运行期间,只被调用1次,用于建立初始化环境
*/
int mm_init(void)
{
int listnumber;
char* heap;
/* 初始化分离空闲链表 */
for (listnumber = 0; listnumber < LISTLIMIT; listnumber++)
{
segregated_free_lists[listnumber] = NULL;
}
/* 初始化堆 */
if ((long)(heap = mem_sbrk(4 * WSIZE)) == -1)
return -1;
/* 这里的结构参见本文上面的“堆的起始和结束结构” */
PUT(heap, 0);
PUT(heap + (1 * WSIZE), PACK(DSIZE, 1));
PUT(heap + (2 * WSIZE), PACK(DSIZE, 1));
PUT(heap + (3 * WSIZE), PACK(0, 1));
/* 扩展堆 */
if (extend_heap(INITCHUNKSIZE) == NULL)
return -1;
return 0;
}
堆的初始化,这个没什么好说的。就是把空闲链表初始化一下,然后初始化堆、扩展堆
2️⃣malloc
/*
* mm_malloc - 通过增加brk指针,来分配一块内存。
* 总是分配一块内存,它的大小是ALIGNMENT的整数倍(对齐)。
*/
void* mm_malloc(size_t size)
{
if (size == 0)
return NULL;
/* 内存对齐 */
if (size <= DSIZE)
{
size = 2 * DSIZE;
}
else
{
size = ALIGN(size + DSIZE);
}
int listnumber = 0;
size_t searchsize = size;
void* ptr = NULL;
while (listnumber < LISTLIMIT)
{
/* 寻找对应链 */
if (((searchsize <= 1) && (segregated_free_lists[listnumber] != NULL)))
{
ptr = segregated_free_lists[listnumber];
/* 在该链寻找大小合适的free块 */
while ((ptr != NULL) && ((size > GET_SIZE(HDRP(ptr)))))
{
ptr = PRED(ptr);
}
/* 找到对应的free块 */
if (ptr != NULL)
break;
}
searchsize >>= 1;
listnumber++;
}
/* 没有找到合适的free块,扩展堆 */
if (ptr == NULL)
{
if ((ptr = extend_heap(MAX(size, CHUNKSIZE))) == NULL)
return NULL;
}
/* 在free块中allocate size大小的块 */
ptr = place(ptr, size);
return ptr;
}
首先进行内存对齐操作。如果size小于或等于DSIZE(可能是某个预设的最小分配单位),则将size调整为2 * DSIZE,否则调整size为ALIGN(size + DSIZE),其中ALIGN函数可能用于确保size是ALIGNMENT的整数倍。
然后在空闲链表中查找合适的块,在找到可能包含合适块的链表后,进一步在链表中查找
3️⃣free
/*
* mm_free - 释放一块内存。其实没干啥事....
*/
void mm_free(void* ptr)
{
size_t size = GET_SIZE(HDRP(ptr));
PUT(HDRP(ptr), PACK(size, 0));
PUT(FTRP(ptr), PACK(size, 0));
/* 插入分离空闲链表 */
insert_node(ptr, size);
/* 注意合并 */
coalesce(ptr);
}
4️⃣realloc
/*
* mm_realloc - 重新扩展一块已分配的内存。仅仅是使用mm_malloc和mm_free来实现,很蠢
*/
void *mm_realloc(void *ptr, size_t size)
{
void *new_block = ptr;
int remainder;
if (size == 0)
return NULL;
/* 内存对齐 */
if (size <= DSIZE)
{
size = 2 * DSIZE;
}
else
{
size = ALIGN(size + DSIZE);
}
/* 如果size小于原来块的大小,直接返回原来的块 */
if ((remainder = GET_SIZE(HDRP(ptr)) - size) >= 0)
{
return ptr;
}
/* 否则先检查地址连续下一个块是否为free块或者该块是堆的结束块,因为我们要尽可能利用相邻的free块,以此减小“external fragmentation” */
else if (!GET_ALLOC(HDRP(NEXT_BLKP(ptr))) || !GET_SIZE(HDRP(NEXT_BLKP(ptr))))
{
/* 即使加上后面连续地址上的free块空间也不够,需要扩展块 */
if ((remainder = GET_SIZE(HDRP(ptr)) + GET_SIZE(HDRP(NEXT_BLKP(ptr))) - size) < 0)
{
if (extend_heap(MAX(-remainder, CHUNKSIZE)) == NULL)
return NULL;
remainder += MAX(-remainder, CHUNKSIZE);
}
/* 删除刚刚利用的free块并设置新块的头尾 */
delete_node(NEXT_BLKP(ptr));
PUT(HDRP(ptr), PACK(size + remainder, 1));
PUT(FTRP(ptr), PACK(size + remainder, 1));
}
/* 没有可以利用的连续free块,而且size大于原来的块,这时只能申请新的不连续的free块、复制原块内容、释放原块 */
else
{
new_block = mm_malloc(size);
memcpy(new_block, ptr, GET_SIZE(HDRP(ptr)));
mm_free(ptr);
}
return new_block;
}
看注释