CS:APP3e 深入理解計算機系統_3e MallocLab實驗


**詳細的題目要求和資源可以到 http://csapp.cs.cmu.edu/3e/labs.html 或者 http://www.cs.cmu.edu/~./213/schedule.html 獲取。**
在這個實驗中我們需要實現自己的動態內存申請器(malloc、free、realloc)

前期准備:

  • 完全閱讀書本第9章
  • man 3 realloc

注意事項:

1.先從小的測試文件開始,例如short1-bal.rep

2.為了調試方便,在Makefile中將CFLAGS更改為:

CFLAGS = -Wall -O2 -m32 -ggdb

這樣用GDB調試的時候就能看到源碼了

3.地址要對8字節對齊。

4.注意realloc的實現要和libc一致。

5.本實驗環境WORD=4=sizeof(void *),DWORD=8(gcc -m32)


思路要點及其實現:

對於速度(thru)而言,我們需要關注malloc、free、realloc每次操作的復雜度。對於內存利用率(util)而言,我們需要關注internal fragmentation (塊內損失)和 external fragmentation (塊是分散不連續的,無法整體利用),即我們free和malloc的時候要注意整體大塊利用(例如合並free塊、realloc的時候判斷下一個塊是否空閑)。

我這里實現的是書上9.9.13和9.9.14提到的Explicit Free Lists + Segregated Free Lists + Segregated Fits ,詳細的介紹參考書上寫的。


塊的結構如下,其中低三位由於內存對齊的原因總會是0,A代表最低位為1,即該塊已經allocated:

堆的起始和結束結構如下:

Free list的結構如下,每條鏈上的塊按大小由小到大排列,這樣我們用“first hit”策略搜索鏈表的時候就能獲得“best hit”的性能,例如第一條鏈,A是B的successor,B是A的predecessor,A的大小小於等於B;不同鏈以塊大小區分,依次為{1}{2}{34}{58}...{1025~2048}... :

更新:這里的箭頭應該是雙向的,畫錯了。


下面是各個模塊的實現,**部分代碼改編自CS:APP3e官網的Code examplesmm.c(完整代碼),如需使用請聯系Randy Bryant and Dave O'Hallaron ** 。這次注釋用中文寫的,就不另外解釋了。


常數及指針運算的宏定義

/* 向上進行對齊 */
#define ALIGNMENT 8
#define ALIGN(size) ((((size) + (ALIGNMENT-1)) / (ALIGNMENT)) * (ALIGNMENT))

#define WSIZE     4
#define DSIZE     8

/* 每次擴展堆的塊大小(系統調用“費時費力”,一次擴展一大塊,然后逐漸利用這一大塊) */
#define INITCHUNKSIZE (1<<6)
#define CHUNKSIZE (1<<12)

#define LISTMAX     16

#define MAX(x, y) ((x) > (y) ? (x) : (y))
#define MIN(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 SET_PTR(p, ptr) (*(unsigned int *)(p) = (unsigned int)(ptr))

#define GET_SIZE(p)  (GET(p) & ~0x7)
#define GET_ALLOC(p) (GET(p) & 0x1)

#define HDRP(ptr) ((char *)(ptr) - WSIZE)
#define FTRP(ptr) ((char *)(ptr) + GET_SIZE(HDRP(ptr)) - DSIZE)

#define NEXT_BLKP(ptr) ((char *)(ptr) + GET_SIZE((char *)(ptr) - WSIZE))
#define PREV_BLKP(ptr) ((char *)(ptr) - GET_SIZE((char *)(ptr) - DSIZE))

#define PRED_PTR(ptr) ((char *)(ptr))
#define SUCC_PTR(ptr) ((char *)(ptr) + WSIZE)

#define PRED(ptr) (*(char **)(ptr))
#define SUCC(ptr) (*(char **)(SUCC_PTR(ptr)))

全局變量

/* 分離空閑表 */
void *segregated_free_lists[LISTMAX];
/* 實驗信息 */
team_t team = {"1603002","Qiuhao Li","liqiuhao727@outlook.com","",""};


Helper functions

/* 擴展推 */
static void *extend_heap(size_t size);
/* 合並相鄰的Free block */
static void *coalesce(void *ptr);
/* 在prt所指向的free block塊中allocate size大小的塊,如果剩下的空間大於2*DWSIZE,則將其分離后放入Free list */
static void *place(void *ptr, size_t size);
/* 將ptr所指向的free block插入到分離空閑表中 */
static void insert_node(void *ptr, size_t size);
/* 將ptr所指向的塊從分離空閑表中刪除 */
static void delete_node(void *ptr);

