1. 程式人生 > >記憶體管理演算法--夥伴演算法

記憶體管理演算法--夥伴演算法

本來想年前就把記憶體管理這部分結束,可是計劃趕不上變化啊,過年事情太多,只能一拖再拖了,今天終於把夥伴演算法機制寫完了。這一節不再像前面似的只給出理論框架,畢竟linux記憶體管理的兩個演算法--夥伴演算法和slab網上資料太多了,在家時間比起上班畢竟是多一些,所以想直接用程式碼實現算了,這樣不僅可以理解更深一些,或許哪天還能直接用的上。

在貼程式碼之前還是先得說一下演算法思想,夥伴演算法是linux核心用來分配頁級別記憶體的管理演算法,我們平時在申請記憶體時候,通常是以位元組為單位的,但是有些時候需要大段的記憶體,這時候核心就以(頁)4KB為單位來分配,這樣每次分配的結果就是4KB的倍數。分配出來的線性地址一般都是連續的,但是不能保證實體地址連續,而有時候我們需要分配一些大段的實體地址也連續的記憶體,比如DMA操作。但是從前面兩節分頁管理機制可以看出,線性地址在進行對映時候是以4KB為單位隨機的,所以必然會導致記憶體碎片,什麼是記憶體碎片呢,以下圖為例:


這是一個真實的實體記憶體,前面1-6表示6個頁面,如果分配了3個4KB,佔用了1、3、5,這時候我們想分配一個連續的8KB實體記憶體,2和4是8KB,但是不連續,不能分配使用。這時候相對於8KB連續實體記憶體來說,2和4就成了記憶體碎片了,這很容易理解。所以linux使用了夥伴管理演算法來改善這個問題,對於演算法基本理論,引用自謀篇部落格:

http://www.cnblogs.com/leaven/archive/2010/12/03/1895134.html,內容如下:

Buddy System是一種經典的記憶體管理演算法。在Unix和Linux作業系統中都有用到。其作用是減少儲存空間中的空洞、減少碎片、增加利用率。避免外碎片的方法有兩種:

a.利用分頁單元把一組非連續的空閒頁框對映到非連續的線性地址區間。

b.開發適當的技術來記錄現存的空閒連續頁框塊的情況,以儘量避免為滿足對小塊的請求而把大塊的空閒塊進行分割。

基於下面三種原因,核心選擇第二種避免方法:

a.在某些情況下,連續的頁框確實必要。

b.即使連續頁框的分配不是很必要,它在保持核心頁表不變方面所起的作用也是不容忽視的。假如修改頁表,則導致平均訪存次數增加,從而頻繁重新整理TLB。

c.通過4M的頁可以訪問大塊連續的實體記憶體,相對於4K頁的使用,TLB未命中率降低,加快平均訪存速度。

buddy演算法將所有空閒頁框分組為10個塊連結串列,每個塊連結串列分別包含1,2,4,8,16,32,64,128,256,512個連續的頁框,每個塊的第一個頁框的實體地址是該塊大小的整數倍。如,大小為16個頁框的塊,其起始地址是16*2^12的倍數。

例,假設要請求一個128個頁框的塊,演算法先檢查128個頁框的連結串列是否有空閒塊,如果沒有則查256個頁框的鏈 表,有則將256個頁框的塊分裂兩份,一份使用,一份插入128個頁框的連結串列。如果還沒有,就查512個頁框的連結串列,有的話就分裂為 128,128,256,一個128使用,剩餘兩個插入對應連結串列。如果在512還沒查到,則返回出錯訊號。

回收過程相反,核心試圖把大小為b的空閒夥伴合併為一個大小為2b的單獨塊,滿足以下條件的兩個塊稱為夥伴:

a.兩個塊具有相同的大小,記做b。

b.它們的實體地址是連續的。

c.第一個塊的第一個頁框的實體地址是2*b*2^12的倍數。

該演算法迭代,如果成功合併所釋放的塊,會試圖合併2b的塊來形成更大的塊。

演算法思想上面說的很清楚了,概況如下:

