MallocLab
開始日期:22.2.8
操作系統:linux
調試工具:gdb
Link:CS:APP3e
Pre-knowledge
-
完全閱讀CSAPP9.9章節,該章節有Implicit list + First fit + LIFO的全實現
-
本文主要參考2015 fall的ppt及rec,2022 spring今年開始更新,大家也以去看
-
任務要求是實現,
mm_free,mm_malloc,mm_realloc,期間還需要編寫輔助函數(helper function) -
本文主要參考blog:[讀書筆記]CSAPP:MallocLab,CSAPP:Lab5-Malloc Lab,針對-coalesce-trace-文件優化定制,CS:APP3e 深入理解計算機系統_3e MallocLab實驗
-
traces文件是缺失的,請自取link: traces
- 第一次學git往github送文件,折騰了好一會兒
-
主要知識
-
塊結構(注意
bp指向payload,而p是指向Header)|Header|payload|padding|Footer| ^p ^bp教材:
P 592:malloc返回一個指針,它指向有效載荷的開始處
p 599:塊指針bp指向第一個有效載荷字節 -
內存模型(注意
head_listp指向Prologue Footer)|Padding|Prologue Head|Prologue Foot|Normal Block|...|Epilogue Head| ^head_listp -
鏈表結構(Keeping Track of Free Blocks)
- Implicit list(隱式鏈)
- Root -> block1 -> block2 -> block3 -> ...
- Explicit list(顯示鏈)
- Root -> free block 1 -> free block 2 -> free block 3 -> ...
- Segregated lists(離散鏈表)
- Small-malloc root -> free small block 1 -> free small block 2 -> ...
- Medium-malloc root -> free medium block 1 -> ...
- Large-malloc root -> free block block 1 -> ...
- Implicit list(隱式鏈)
-
合並策略(Coalescing policy)
分為立即合並(Immediate coalescing)和延遲合並(Deferred coalescing)兩種,本文只采用立即合並。
延遲合並可以參考[讀書筆記]CSAPP:MallocLab -
插入策略(Insert policy)
當有一塊新的空閑塊該如何插入當前列表?-
LIFO,后進先出,直接插入開頭處
-
Address order(配合Segregated list)按照地址順序插入,而地址順序靠size classes(大小類)決定
Insert freed blocks so that free list blocks are always in address order:
addr(prev) < addr(curr) < addr(next)
-
-
放置(搜索)策略(Placement policy)
- First fit(配合LIFO)
每次都從頭找 - Next fit(配合LIFO)
第一次從頭開始找,找到之后馬上標記;第二次起,每次都從上一次的標記找,每次找到也要標記
換句話說,第二次是從第一次標記的地方開始找,第三次是從第二次標記的地方開始找,以此類推
需要注意的是,如果從標記到內存結尾這一遍沒找到,要從內存開始到標記找一遍,如果還是沒找到就算作找不到。 - Best fit(配合Address order)
按照size classes從小到大找,每次都能找到最適合的
- First fit(配合LIFO)
-
Content
Implicit list + First fit + LIFO
- 保持耐心,參照教材敲一遍代碼,能夠很好的理解
- 也可以用Code Examples中的mm.c
- 筆者結合了兩者,其實沒啥修改,代碼放在:Implicit list + First fit + LIFO
- 本文沒有編寫檢查函數
mm_realloc,教材並沒有提及,而且write up沒有寫得很清楚,它的主要功能是給一個已分配的塊重新划分大小,我是看菜鳥教程- 跑分