Helper functions: extend_heap

static void *extend_heap(size_t size)
{
    void *ptr;
    /* 內存對齊 */
    size = ALIGN(size);
    /* 系統調用“sbrk”擴展堆 */
    if ((ptr = mem_sbrk(size)) == (void *)-1)
        return NULL;

    /* 設置剛剛擴展的free塊的頭和尾 */
    PUT(HDRP(ptr), PACK(size, 0));
    PUT(FTRP(ptr), PACK(size, 0));
    /* 注意這個塊是堆的結尾,所以還要設置一下結尾 */
    PUT(HDRP(NEXT_BLKP(ptr)), PACK(0, 1));
    /* 設置好后將其插入到分離空閑表中 */
    insert_node(ptr, size);
    /* 另外這個free塊的前面也可能是一個free塊,可能需要合並 */
    return coalesce(ptr);
}

Helper functions: insert_node

static void insert_node(void *ptr, size_t size)
{
    int listnumber = 0;
    void *search_ptr = NULL;
    void *insert_ptr = NULL;

    /* 通過塊的大小找到對應的鏈 */
    while ((listnumber < LISTMAX - 1) && (size > 1))
    {
        size >>= 1;
        listnumber++;
    }

    /* 找到對應的鏈后,在該鏈中繼續尋找對應的插入位置,以此保持鏈中塊由小到大的特性 */
    search_ptr = segregated_free_lists[listnumber];
    while ((search_ptr != NULL) && (size > GET_SIZE(HDRP(search_ptr))))
    {
        insert_ptr = search_ptr;
        search_ptr = PRED(search_ptr);
    }

    /* 循環后有四種情況 */
    if (search_ptr != NULL)
    {
        /* 1. ->xx->insert->xx 在中間插入*/
        if (insert_ptr != 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);
        }
        /* 2. [listnumber]->insert->xx 在開頭插入,而且后面有之前的free塊*/
        else
        {
            SET_PTR(PRED_PTR(ptr), search_ptr);
            SET_PTR(SUCC_PTR(search_ptr), ptr);
            SET_PTR(SUCC_PTR(ptr), NULL);
            segregated_free_lists[listnumber] = ptr;
        }
    }
    else
    {
        if (insert_ptr != NULL)
        { /* 3. ->xxxx->insert 在結尾插入*/
            SET_PTR(PRED_PTR(ptr), NULL);
            SET_PTR(SUCC_PTR(ptr), insert_ptr);
            SET_PTR(PRED_PTR(insert_ptr), ptr);
        }
        else
        { /* 4. [listnumber]->insert 該鏈為空,這是第一次插入 */
            SET_PTR(PRED_PTR(ptr), NULL);
            SET_PTR(SUCC_PTR(ptr), NULL);
            segregated_free_lists[listnumber] = ptr;
        }
    }
}

Helper functions: delete_node

static void delete_node(void *ptr)
{
    int listnumber = 0;
    size_t size = GET_SIZE(HDRP(ptr));

    /* 通過塊的大小找到對應的鏈 */
    while ((listnumber < LISTMAX - 1) && (size > 1))
    {
        size >>= 1;
        listnumber++;
    }

    /* 根據這個塊的情況分四種可能性 */
    if (PRED(ptr) != NULL)
    {
        /* 1. xxx-> ptr -> xxx */
        if (SUCC(ptr) != NULL)
        {
            SET_PTR(SUCC_PTR(PRED(ptr)), SUCC(ptr));
            SET_PTR(PRED_PTR(SUCC(ptr)), PRED(ptr));
        }
        /* 2. [listnumber] -> ptr -> xxx */
        else
        {
            SET_PTR(SUCC_PTR(PRED(ptr)), NULL);
            segregated_free_lists[listnumber] = PRED(ptr);
        }
    }
    else
    {
        /* 3. [listnumber] -> xxx -> ptr */
        if (SUCC(ptr) != NULL)
        {
            SET_PTR(PRED_PTR(SUCC(ptr)), NULL);
        }
        /* 4. [listnumber] -> ptr */
        else
        {
            segregated_free_lists[listnumber] = NULL;
        }
    }
}

Helper functions: coalesce