1.把不同大小的記憶體塊形成連結串列,在分配記憶體時候直接到連結串列查詢有沒有空閒塊。

2.如果連結串列中沒有空閒記憶體塊,那麼到更大的記憶體塊連結串列中查詢,查詢到以後對其進行拆分

3.釋放某個記憶體塊時候,看其有無相鄰的空閒記憶體塊,如果有,合併為更大記憶體塊連結入新的連結串列

下面就是原始碼實現了:

1.因為演算法實現大量依賴與連結串列操作,所以我直接把linux比較經典的連結串列結構移植過來使用。裡邊有個巨集定義 list_entry 有點特殊,裡邊某些關鍵字可能只有GNU支援,或者是C99的一些擴充套件,具體沒有去查證。記得VC6.0是不支援的,我用的開發環境是windows環境的qt5.2,它也是支援的。

2.這並不是linux真實原始碼,而是在使用者空間模擬核心夥伴演算法

3.為了不對釋放過程造成干擾,人為對記憶體塊做了特殊處理:初始化時候讓相同大小的記憶體塊做到不相鄰

首先是連結串列操作:
#ifndef _LIST_H
#define _LIST_H

//定義核心連結串列結構
struct list_head
{
    struct list_head *next, *prev;
};

//連結串列初始化
static inline void INIT_LIST_HEAD(struct list_head *list)
{
    list->next = list;
    list->prev = list;
}

//插入結點
static inline void __list_add(struct list_head *new_list,
                            struct list_head *prev, struct list_head *next)
{
    next->prev = new_list;
    new_list->next = next;
    new_list->prev = prev;
    prev->next = new_list;
}

//在連結串列頭部插入
static inline void list_add(struct list_head *new_list, struct list_head *head)
{
    __list_add(new_list, head, head->next);
}

//刪除任意結點
static inline void __list_del(struct list_head * prev, struct list_head * next)
{
    next->prev = prev;
    prev->next = next;
}

static inline void list_del(struct list_head *entry)
{
    __list_del(entry->prev, entry->next);
}

//後序(指標向後走)遍歷連結串列
#define list_for_each(pos, head) \
    for (pos = (head)->next; pos != (head); pos = pos->next)

//前序(指標向前走)遍歷連結串列
#define list_for_each_prev(pos, head) \
    for (pos = (head)->prev; pos != (head); pos = pos->prev)

#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)

//這個巨集中某些可能只有GNU支援,我實驗的環境是windows下qt5.2,很幸運,也支援
#define list_entry(ptr, type, member) ({			\
    const typeof( ((type *)0)->member ) *__mptr = (ptr);	\
    (type *)( (char *)__mptr - offsetof(type,member) );})

#endif
標頭檔案
#ifndef MEM_MANAGE_H
#define MEM_MANAGE_H

#include "list.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define MEM_NUM 11  //不同種類記憶體塊總數目
#define BLOCK_BASE_SIZE 4096  //以4KB為單位分配

#define TRUE  1
#define FALSE 0

#define random(x) (rand()%x + 1)  //隨機數範圍(1到x),用於屬性隨機數產生
#define cloth2cover(num) ((num & num-1) == 0)? TRUE:FALSE  //判斷是否是2^n

typedef unsigned int u32;

//記憶體連結串列資料結構
struct free_area_head
{
    u32 size;  //連結串列記憶體大小
    u32 num;  //相應記憶體塊數目
    struct list_head list;
};

//記憶體塊資料結構
struct free_chunk
{
    u32 size;   //單位是KB
    u32 addr;  //記憶體地址
    int dir;    //標識這塊記憶體塊是否被佔用,1被佔用,0空閒
    struct list_head list;
};

//函式宣告
int mem_init();
int mem_alloc(int size);
inline int mem_avail(int size);
int mem_free(int size);

#endif // MEM_MANAGE_H

夥伴演算法實現檔案:
#include "mem_manage.h"

struct free_area_head mem_arr[MEM_NUM];  //指向不同種類記憶體塊的指標陣列
char *pmem = NULL;

