基于C语言实现的MallocLab实验

资源下载地址:https://download.csdn.net/download/sheziqiong/85895822
资源下载地址:https://download.csdn.net/download/sheziqiong/85895822

MallocLab 实验报告

一、实验目标

深入理解堆内存管理机制,实现四个函数 mm_init(),mm_malloc(size_t size) mm_free(void* bp)mm_realloc(void* bp, size_t newsize),分别用于初始化堆,按照用户的要求分配指定大小的空间,将已分配的空间释放称为未分配空间,为已分配的空间重新分配一段不同大小的内存,并将原有的内容进

行拷贝操作。通过这四个函数的实现来模拟堆内存管理机制,尽可能地提高吞吐量和峰值空间利用率

(peek utility percentage)

二、设计思路

本质是对一系列的 malloc,free 请求队列的相应,同时满足一些特定的限制条件,比如说:不可以控制用户请求空间的大小;用户的请求必须立即响应,不可以使用 buffer 的形式,这一点是和 shedlab 本质的区别;只可以使用空闲内存,不能干涉已经分配给用户的内存;为用户分配的内存必须满足一系列的 alignment 要求。无论使用哪一种分配手段,都需要一些通用的函数,整体的框架也不会有特别大的改变。主要使用的函数有:

mm_init()初始化先分配一块内存,并设置好整个 heap 的开始块,序言块,内存块,结尾块。

void extend_heap(size_t size)当现有的已申请内存不够用的时候,扩展内存。

void* coalesce(void* bp,size_t size)bp 是一个空闲块的指针,这个函数的功能是检查一个空闲块的左右,并将左右可能的空闲块进行合并成为一个大的空闲块。

void* find_fit(size_t asize)从所有的空闲块之中选取 适合当前请求的,返回指针。

void place(void* bp,size_t asize)将 asize 大小的内容放置到 bp 所指向的空闲块,对于剩余的部分,如果超过了一定大小,会被分割成另外一个空闲块;如果比较小,就直接把整个 bp 所在的空闲块全部都分给请求内容,直接作为 internal fragment。

由于无法使用全局变量和结构体定义,因此需要广泛地使用宏定义:各种不同的思路,其实只是在不断地调整以上几个函数的实现方式而已,整体大的框架没有改变。

2.1 解决方案 1:implicit list + boundary tags

解决方案整体参考了教材 CSAPP 上的有关代码和框架。

动态内存分配器 简单的实现方案就是采取隐式链表的形式,同时对于每一个块(无论是未分配块还是已分配块),不仅含有包含 header,同时在尾部块的 后 4 个字节还设置了一个 footer,head 和 footer

具有完全相同的内容,都是由两部分组成,size(前三个字节)和 alloc( 后一个字节的 后一个 bit)。

将 coalesce 分为四种可能的情况:

  • 左右两边都已经被分配,此时无法合并,可以直接返回上一块已经被分配,下一块空闲上一块空闲,下一块已经被分配上下两块都是空闲状态
  • 尤其需要注意的一点是,在更改相应的 header 和 footer 时,先后次序很重要。从相关的宏定义可以看到,
  • footer 和 header 的确定并不是独立的,后者的位置确定依赖于前者。

place()放置函数分为两种情况,如果剩余空间比较小,小于 16 个字节的时候,就直接将整个块分配给用户,不再做进一步的切割,形成 internal fragment;如果剩余空间比较大,就再做一次切割,把剩下的部分加入到隐式空闲链表之中。

find_fit 采用 first fit 方法,从头开始扫描,遇到尺寸超过请求的空闲块,直接放置。

realloc 采取原始的版本。

实验效果如下图所示:

在这里插入图片描述

可以看到,虽然前 7 个 trace 的测试比较理想,但是到了 trace7 和 trace8 的 utility 明显下降,trace10 和 trace9 的 utility 下降更加严重, 终只能拿到 53 分

2.2 解决方案 2:best fit+realloc 改进

我们从解决方案一的整体框架开始进行优化,首先一种优化办法是采用 best fit 来替代原有的 find fit 策略。虽然这样做会导致 place 的时间复杂度成为标准的 O(N)级别,但是作为 trade-off 可以选择尺寸合适的的块,减少 external fragment best fit 代码如下

