官网 http://csapp.cs.cmu.edu/3e/labs.html
Students implement simple logical, two’s complement, and floating point functions, but using a highly restricted subset of C. For example, they might be asked to compute the absolute value of a number using only bit-level operations and straightline code. This lab helps students understand the bit-level representations of C data types and the bit-level behavior of the operations on data.
参考 [读书笔记]CSAPP:MallocLab
CSAPP: Malloc lab
实验要求 在mm.c里完善一个自己的动态分配器。
int mm_init(void);
: 调用其他几个函数前,调用mm_init
进行必要的初始化。初始化发生错误时,应返回-1,正常则返回0。
void *mm_malloc(size_t size);
: 返回一个指针,指向一个至少size
字节的已分配块的有效载荷。整个分配块应位于堆区域内,且不与任何其他分配块重叠。该函数应和libc
中的malloc
一样,总是返回 8 字节对齐的指针。
void mm_free(void *ptr);
: 释放指针ptr
指向的块,不返回任何值。只有指针ptr
是由mm_malloc
或者mm_realloc
先前返回的时候,该函数会保证正常工作。
void *mm_realloc(void *ptr, size_t size);
: 返回值同mm_malloc
,但是有以下限制:
如果ptr
为空,等同于调用mm_malloc(size)
如果size=0
,等同于调用mm free(ptr)
否则,指针ptr
必须是由mm_malloc
或者mm_realloc
先前返回的。该函数修改该块的大小为size
,然后返回新块的地址。新块的地址是否和旧块相同,取决于旧块的内部碎片情况和新块所请求的大小。
如果是”压缩“,旧块的部分内容扔保留。如果是”扩展“,旧块的内容保留,扩展区域为非初始化的。
辅助函数 可以使用memlib.c
里的函数。
void *mem sbrk(int incr)
: Expands the heap by incr bytes, where incr is a positive non-zero integer and returns a generic pointer to the first byte of the newly allocated heap area. The semantics are identical to the Unix sbrk function, except that mem sbrk accepts only a positive non-zero integer argument.
void *mem heap lo(void)
: Returns a generic pointer to the first byte in the heap.
void *mem heap hi(void)
: Returns a generic pointer to the last byte in the heap.
size t mem heapsize(void)
: Returns the current size of the heap in bytes.
size t mem pagesize(void)
: Returns the system’s page size in bytes (4K on Linux systems).
评分 调用./mdriver -f short1-bal.rep -V
来查看单个文件的测试结果。该课程的其他测试数据可以从这里 下载,得到一个trace
文件夹,然后调用./mdriver -t ./trace -V
来查看测试结果。
思路
分离空闲链表:segregated free list中按大小类分类,并且将该list表放在heap的头部,通过序言块将它与数据隔离。在每一个大小类中,空闲块按照size由小到大排序。
立即合并:free()后对空闲块立即合并。
最佳适配:因为同一大小类中空闲块由小到大排序,所以查找是第一个合适的就是最适配的。
改进的realloc:对于请求“扩展”这种情况,运用类似coalesce中的方法,先去检查前后是否有空闲块,并是否满足前后空闲块和当前已分配的空闲块size相加大于newsize,如果是则合并,不需要再重新请求空闲块。如果不行,则需要重新mm_malloc一块新的空间。
块的数据结构
空闲块使用两个字的空间分别记录了前驱和后继的空闲块,这样,所有空闲块就可以显式地链接起来,就能轻松地直接遍历所有的空闲块了。
宏和全局变量 定义如下宏和全局变量:
#define ALIGNMENT 8 #define ALIGN(size) (((size) + (ALIGNMENT-1)) & ~0x7) #define SIZE_T_SIZE (ALIGN(sizeof(size_t))) #define WSIZE 4 #define DSIZE 8 #define CHUNKSIZE (1<<12) #define MAX(x, y) ((x)>(y)?(x):(y)) #define PACK(size, alloc) ((size) | (alloc)) #define GET(p) (*(unsigned int *)(p)) #define PUT(p, val) (*(unsigned int *)(p) = (val)) #define GET_SIZE(p) (GET(p) & ~0x7) #define GET_ALLOC(p) (GET(p) & 0x1) #define HDRP(bp) ((char *)(bp)-WSIZE) #define FTRP(bp) ((char *)(bp)+GET_SIZE(HDRP(bp))-DSIZE) #define PREV_LINKNODE_RP(bp) ((char*)(bp)) #define NEXT_LINKNODE_RP(bp) ((char*)(bp)+WSIZE) #define NEXT_BLKP(bp) ((char *)(bp)+GET_SIZE(((char *)(bp)-WSIZE))) #define PREV_BLKP(bp) ((char *)(bp)-GET_SIZE(((char *)(bp)-DSIZE))) static char *heap_listp = NULL ;static char *block_list_start = NULL ;
PREV_LINKNODE_RP
就是前文所提到的空闲块中的前驱指针,NEXT_LINKNODE_RP
同理。bp
指向的是块header下一个字的位置。
heap_listp
指向序言块的有效载荷,有如下结构:
block_list_start
则是指向第一个空闲链表的位置。
空闲链表 一共有10个空闲链表,不需要记录小于16Bytes的块,因为空闲块最小为16Bytes。空闲链表内的空闲块大小范围分别如下。
{0-32},{33-64},{65-128},{129-256},{257-512},{513-1024},{1025-2048},{2049-3072},{3073-4096},{4097-inf}
ins_block
是将一个空闲块插入对应的空闲链表中,注意要保证各块大小非递减,便于进行最佳适配。
void ins_block (char *p) { char *root = find_list_root(GET_SIZE(HDRP(p))); char *prevp = root; char *nextp = GET(root); while (nextp != NULL ) { if (GET_SIZE(HDRP(nextp)) >= GET_SIZE(HDRP(p))) break ; prevp = nextp; nextp = GET(NEXT_LINKNODE_RP(nextp)); } if (prevp == root) { PUT(root, p); PUT(NEXT_LINKNODE_RP(p), nextp); PUT(PREV_LINKNODE_RP(p), NULL ); if (nextp != NULL ) PUT(PREV_LINKNODE_RP(nextp), p); } else { PUT(NEXT_LINKNODE_RP(prevp), p); PUT(PREV_LINKNODE_RP(p), prevp); PUT(NEXT_LINKNODE_RP(p), nextp); if (nextp != NULL ) PUT(PREV_LINKNODE_RP(nextp), p); } }
与其相对应的,del_block
把某个块从相应链表中删除。
void del_block (char *p) { char *root = find_list_root(GET_SIZE(HDRP(p))); char *prevp = GET(PREV_LINKNODE_RP(p)); char *nextp = GET(NEXT_LINKNODE_RP(p)); if (prevp == NULL && nextp == NULL ) { PUT(root, 0 ); } else if (prevp != NULL && nextp == NULL ) { PUT(NEXT_LINKNODE_RP(prevp), nextp); } else if (prevp == NULL && nextp != NULL ) { PUT(PREV_LINKNODE_RP(nextp), 0 ); PUT(root, nextp); } else if (prevp != NULL && nextp != NULL ) { PUT(PREV_LINKNODE_RP(nextp), prevp); PUT(NEXT_LINKNODE_RP(prevp), nextp); } PUT(NEXT_LINKNODE_RP(p), NULL ); PUT(PREV_LINKNODE_RP(p), NULL ); }
find_list_root
是计算某个大小为size的空闲块应该对应于哪个空闲链表,返回对应空闲链表的首地址。
char *find_list_root (size_t size) { int i; if (size <= 32 ) i = 0 ; else if (size <= 64 ) i = 1 ; else if (size <= 128 ) i = 2 ; else if (size <= 256 ) i = 3 ; else if (size <= 512 ) i = 4 ; else if (size <= 1024 ) i = 5 ; else if (size <= 2048 ) i = 6 ; else if (size <= 3072 ) i = 7 ; else if (size <= 4096 ) i = 8 ; else i = 9 ; return block_list_start + i * WSIZE; }
mm_init int mm_init (void ) { int ind; if ((heap_listp = mem_sbrk(14 * WSIZE)) == (void *) -1 ) return -1 ; for (ind = 0 ; ind < 10 ; ind++) PUT(heap_listp + ind * WSIZE, 0 ); PUT(heap_listp + 10 * WSIZE, 0 ); PUT(heap_listp + 11 * WSIZE, PACK(DSIZE, 1 )); PUT(heap_listp + 12 * WSIZE, PACK(DSIZE, 1 )); PUT(heap_listp + 13 * WSIZE, PACK(0 , 1 )); block_list_start = heap_listp; heap_listp += 12 * WSIZE; return ((extend_heap(CHUNKSIZE / WSIZE)) == NULL ) ? -1 : 0 ; }
和CSAPP书上的例程没有太大区别,主要是加入了block list,放在序言块之前。注意申请的堆大小要保持双字对齐,使用padding块同时还可以将序言块和block list部分隔开。heap_listp仍是指向序言块的有效载荷。涉及到的extend_heap
和coalesce
函数如下。主要注意链表和NEXT_LINKNODE_RP
等字段的维护。
static void *extend_heap (size_t words) { char *bp; size_t size; size = (words % 2 ) ? (words + 1 ) * WSIZE : words * WSIZE; if ((bp = mem_sbrk(size)) == (void *) -1 ) return NULL ; PUT(HDRP(bp), PACK(size, 0 )); PUT(FTRP(bp), PACK(size, 0 )); PUT(NEXT_LINKNODE_RP(bp), NULL ); PUT(PREV_LINKNODE_RP(bp), NULL ); PUT(HDRP(NEXT_BLKP(bp)), PACK(0 , 1 )); return coalesce(bp); }
static void *coalesce (void *bp) { size_t prev_alloc = GET_ALLOC(FTRP(PREV_BLKP(bp))); size_t next_alloc = GET_ALLOC(HDRP(NEXT_BLKP(bp))); size_t size = GET_SIZE(HDRP(bp)); if (prev_alloc && !next_alloc) { size += GET_SIZE(HDRP(NEXT_BLKP(bp))); del_block(NEXT_BLKP(bp)); PUT(HDRP(bp), PACK(size, 0 )); PUT(FTRP(bp), PACK(size, 0 )); } else if (!prev_alloc && next_alloc) { size += GET_SIZE(HDRP(PREV_BLKP(bp))); del_block(PREV_BLKP(bp)); PUT(FTRP(bp), PACK(size, 0 )); PUT(HDRP(PREV_BLKP(bp)), PACK(size, 0 )); bp = PREV_BLKP(bp); } else if (!prev_alloc) { size += GET_SIZE(FTRP(NEXT_BLKP(bp))) + GET_SIZE(HDRP(PREV_BLKP(bp))); del_block(PREV_BLKP(bp)); del_block(NEXT_BLKP(bp)); PUT(FTRP(NEXT_BLKP(bp)), PACK(size, 0 )); PUT(HDRP(PREV_BLKP(bp)), PACK(size, 0 )); bp = PREV_BLKP(bp); } ins_block(bp); return bp; }
mm_malloc void *mm_malloc (size_t size) { size_t asize; size_t extendsize; char *bp; if (size == 0 ) return NULL ; asize = size <= DSIZE ? 2 * DSIZE : DSIZE * ((size + (DSIZE) + (DSIZE - 1 )) / DSIZE); if ((bp = find_fit(asize)) == NULL ) { extendsize = MAX(asize, CHUNKSIZE); if ((bp = extend_heap(extendsize / WSIZE)) == NULL ) { return NULL ; } } place(bp, asize); return bp; }
static void place (void *bp, size_t asize) { size_t csize = GET_SIZE(HDRP(bp)); del_block(bp); if ((csize - asize) >= 2 * DSIZE) { PUT(HDRP(bp), PACK(asize, 1 )); PUT(FTRP(bp), PACK(asize, 1 )); bp = NEXT_BLKP(bp); PUT(HDRP(bp), PACK(csize - asize, 0 )); PUT(FTRP(bp), PACK(csize - asize, 0 )); PUT(NEXT_LINKNODE_RP(bp), 0 ); PUT(PREV_LINKNODE_RP(bp), 0 ); coalesce(bp); } else { PUT(HDRP(bp), PACK(csize, 1 )); PUT(FTRP(bp), PACK(csize, 1 )); } }
mm_free void mm_free (void *ptr) { if (ptr == 0 ) return ; size_t size = GET_SIZE(HDRP(ptr)); PUT(HDRP(ptr), PACK(size, 0 )); PUT(FTRP(ptr), PACK(size, 0 )); PUT(NEXT_LINKNODE_RP(ptr), NULL ); PUT(PREV_LINKNODE_RP(ptr), NULL ); coalesce(ptr); }
也是要注意NEXT_LINKNODE_RP
等字段的维护。采用的是立即合并策略,释放后就立即尝试与前后空闲块进行合并。
重点:mm_realloc void *mm_realloc (void *ptr, size_t size) { size_t oldsize = GET_SIZE(HDRP(ptr)); void *newptr; size_t asize; if (size == 0 ) { mm_free(ptr); return 0 ; } if (ptr == NULL ) return mm_malloc(size); asize = size <= DSIZE ? 2 * DSIZE : DSIZE * ((size + (DSIZE) + (DSIZE - 1 )) / DSIZE); if (oldsize == asize) return ptr; if (oldsize < asize) { int isnextFree; char *bp = realloc_coalesce(ptr, asize, &isnextFree); if (isnextFree == 1 ) { realloc_place(bp, asize); return bp; } else if (isnextFree == 0 && bp != ptr) { memcpy (bp, ptr, size); realloc_place(bp, asize); return bp; } else { newptr = mm_malloc(size); memcpy (newptr, ptr, size); mm_free(ptr); return newptr; } } else { realloc_place(ptr, asize); return ptr; } }
根据实验要求,在需要重新分配一块更大的内存块时,首先判断前后是否为空闲块,可以就地分配。相应的place
和coalesce
函数也要作出修改。
static void realloc_place (void *bp, size_t asize) { size_t csize = GET_SIZE(HDRP(bp)); if ((csize - asize) >= 2 * DSIZE) { PUT(HDRP(bp), PACK(asize, 1 )); PUT(FTRP(bp), PACK(asize, 1 )); bp = NEXT_BLKP(bp); PUT(HDRP(bp), PACK(csize - asize, 0 )); PUT(FTRP(bp), PACK(csize - asize, 0 )); PUT(NEXT_LINKNODE_RP(bp), 0 ); PUT(PREV_LINKNODE_RP(bp), 0 ); coalesce(bp); } else { PUT(HDRP(bp), PACK(csize, 1 )); PUT(FTRP(bp), PACK(csize, 1 )); } }
static void *realloc_coalesce (void *bp, size_t newSize, int *isNextFree) { size_t prev_alloc = GET_ALLOC(FTRP(PREV_BLKP(bp))); size_t next_alloc = GET_ALLOC(HDRP(NEXT_BLKP(bp))); size_t size = GET_SIZE(HDRP(bp)); *isNextFree = 0 ; if (prev_alloc && !next_alloc) { size += GET_SIZE(HDRP(NEXT_BLKP(bp))); if (size >= newSize) { del_block(NEXT_BLKP(bp)); PUT(HDRP(bp), PACK(size, 1 )); PUT(FTRP(bp), PACK(size, 1 )); *isNextFree = 1 ; return bp; } } else if (!prev_alloc && next_alloc) { size += GET_SIZE(HDRP(PREV_BLKP(bp))); if (size >= newSize) { del_block(PREV_BLKP(bp)); PUT(FTRP(bp), PACK(size, 1 )); PUT(HDRP(PREV_BLKP(bp)), PACK(size, 1 )); bp = PREV_BLKP(bp); return bp; } } else if (!prev_alloc) { size += GET_SIZE(FTRP(NEXT_BLKP(bp))) + GET_SIZE(HDRP(PREV_BLKP(bp))); if (size >= newSize) { del_block(PREV_BLKP(bp)); del_block(NEXT_BLKP(bp)); PUT(FTRP(NEXT_BLKP(bp)), PACK(size, 1 )); PUT(HDRP(PREV_BLKP(bp)), PACK(size, 1 )); bp = PREV_BLKP(bp); } } return bp; }
realloc_place
函数省去了对空闲块的删除步骤,因为当前块在place之前并不是空闲块。realloc_coalesce
则是接口发生了变化,记录了前后内存块的分配情况。如果没有前后空间可以合并或合并后仍然不能满足需求,则重新找空白块进行place。
测试结果