/**
 * @brief 記憶體初始化函式,包括申請記憶體,分割記憶體,掛入管理連結串列等操作
 * @return 成功返回0,失敗返回-1
 */
int mem_init(void)
{
    int i, j, total_size = 0;
    u32 trunk_size;
    struct free_chunk *tmp_chunk = NULL;

    for(i=0; i<MEM_NUM; i++)
    {
        memset(&mem_arr[i], 0, sizeof(struct free_area_head));
        //第0個連結串列代表4KB的記憶體塊,第1個代表8KB,第2個代表16KB...以此類推
        mem_arr[i].size = (1<<i) * BLOCK_BASE_SIZE;  //單位是位元組
        INIT_LIST_HEAD(&mem_arr[i].list);  //初始化每個連結串列頭
    }

    //分配4KB的記憶體塊11個,8KB的10個,16KB的9個....4MB的1個
    for(i=0; i<MEM_NUM; i++)
    {
        total_size += (i+1)*BLOCK_BASE_SIZE * (1<<(10-i));
    }

    //把這塊記憶體分配出來
    pmem = (char *)malloc(total_size);
    if(pmem == NULL)
    {
        printf("err malloc mem\n");
        return -1;
    }
    else
    {
        printf("malloc mem success\n");
        printf("alloc mem init addr is %u\n\n", pmem);
    }


    /*
        這段程式碼可能不太好理解,需要把具體數帶進去驗算一下
        目的:把上面分配的一大段連續記憶體依次拆分為:
        4KB/8KB/16KB...4096KB          第1個序列
        4KB/8KB/16KB...2048KB          第2個序列
        4KB/8KB/16KB...1024KB          第3個序列
        4KB/8KB/16KB...512KB           第4個序列
        4KB/8KB/16KB...256KB           第5個序列
        4KB/8KB/16KB...128KB           第6個序列
        4KB/8KB/16KB...64KB            第7個序列
        4KB/8KB/16KB/32KB              第8個序列
        4KB/8KB/16KB                   第9個序列
        4KB/8KB                        第10個序列
        4KB                            第11個序列

        上面的記憶體段都是連續的,這樣做的目的是讓所有相同大小的記憶體塊都不相鄰
        兩個巢狀之後,上面記憶體卡就依次聯入相應的連結串列中了
    */

    for(i=MEM_NUM; i>0; i--)
    {
        //第一個連續記憶體  4KB/8KB/16KB...2048KB/4096KB
        for(j=0; j<i; j++)
        {
            //先把4KB掛在4KB的連結串列上
            trunk_size = BLOCK_BASE_SIZE*(1<<j);
            tmp_chunk = (struct free_chunk *)malloc(sizeof(struct free_chunk));
            tmp_chunk->size = trunk_size / 1024;  //以KB為單位
            tmp_chunk->addr = pmem;  //記錄下這個地址
            tmp_chunk->dir = 0;  //初始化記憶體卡未被佔用
            list_add(&tmp_chunk->list, &mem_arr[j].list); //插入連結串列
            mem_arr[j].num++;  //相應連結串列記憶體塊數目加1
            pmem += trunk_size; //指標相應往後移動4KB/8KB/16KB...2048KB/4096KB
        }
    }

    //我們把各個連結串列相關內容打印出來看一下
    struct list_head *pos;
    struct free_chunk *tmp;

    //首先是每個連結串列的記憶體塊大小和記憶體塊數目
    for(i=0; i<MEM_NUM; i++)
    {
        printf("the %d list mem num is %d:", i, mem_arr[i].num);
        list_for_each(pos, &mem_arr[i].list)
        {
            tmp = list_entry(pos, struct free_chunk, list);
            printf("%d ", tmp->size);
        }
        printf("\n");
    }

    //怎麼驗證記憶體地址的正確性呢?
    printf("\nthe 4KB list chunk addr is:\n");
    //由於list_add是頭部插入,所以這裡按照從尾部到頭部的順序列印
    list_for_each_prev(pos, &mem_arr[0].list)
    {
        tmp = list_entry(pos, struct free_chunk, list);
        printf("%u ", tmp->addr);
    }
    printf("\n\n");

    /*
        malloc mem success
        alloc mem init addr is 19136544

        the 0 list mem num is 11:4 4 4 4 4 4 4 4 4 4 4
        the 1 list mem num is 10:8 8 8 8 8 8 8 8 8 8
        the 2 list mem num is 9:16 16 16 16 16 16 16 16 16
        the 3 list mem num is 8:32 32 32 32 32 32 32 32
        the 4 list mem num is 7:64 64 64 64 64 64 64
        the 5 list mem num is 6:128 128 128 128 128 128
        the 6 list mem num is 5:256 256 256 256 256
        the 7 list mem num is 4:512 512 512 512
        the 8 list mem num is 3:1024 1024 1024
        the 9 list mem num is 2:2048 2048
        the 10 list mem num is 1:4096

        the 4KB list chunk addr is:
        19136544 27521056 31711264 33804320 34848800 35368992
        35627040 35754016 35815456 35844128 35856416

        這些地址每次執行程式都不一樣,根據實際情況來看
        得到上面列印結果,首先我們看到11個連結串列的數目和每個記憶體卡的大小是正確的
        然後如何驗證地址呢?理解這個需要自己畫下圖,以第一個連結串列的前兩個4KB塊的
        首地址為例,這兩個首地址應該隔著這麼大一塊地址空間:
        4+8+16+32+64+128+256+512+1024+2048+4096 = ?KB
        那麼 27521056 - 19136544 = 8384512(byte) = 8188KB
        你可以驗算一下,這兩個值是相等的,同理,你可以驗證第2個和第3個的差值
        看是否和理論上的值一樣
    */
    return 0;
}