同时将 realloc 进行改进。按照 mm_realloc 的要求,分为以下几种情况:

指针为空,这种情况下相当于直接进行 malloc 操作新请求的空间大小为 0,这种情况归结为 free 操作

将新请求的空间进行 alignment 对齐操作。

新请求的空间比原有的空间小,不做任何处理新请求的空间比原有的空间更大,首先将原有的块进行一次合并操作,即 coalesce,如果合并之后的尺寸超过了已请求的尺寸,就只需要复制内容就好

如果进行合并之后的尺寸依然比请求尺寸要小,就直接 malloc,将内容搬移过来之后,再次 free

优化之后得分效果如下:

在这里插入图片描述

可以看到,虽然 trace9 和 trace10 的 utility 有了非常大的改进,但是整体上效果变得更差了, 因为 trace0,trace1,trace2 和 trace3 的 utility 变得很差。

2.3 解决方案 3 segment list

为了进一步提高效率,我们把所有的空闲块用显式的链表串联起来,并根据空闲块的大小,分别分配到不同的链表之中。为此,所有的块之间,有两种关系,一种关系是物理地址上的相邻,另一种关系是在同一条链表上的相邻。为此,需要添加两个宏定义

由此我们可以看到,对于分配块,只有 header 和 footer,其余的部分全部都是 payload。而对于未分配块,则分为 5 个部分。header,footer,next pointer previous pointer,payload 五个部分。

在这里插入图片描述

  • 要做到 segment list,就需要对大小不同的块进行分类,放到不同的链表之中。由于不允许使用全局变
  • 量,所以我把所有的链表头都放在了 heap 的头部。并按照 64byte,128byte,256byte,512byte,
  • byte,2048byte,4096byte 进行分级。

每次由于用户的 free 导致出现了新的空闲块之后,需要将空闲块加入到列表之中。首先,我们需要根据这个块的大小来确定链表头的位置,这一部分使用一个函数来实现,这个函数叫做 seekroot()

在每一次 coalesce 的过程中,需要做两件事,将前后的空闲块从链表之中删除;将新合并之后的空闲块插入应有的位置上去。我按照尺寸的有序性插入,也就是在同一个 segment 的范围之内,大块在后,小块在前。一个新的空闲块分为四种可能的情况:

空插,链表头是空的,新的空闲块成为唯一的元素头插,新块插入到紧接在链表头之后

中插,插入在中间位置

尾插,新的空闲块 大,被插入在 后与之相对的,删除也是同样分为这几种情况。尤其需要注意的一点是,正常的 mm_free 所使用的 coalesce 函数和 mm_realloc 使用的 coalesce 不能混为一谈。我在实现 mm_realloc 的过程中,曾经想要直接借助实现 mm_free 阶段已经写好的 coalesce 函数,但是发现其实是行不通的。因为 mm_free 进行合并的时候,是把当前的块当成了空闲块,合并完之后是会直接加入到新的对应大小的 list 之中。但是 mm_realloc 需要在合并完之后依然当作分配块,不可以加入到链表中去的。否则的话,由于空闲块具有 NEXT 和 PREV 两个指针,所以如果对已经分配的块调用这两个并不存在的指针,会导致 segment fault。

下图是解决方案 3 的 终评价得分。

在这里插入图片描述

三、反思与总结

整个实验可以分步进行,realloc 的部分,在一开始可以直接使用 malloc 和 free 来完成,后期把 malloc 和 free 写的差不多了的时候可以从新开始写 realloc

realloc 之中使用的合并一定不可以直接利用在 free 的过程之中使用的合并,两者虽然本质是相同的,但是一个处理的是空闲块,一个处理的是已分配块,不可同日而语。

基本上能用宏定义的尽量都用宏定义,哪怕仅仅是一个地址解析,因为中间涉及到了两层指针,会有点混乱

基本上 bug 都是 segment fault,基本上所有的段错误都是由于使用宏定义的时候对分配块使用了只用空闲块才具有的结构。

资源下载地址:https://download.csdn.net/download/sheziqiong/85895822
资源下载地址:https://download.csdn.net/download/sheziqiong/85895822

猜你喜欢

转载自blog.csdn.net/sheziqiong/article/details/125598048