Implicit list + Next fit + LIFO
-
添加了全局變量
rover,相比上一個需要更改三處,代碼放在:Implicit list + Next fit + LIFOstatic char *rover; /* Next fit rover */ -
mm_init,添加初始化rover語句,將其指向heap_listp/* Initialize rover */ rover = heap_listp; -
find_fit,使用Next fitstatic void *find_fit(size_t asize) { /* Next fit search */ char *oldrover = rover; /* Search from the rover to the end of list */ for ( ; GET_SIZE(HDRP(rover)) > 0; rover = NEXT_BLKP(rover)) if (!GET_ALLOC(HDRP(rover)) && (asize <= GET_SIZE(HDRP(rover)))) return rover; /* Divide and Conquer */ /* search from start of list to old rover */ for (rover = heap_listp; rover < oldrover; rover = NEXT_BLKP(rover)) if (!GET_ALLOC(HDRP(rover)) && (asize <= GET_SIZE(HDRP(rover)))) return rover; return NULL; /* no fit found */ } -
coalesce,防止rover指向已經被合並的bp/* if the rover is pointing into the free block that we just coalesced */ /* we change it pointing to newest free block */ if ((rover > (char *)bp) && (rover < NEXT_BLKP(bp))) rover = bp;- 靈魂圖示

- 靈魂圖示
-
跑分