/**
 * @brief 得到記憶體陣列索引,實際上就是求size是2的幾次冪
 */
static int get_index(int size)
{
    int i, tmp = 0;
    size /= 4;

    for(i=0; tmp < size; i++)
    {
        tmp = 1<<i;
        if(tmp == size)
        {
            return i;
        }
    }

    return -1;  //實際上經過前面判斷不可能執行到這
}


/**
 * @brief 比較核心的函式,完成功能如下
 * 1.判斷需要拆分的記憶體塊是目標記憶體塊的多少倍,從而知道應該拆分為幾塊
 * 2.把被拆分的大記憶體塊從相應連結串列執行上斷鏈操作
 * 3.把拆分的新記憶體塊鏈入目標記憶體連結串列中並置dir位為0(未佔用)
 * 4.選取一塊返回,申請成功
 *
 * @param dst_index 目的連結串列索引值,即本想要申請的記憶體塊所在連結串列索引值
 * @param src_index 源連結串列索引值,即需要拆分的記憶體塊所在的連結串列索引值
 * @param block 需要拆分記憶體塊
 */
static void separate_block(int dst_index, int src_index, struct free_chunk *block)
{
    int i;
    char *pmem;
    u32 block_num, dst_size;
    block_num = (1<<(src_index - dst_index));  //2^差值 倍

    list_del(&block->list);  //把被拆分的大記憶體塊從相應連結串列執行上斷鏈
    mem_arr[src_index].num--;  //記憶體塊數目-1
    printf("%d list separate 1 block\n", src_index);

    //拆分為block_num塊
    pmem = block->addr;  //記錄首地址
    dst_size = mem_arr[dst_index].size;  //目的記憶體塊大小

    //拆分併入鏈
    struct free_chunk *tmp_chunk = NULL;

    printf("%d list increase %d block\n", dst_index, block_num);
    for(i=0; i<block_num; i++)
    {
        tmp_chunk = (struct free_chunk *)malloc(sizeof(struct free_chunk));
        tmp_chunk->size = dst_size / 1024;  //以KB為單位
        tmp_chunk->addr = pmem;  //記錄下這個地址
        tmp_chunk->dir = 0;  //初始化記憶體卡未被佔用
        list_add(&tmp_chunk->list, &mem_arr[dst_index].list); //插入連結串列
        mem_arr[dst_index].num++;  //相應連結串列記憶體塊數目加1
        pmem += dst_size; //指標相應往後移動dst_size位元組
    }

    //經過上面的迴圈,拆分入鏈操作就完成了,下面只需要把拆分的記憶體塊選一塊返回即可
    struct list_head *pos;
    struct free_chunk *tmp;

    //肯定能找到的
    list_for_each(pos, &mem_arr[dst_index].list)
    {
        tmp = list_entry(pos, struct free_chunk, list);
        if(tmp->dir == 0)
        {
            printf("malloc success,addr = %u\n", tmp->addr);
            tmp->dir = 1; //標記記憶體塊為佔用
            return;
        }
    }
}

/**
 * @brief 行內函數,判斷輸入的size是否合法
 */
inline int mem_avail(int size)
{
    if(size<4 || size>(4<<10))  //最小4KB,最大4096KB
    {
        printf("size must > 4 and <= 4096\n");
        return FALSE;
    }
    else if(!cloth2cover(size))  //必須是2的冪
    {
        printf("size must be 2^n\n");
        return FALSE;
    }
    else
    {
        return TRUE;
    }
}

/**
* @brief 模擬核心申請記憶體過程
* @param size 申請記憶體大小,單位為KB
* @return 成功返回0,失敗返回-1
*/
int mem_alloc(int size)
{
    int index, i;

    if(!mem_avail(size))
    {
        return -1;
    }

    /*
      下面是夥伴演算法記憶體申請的過程,思想如下:
      1.首先根據size大小去相應的連表上去查詢有沒有空閒的記憶體塊
        如果有,那麼直接返回地址,並把相應記憶體塊的的dir標誌置位
      2.如果沒有,那麼去它上一級連結串列中找空閒塊,比如4KB沒有,那就去8KB找
        如果8KB也沒有,就去16KB找...
      3.如果上一級連結串列中找到了空閒塊,那麼把這個空閒塊從上一級連結串列中分類
        拆分為相應大小的記憶體卡後鏈入查詢的連結串列中
      4.如果直到4MB的記憶體卡都沒有空閒塊,那麼返回錯誤,提示記憶體不足

      這段程式碼的難點在窮盡的查詢比比size大的連結串列操作
    */
    index = get_index(size);

    printf("first find %d list\n", index);

    struct list_head *pos;
    struct free_chunk *tmp;

    list_for_each(pos, &mem_arr[index].list)
    {
        tmp = list_entry(pos, struct free_chunk, list);
        if(tmp->dir == 0)  //找到了一塊
        {
            printf("malloc success,addr = %u\n", tmp->addr);
            tmp->dir = 1; //標記記憶體塊為佔用
            return 0;
        }
    }

    //如果執行到這裡,那麼說明對應的記憶體塊連結串列沒有找到空閒記憶體塊
    printf("the %d list has no suitable mem block\n", index);

    //我們從比它大的記憶體塊中再去查詢,最大就是4MB的連結串列了
    for(i=index+1; i<MEM_NUM; i++)
    {
        printf("we will find %d list\n", i);

        list_for_each(pos, &mem_arr[i].list)
        {
            tmp = list_entry(pos, struct free_chunk, list);
            if(tmp->dir == 0)  //找到了一塊
            {
                printf("find a free block from %d list,addr = %u\n", i, tmp->addr);
                //把這塊大的記憶體塊拆分為小的,並進行分配處理
                separate_block(index, i, tmp);
                return 0;
            }
        }
    }

    //如果執行到這裡,那麼說明相應大小的記憶體塊無法分配成功
    printf("can't malloc mem\n");
    return -1;
}

/**
 * @brief 判斷兩個地址是否相鄰
 * @param compare_addr 比較的地址
 * @param target_addr  目標地址
 * @param size  連結串列上記憶體塊的大小
 * @return 相鄰:TRUE 不相鄰:FALSE
 */