static void *coalesce(void *ptr)
{
    _Bool is_prev_alloc = GET_ALLOC(HDRP(PREV_BLKP(ptr)));
    _Bool is_next_alloc = GET_ALLOC(HDRP(NEXT_BLKP(ptr)));
    size_t size = GET_SIZE(HDRP(ptr));
    /* 根據ptr所指向塊前后相鄰塊的情況,可以分為四種可能性 */
    /* 另外注意到由於我們的合並和申請策略,不可能出現兩個相鄰的free塊 */
    /* 1.前后均為allocated塊,不做合並,直接返回 */
    if (is_prev_alloc && is_next_alloc)
    {
        return ptr;
    }
    /* 2.前面的塊是allocated,但是后面的塊是free的,這時將兩個free塊合並 */
    else if (is_prev_alloc && !is_next_alloc)
    {
        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));
    }
    /* 3.后面的塊是allocated,但是前面的塊是free的,這時將兩個free塊合並 */
    else if (!is_prev_alloc && is_next_alloc)
    {
        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);
    }
    /* 4.前后兩個塊都是free塊,這時將三個塊同時合並 */
    else
    {
        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);
    }

    /* 將合並好的free塊加入到空閑鏈接表中 */
    insert_node(ptr, size);

    return ptr;
}

Helper functions: place

static void *place(void *ptr, size_t size)
{
    size_t ptr_size = GET_SIZE(HDRP(ptr));
    /* allocate size大小的空間后剩余的大小 */
    size_t remainder = ptr_size - size;

    delete_node(ptr);

    /* 如果剩余的大小小於最小塊,則不分離原塊 */
    if (remainder < DSIZE * 2)
    {
        PUT(HDRP(ptr), PACK(ptr_size, 1));
        PUT(FTRP(ptr), PACK(ptr_size, 1));
    }

    /* 否則分離原塊,但這里要注意這樣一種情況(在binary-bal.rep和binary2-bal.rep有體現): 
    *  如果每次allocate的塊大小按照小、大、小、大的連續順序來的話,我們的free塊將會被“拆”成以下這種結構:
    *  其中s代表小的塊,B代表大的塊

 s      B      s       B     s      B      s     B
+--+----------+--+----------+-+-----------+-+---------+
|  |          |  |          | |           | |         |
|  |          |  |          | |           | |         |
|  |          |  |          | |           | |         |
+--+----------+--+----------+-+-----------+-+---------+

    *  這樣看起來沒什么問題,但是如果程序后來free的時候不是按照”小、大、小、大“的順序來釋放的話就會出現“external fragmentation”
    *  例如當程序將大的塊全部釋放了,但小的塊依舊是allocated:

 s             s             s             s
+--+----------+--+----------+-+-----------+-+---------+
|  |          |  |          | |           | |         |
|  |   Free   |  |   Free   | |   Free    | |   Free  |
|  |          |  |          | |           | |         |
+--+----------+--+----------+-+-----------+-+---------+

    *  這樣即使我們有很多free的大塊可以使用,但是由於他們不是連續的,我們不能將它們合並,如果下一次來了一個大小為B+1的allocate請求
    *  我們就還需要重新去找一塊Free塊
    *  與此相反,如果我們根據allocate塊的大小將小的塊放在連續的地方,將達到開放在連續的地方:

 s  s  s  s  s  s      B            B           B
+--+--+--+--+--+--+----------+------------+-----------+
|  |  |  |  |  |  |          |            |           |
|  |  |  |  |  |  |          |            |           |
|  |  |  |  |  |  |          |            |           |
+--+--+--+--+--+--+----------+------------+-----------+

    *  這樣即使程序連續釋放s或者B,我們也能夠合並free塊,不會產生external fragmentation
    *  這里“大小”相對判斷是根據binary-bal.rep和binary2-bal.rep這兩個文件設置的,我這里在96附近能夠達到最優值
    *  
    */
    else if (size >= 96)
    {
        PUT(HDRP(ptr), PACK(remainder, 0));
        PUT(FTRP(ptr), PACK(remainder, 0));
        PUT(HDRP(NEXT_BLKP(ptr)), PACK(size, 1));
        PUT(FTRP(NEXT_BLKP(ptr)), PACK(size, 1));
        insert_node(ptr, remainder);
        return NEXT_BLKP(ptr);
    }

    else
    {
        PUT(HDRP(ptr), PACK(size, 1));
        PUT(FTRP(ptr), PACK(size, 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;
}


初始化堆 mm_init:

int mm_init(void)
{
    int listnumber;
    char *heap; 

    /* 初始化分離空閑鏈表 */
    for (listnumber = 0; listnumber < LISTMAX; 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;
}

申請塊:mm_malloc:

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 < LISTMAX)
    {
        /* 尋找對應鏈 */
        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;
}

釋放塊: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);
}

調整塊:mm_realloc:

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;
}



最終結果:


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM