學習地址:
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);