static int inline is_neighbor(u32 compare_addr, u32 target_addr, u32 size)
{
    //這裡是無符號數,不能用絕對值
    if(compare_addr > target_addr)
    {
        if(compare_addr - target_addr == size)
            return TRUE;
        else
            return FALSE;
    }
    else
    {
        if(target_addr - compare_addr == size)
            return TRUE;
        else
            return FALSE;
    }
}

/**
 * @brief 從索引值為index的連結串列上查詢block的夥伴記憶體塊並返回
 * @param block
 * @param index
 * @return
 */
struct free_chunk *find_buddy(struct free_chunk *block, int index)
{
    //夥伴記憶體塊:大小相同,地址相鄰,並且也沒有被佔用
    struct list_head *pos;
    struct free_chunk *tmp;

    list_for_each(pos, &mem_arr[index].list)
    {
        tmp = list_entry(pos, struct free_chunk, list);
        if(tmp->dir == 0)  //沒有被佔用才有比較的資格
        {
            if(is_neighbor(tmp->addr, block->addr, block->size*1024))
            {
                return tmp;
            }
        }
    }

    //到這裡就是沒找到
    return NULL;
}

enum BUDDY_TYPE
{
    NO_BUDDY = 0,  //沒有夥伴
    LAST_LIST,      //到了最後的連結串列了
};

/**
 * @brief 遞迴的查詢夥伴並釋放記憶體
 * @param block 需要釋放的記憶體塊
 * @param index 記憶體塊所在的連結串列索引
 */
int recursive_free(struct free_chunk *block, int index)
{
    struct free_chunk *buddy;

    if(index > MEM_NUM)  //遞迴到了4MB的連結串列上
    {
        printf("max index list\n");
        return LAST_LIST;
    }

    buddy = find_buddy(block, index);  //在本連結串列上為它找一個“夥伴記憶體塊”
    if(buddy == NULL)  //這個記憶體塊沒有”夥伴“
    {
        printf("this block has no buddy\n");
        block->dir = 0;  //釋放它既可
        return NO_BUDDY;
    }
    else
    {
        printf("this block find a buddy\n");
        //兩個記憶體塊從原來連結串列斷鏈
        list_del(&block->list);
        list_del(&buddy->list);
        mem_arr[index].num -= 2;  //少了兩塊
        printf("%d list decrease 2 block\n", index);

        //合併為新的記憶體塊併入鏈下一級連結串列
        int new_addr = (block->addr < buddy->addr) ? block->addr:buddy->addr;

        struct free_chunk *tmp_chunk = NULL;
        tmp_chunk = (struct free_chunk *)malloc(sizeof(struct free_chunk));
        tmp_chunk->size = block->size * 2;  //是原來記憶體塊的兩倍大
        tmp_chunk->addr = new_addr;  //記錄下這個地址
        tmp_chunk->dir = 0;  //初始化記憶體塊未被佔用
        index++;
        list_add(&tmp_chunk->list, &mem_arr[index].list); //插入連結串列
        mem_arr[index].num++;  //相應連結串列記憶體塊數目加1
        printf("%d list increase 1 block\n", index);

        //迴圈這個過程,在上一級連結串列中查詢夥伴,直到找不到夥伴或者到了4MB連結串列
        recursive_free(tmp_chunk, index);
    }
}

/**
* @brief 模擬核心釋放記憶體過程
* @param size 釋放記憶體大小,單位為KB
* @return 成功返回0,失敗返回-1
*/
int mem_free(int size)
{
    int index, i;

    if(!mem_avail(size))
    {
        return -1;
    }

    /*
      下面是夥伴演算法釋放記憶體的過程,思想如下:
      1.首先根據size大小去相應的連表上去查詢第一個被佔用的記憶體塊
      2.判斷這個記憶體塊是否有夥伴(大小相同,地址相鄰)
      3.如果沒有,直接把dir位清0即可
      4.如果有,那麼把這兩個記憶體塊分別從所在的連結串列上斷鏈,然後入鏈到上一級連結串列中
      5.到上一級連結串列中繼續 2 3 4 操作,直到某一級連結串列沒有夥伴為止
    */
    index = get_index(size);

    printf("first find %d list\n", index);

    struct list_head *pos;
    struct free_chunk *tmp;

    list_for_each(pos, &mem_arr[index].list)
    {
        tmp = list_entry(pos, struct free_chunk, list);
        if(tmp->dir == 1)  //找到了第一塊被佔用的記憶體
        {
            printf("find an occupy block,addr = %u\n", tmp->addr);
            recursive_free(tmp, index);
            return 0;
        }
    }

    //如果執行到這裡,那麼說明對應的記憶體塊連結串列沒有佔用記憶體塊
    printf("the %d list has no occupy mem block\n", index);
    return -1;
}
</pre><pre name="code" class="cpp">主函式:
#include "mem_manage.h"

int main()
{
    mem_init();  //初始化記憶體

    /*
        到這了為止我們的記憶體分配及連結串列的連結工作就已經做完了,下面就是在應用層模擬核心的夥伴演算法
        我們怎麼模擬呢,我們以4MB和2MB為實驗材料,因為這兩個消耗的快
        可以想象這麼一種情況:
        1.第一次分配一個2MB,ok,沒有問題,分配給你,然後又分配一個2MB,還是沒有問題
        2.第三次分配2MB就出問題了,2MB的連結串列上已經沒有2MB了,怎麼辦
        3.拆4MB的,把4MB的記憶體卡拆為兩個2MB的,這兩個地址是連續的,並把這個記憶體塊從
          4MB的連結串列刪除,鏈入2MB的連結串列中,我們記這兩個記憶體塊為a和b
        4.這時候如果再分配4MB的,就會提示沒有可分配的記憶體,而2MB的又有了
        5.當我們釋放a時候,沒有什麼發生,但是a釋放後如果再釋放了b,就會把a和b組成新的4MB
          記憶體塊重新鏈入4MB的記憶體連結串列
    */
    char c;
    int size;
    while(1)
    {
        printf("\nmalloc type m, free type f:");
        scanf("%c",&c);

        if(c == 'm')
        {
            printf("\ninput alloc mem size,unit is KB :");
            scanf("%d",&size);
            if(!mem_avail(size))
            {
                continue;
            }

            mem_alloc(size);
        }
        else if(c == 'f')
        {
            printf("\nfree mem size,unit is KB :");
            scanf("%d",&size);
            if(!mem_avail(size))
            {
                continue;
            }

            mem_free(size);
        }
        else
        {
//            printf("\nerr input, again\n");
            continue;
        }
    }
}

看下執行截圖,首先是分配過程,為了快速演示,使用2MB的記憶體塊進行分配。因為它只有兩個塊,分配到第三次時候,就需要拆分4MB的記憶體塊了。

圖中m是輸入malloc的意思



從圖上可以看出,第一次分配2048KB(2MB)時候,直接從連結串列9上分配成功,第二次分配,還是從連結串列9分配成功,但是到第三次,由於連結串列9上只有兩個2MB的記憶體塊,故在第一次查詢連結串列9的時候,已經沒有空閒的記憶體塊了,然後就去4MB的連結串列上查詢,發現有空閒的4MB記憶體塊,所以就把這個記憶體塊拆分為兩個2MB的記憶體塊,然後加到連結串列9上,模擬了夥伴演算法的分配過程。

再來看釋放過程。在釋放之前,又進行了一次分配,把上面連結串列9剛得到的2MB記憶體塊都佔用,然後開始釋放2MB的記憶體塊。如下圖



首先第一次釋放2MB記憶體塊時候,因為已經沒有空閒的記憶體塊了,所以肯定不會找到“夥伴”,第二次釋放時候,就和第一次釋放的空閒記憶體塊組成“夥伴”,所以會列印找到一個夥伴,然後合成4MB的記憶體塊,所以連結串列9會失去兩個2MB的記憶體塊,而連結串列10會增加一個4MB的記憶體塊。然後再從連結串列10上查詢有沒有剛剛合成的4MB記憶體塊的“夥伴”,沒有找到,所以列印最後一句沒有找到“夥伴”。模擬了夥伴演算法的釋放過程。