Explicit list + First fit + LIFO
-
使用了Explicit list,需要修改不少地方,代碼放在:Explicit list + First fit + LIFO
-
添加全局變量
static char* efree_listp = 0; /* Pointer to first free block */ -
mm_init,添加初始化efree_listp語句,它是空指針,在合並的時候要注意/* Initialize efree_listp */ efree_listp = NULL; -
添加四個Marco,分別用於計算某一個空閑塊前后繼的地址,修改某一個空閑塊前后繼的指向地址
/* Given block ptr bp, compute value of its succ and pred */ #define GET_SUCC(bp) (*(unsigned int *)(bp)) #define GET_PRED(bp) (*((unsigned int *)(bp) + 1)) /* Put a value at address of succ and pred */ #define PUT_SUCC(bp, val) (GET_SUCC(bp) = (unsigned int)(val)) #define PUT_PRED(bp, val) (GET_PRED(bp) = (unsigned int)(val))此時空閑塊的結構如下:
|Header|succ|pred|padding|Footer| ^p ^bp -
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)); PUT_PRED(ptr, NULL); PUT_SUCC(ptr, NULL); coalesce(ptr); } -
輔助函數
remove_s_p,輔助place和coalesce
它分為四種情況,通過修改前后繼,將空閑塊從空閑鏈中移除,放置或者合並都要使用到static void remove_s_p(void *bp) { void* pred = GET_PRED(bp); void* succ = GET_SUCC(bp); PUT_PRED(bp, NULL); PUT_SUCC(bp, NULL); if (pred == NULL && succ == NULL) /* NULL-> bp ->NULL */ { efree_listp = NULL; } else if (pred == NULL) /* NULL-> bp ->FREE_BLK */ { PUT_PRED(succ, NULL); /* as the first block */ efree_listp = succ; } else if (succ == NULL) /* FREE_BLK-> bp ->NULL */ { PUT_SUCC(pred, NULL); } else /* FREE_BLK-> bp ->FREE_BLK */ { PUT_SUCC(pred, succ); PUT_PRED(succ, pred); } } -
輔助函數
insert,使用LIFO策略,將空閑塊插入空閑鏈的最開始處,輔助coalesce- 如果efree_listp是空的,說明是第一次插入
- 如果不空,將空閑塊插入空閑鏈的最開始處
static void insert(void *new_first) { /* First insert */ if (efree_listp == NULL){ efree_listp = new_first; return; } /* efree_list -> insert -> succ */ void* old_first = efree_listp; PUT_SUCC(new_first, old_first); PUT_PRED(new_first, NULL); PUT_PRED(old_first, new_first); efree_listp = new_first; } -
place,注意remove_s_p的調用,當一個空閑塊被分配了,必須移除它static void place(void *bp, size_t asize) { size_t bsize = GET_SIZE(HDRP(bp)); /* when a free bloc be allocted, we must remove it */ remove_s_p(bp); if ((bsize - asize) >= (2*DSIZE)) { PUT(HDRP(bp), PACK(asize, 1)); PUT(FTRP(bp), PACK(asize, 1)); /* bp: Remainder of block */ bp = NEXT_BLKP(bp); PUT(HDRP(bp), PACK(bsize-asize, 0)); PUT(FTRP(bp), PACK(bsize-asize, 0)); PUT_SUCC(bp, NULL); PUT_PRED(bp, NULL); coalesce(bp); } else { PUT(HDRP(bp), PACK(bsize, 1)); PUT(FTRP(bp), PACK(bsize, 1)); } } -
coalesce- 要區別
remove_s_p的4個情況 和coalesce的case 1 ~ 4 情況
前者是把block從explicit free list中移除,這個block要么是將要被分配的block,要么將要被合並(coalesce)的free block,這4個情況指的是block的succ,pred的有無;
后者是把鄰近的free block合並,case 1 ~ 4 情況指的是block鄰近free block的有無 - 結束時,記得插入到空閑鏈當中
static void *coalesce(void *bp) { size_t prev_alloc = GET_ALLOC(HDRP(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) { /* alloc-> bp ->alloc */ /* then we will insert(bp) */ } else if (prev_alloc && !next_alloc) { /* alloc-> bp ->free */ remove_s_p(NEXT_BLKP(bp)); size += GET_SIZE(HDRP(NEXT_BLKP(bp))); PUT(HDRP(bp), PACK(size, 0)); PUT(FTRP(bp), PACK(size,0)); } else if (!prev_alloc && next_alloc) { /* free-> bp ->alloc */ remove_s_p(PREV_BLKP(bp)); size += GET_SIZE(HDRP(PREV_BLKP(bp))); PUT(FTRP(bp), PACK(size, 0)); PUT(HDRP(PREV_BLKP(bp)), PACK(size, 0)); bp = PREV_BLKP(bp); /* prev block as first free block */ } else { /* free-> bp ->free */ remove_s_p(NEXT_BLKP(bp)); remove_s_p(PREV_BLKP(bp)); size += GET_SIZE(HDRP(PREV_BLKP(bp))) + GET_SIZE(FTRP(NEXT_BLKP(bp))); PUT(HDRP(PREV_BLKP(bp)), PACK(size, 0)); PUT(FTRP(NEXT_BLKP(bp)), PACK(size, 0)); bp = PREV_BLKP(bp); } insert(bp); return bp; } - 要區別
-
extend_heap,添加前后繼的初始化語句/* Initialize explicit free list ptr: efree_listp, succ and pred */ PUT_SUCC(bp, 0); /* Free block succ */ PUT_PRED(bp, 0); /* Free block pred */ -
跑分

Segregated list + Best fit + Address order
-
實現Segregated list有兩種方式,一種是開辟新的內存塊來存放不同大小類的頭指針,另一種是用指針鏈表
筆者采用的是前者,代碼放在Segregated list + Best fit + Address order
后者可以看CS:APP3e 深入理解計算機系統_3e MallocLab實驗 -
Segregated list采用了九種大小類(size classes),注意因為
8字節對齊和頭腳的填充,最小塊是16字節 -
block_list_start,添加全局變量,用來指向不同大小類的頭指針static char* block_list_start = 0; /* Pointer to first free block of different size */ -
mm_init,初始化時,開辟新的內存塊來存放不同大小類的頭指針(九種)
block_list_start一開始指向最小類,注意heap_listp的指向也要隨之改變int mm_init(void) { if ((heap_listp = mem_sbrk(12*WSIZE)) == (void*)-1) return -1; PUT(heap_listp, 0); /* block size <= 32 */ PUT(heap_listp+(1*WSIZE), 0); /* block size <= 64 */ PUT(heap_listp+(2*WSIZE), 0); /* block size <= 128 */ PUT(heap_listp+(3*WSIZE), 0); /* block size <= 256 */ PUT(heap_listp+(4*WSIZE), 0); /* block size <= 512 */ PUT(heap_listp+(5*WSIZE), 0); /* block size <= 1024 */ PUT(heap_listp+(6*WSIZE), 0); /* block size <= 2048 */ PUT(heap_listp+(7*WSIZE), 0); /* block size <= 4096 */ PUT(heap_listp+(8*WSIZE), 0); /* block size > 4096 */ PUT(heap_listp+(9*WSIZE), PACK(DSIZE, 1)); /* Prologue header */ PUT(heap_listp+(10*WSIZE), PACK(DSIZE, 1)); /* Prologue footer */ PUT(heap_listp+(11*WSIZE), PACK(0, 1)); /* Epilogue header */ block_list_start = heap_listp; /* pointer to block size <= 32 */ heap_listp += (10 * WSIZE); /* pointer to Prologue footer */ if (extend_heap(2 * DSIZE/WSIZE) == NULL) return -1; return 0; } -
remove_s_p,由於指針頭不再是一個單獨的全局指針,而是一個內存塊,所以remove_s_p需要做出相應的調整,主要是要清空bp的前后繼,以及root要指向原先的后繼(succ),而不是NULLstatic void remove_s_p(void *bp) { void *root = get_sfreeh(GET_SIZE(HDRP(bp))); void* pred = GET_PRED(bp); void* succ = GET_SUCC(bp); PUT_PRED(bp, NULL); PUT_SUCC(bp, NULL); if (pred == NULL) { /* NULL-> bp ->NULL */ PUT_SUCC(root, succ); /* NULL-> bp ->FREE_BLK */ if (succ != NULL) PUT_PRED(succ, NULL); } else { /* FREE_BLK-> bp ->NULL */ PUT_SUCC(pred, succ); /* FREE_BLK-> bp ->FREE_BLK */ if (succ != NULL) PUT_PRED(succ, pred); } } -
insert,這最主要的修改,因為使用了Address order,需要按照不同大小類插入,而且插入時也要按照從小到大的順序插入(這時也要分為4種情況)static void insert(void* bp) { if (bp == NULL) return; void* root = get_sfreeh(GET_SIZE(HDRP(bp))); void* pred = root; void* succ = GET(root); /* size form small to big to aviod always use big free block */ while (succ != NULL) { if (GET_SIZE(HDRP(succ)) >= GET_SIZE(HDRP(bp))) break; pred = succ; succ = GET_SUCC(succ); } /* Luckly! the first succ block bigger than bp block */ /* So bp is root and pred of bp is NULL*/ if (pred == root) { /* 1. root -> insert, First insert*/ PUT(root, bp); PUT_PRED(bp, NULL); PUT_SUCC(bp, succ); /* 2. root -> insert -> xxx, First insert and then having free block*/ /* if succ != NULL, bp is pred of succ */ if (succ != NULL) PUT_PRED(succ, bp); } /* Unluckly! the first succ block smaller than bp block */ /* So bp is NOT root and pred of bp EXISTS*/ else { /* 3. root -> xxx -> insert, Last insert*/ PUT_PRED(bp, pred); PUT_SUCC(bp, succ); PUT_SUCC(pred, bp); /* 4. xxx-> insert -> xxx , Middle insert*/ /* if succ != NULL, bp is pred of succ*/ if (succ != NULL) PUT_PRED(succ, bp); } } -
get_slisth,找到不同大小類的內存塊void* get_sfreeh(size_t size) { int i = 0; /* i > 4096 */ if(size >= 4096) return block_list_start + (8 * WSIZE); /* i <= 32 */ size = size >> 5; /* other */ while (size){ size = size >> 1; i++; } return block_list_start + (i * WSIZE); } -
find_fit,由於采用了Address order,從頭開始找就算best fit了,注意如果當前類找不着,應當去下一個類看看能不能找到。static void* find_fit(size_t asize) { for (void* root = get_sfreeh(asize); root != (heap_listp-WSIZE); root += WSIZE){ void* bp = GET(root); while (bp){ if (GET_SIZE(HDRP(bp)) >= asize) return bp; bp = GET_SUCC(bp); } } return NULL; } -
跑分

Optimization
-
...is to you know first do fairly simple things and then look and see where the slowdowns and inefficiencies are and the just sort of hit those one after the other and optimize only for the things that are necessary.
Optimization: space VS time
- 優化從最簡陋的做起,教授這段話很有見地
-
針對
coalesce trace文件4優化定制,參考針對-coalesce-trace-文件優化定制-
因為文件4的請求經過對齊總是大於
4096bytes,所以第一次的extend_heap擴展堆(4096bytes)總是用不了,導致內存利用率總是66% -
因此第一次的擴展堆我們只申請最小塊(
16bytes),愛用不用= . =,影響很小,更改extend_heap/* First Extend: Only require the 16 bytes */ if (extend_heap(2 * DSIZE/WSIZE) == NULL)
-
-
跑分,代碼放在:Optimization

-
筆者沒有對remalloc進行優化,想看如何減少internal fragment,參考CS:APP3e 深入理解計算機系統_3e MallocLab實驗
Conclusion
Questions in lab
-
由於之前完全沒了解過linux操作系統,做到這個lab才知道
Makefile是干什么用的,在此簡單記錄下:-
指令
make用以編譯文件,形成可執行文件,指令make clean清空之前的可執行文件文件
先輸入make clean清空,再輸入make編譯可以解決以下問題:/usr/bin/ld: i386:x86-64 architecture of input file `mdriver.o' is incompatible with i386 output /usr/bin/ld: i386:x86-64 architecture of input file `mm.o' is incompatible with i386 output -
更改
Makefile的中參數可以改變編譯方式:-
一如,要用到
gdb調試,則對編譯命令加入-g字段(而后使用指令gdb mdriver即可調試)CFLAGS = -Wall -02 -m32 -g -
一如,刪除
-02優化字段,防止調試時出現跳躍,而不是線性(一行行)執行CFLAGS = -Wall -m32 -g
-
-
-
./mdriver -v報錯Testing mm malloc Reading tracefile: amptjp-bal.rep Could not open ... /amptjp-bal.rep in read_trace: No such file or directory-
在
config.h中更改TRACEDIR為本地自己的traces文件夾路徑,重新make即可#define TRACEDIR "/home/ubuntu/.../malloclab-handout/traces/"
-
-
don't worried about
warning: )- 實驗給出的
mdriver.c編譯時會出現warning但不會影響評分 - 筆者編寫的函數有些類型轉換存在問題,沒有徹底解決
- 實驗給出的
-
簡要復習下曾忘掉的
gdb調試命令break pointerstep (go into function) (come back byfinish)next (not go into function)infobreak pointer or othercontinue (jump this time)watch`parameter``- ``d`elete
btbuffer expresslayout srcrespectively express source code and debug cmdrun run at break pointerquitkill- type
Enterfrom keyboard to repeat last operation
-
主要遇到的bug
- segment fault(core Dump)
段錯誤(核心轉移),主要是訪問了已經使用的地址,或者非法訪問(eg. 訪問只讀內容) - Ran out of memory
超出內存范圍 - paylod overlap
重復分配已經分配的塊 - not align to
8byte
沒有向8字節對齊 - 一直在運行,但是跑不出結果 = =
- segment fault(core Dump)
-
誤解bug,改錯函數
-
不同blog的思路雖然相同,但實現差別蠻大,不容易移植
Thinks
-
完成日期:22.2.12
-
這個實驗憑我個人能力,中短時間內很難做出來,主要是bug很多,而且樣例不好調試,個人的debug能力沒有刻意練習過,gdb之前也沒好好練習上手,用的半生不熟,bug難解決,所以借鑒了許多blog的代碼實現。
-
還有的是,思路即使相同,但是實現時沒能想到更多的情形,被ppt的圖示框住了:剛寫
remove_s_p的時候,我一開始默認是前后塊都存在(實際是四種情形),而且也沒考慮到一個空閑塊被分配后也應當remove -
期間看代碼看到懷疑人生,整個人很自閉,太快地敲代碼,沒有細細想
-
以后要先細想清楚再實現,否則拿着錯誤的想法,debug到猴年馬月,還挫傷自信心
-
我的代碼之路離一萬小時還很遠,要保持耐心,加油!
-
一萬小時理論來自於《異類》這本書,以及《奇特的一生》這兩本書改變了我原先的學習心態,我有感覺,它們會對我今后的學習有更大影響,推薦給你,我的讀者。
