關於夥伴算法的學習

學習地址:

https://blog.csdn.net/jy1075518049/article/details/43911183

本文學習自:

https://blog.csdn.net/jy1075518049/article/details/43911183

並不是我自己的原創

首先上代碼:

list.h

//
// Created by zhanglei on 2019/12/27.
//

#ifndef TEST_LIST_H
#define TEST_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

mem_manage.h

#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

#define TRUE 1
#define FALSE 0

#define random(x) (rand()%x+1)

#define cloth2cover(num) ((num & num-1) == 0) ? TRUE : FALSE

typedef unsigned int u32;

struct free_area_head
{
    u32 size;//鏈表內存大小
    u32 num;//相對應的內存塊數
    struct list_head list;
};

struct free_chunk{
    int dir;//標識這塊內存塊是否被使用 1是被使用 0是沒有被使用
    u32 size; //單位是kb
    char* addr;//內存地址
    struct list_head list;
};

enum BUDDY_TYPE
{
    NO_BUDDY = 0,
    LAST_LIST
};

//函數聲明
int mem_init();
int mem_malloc(int size);
int mem_avail(int size);
int mem_free(int size);
#endif

mem_manage.c

#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_malloc(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;
}


/**
 * @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;
}

 

這裏面mem_init做了什麼呢?

我們就是這樣用mem_int初始化完成一個內存矩陣,11個4k ,

 

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

 

就是這樣一個鏈表,我們把所有的總尺寸用malloc申請一個連續的內存地址

pmem = (char *)malloc(total_size);

當我們使用mm_malloc他會把我們申請的尺寸通過get_index,把我們的申請size映射到內存區域中對應的index上,然後循環對應內存吃中的list鏈表,把鏈表上的每一個單元通過list_entry來映射到塊

 

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

當我們在這個區域上發現第一個沒有被使用的塊我們就申請成功了

 

再分析最後一個核心函數

mem_free

還是通過get_index找到尺寸映射的區域,循環鏈表,找到第一個備用的塊

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

然後進行遞歸free,發現一個沒有被使用的大小相同的而且相鄰的塊

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

找到以後在從原來的區域的鏈表裏拿下來,削減計數器

list_del(&block->list);
        list_del(&buddy->list);
        mem_arr[index].num -= 2;  //少了兩塊

最後合併小塊,進入更大的塊

//合併爲新的內存塊併入鏈下一級鏈表
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);
一直循環一直到找不到夥伴或者找到4m纔會結束
​​​​​​​//循環這個過程,在上一級鏈表中查找夥伴,直到找不到夥伴或者到了4MB鏈表
recursive_free(tmp_chunk, index);

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章