前言
经历了两周多吧,总算把Glibc malloc 源码分析的文档啃完了,也算是对于malloc这个东西有深刻了解吧,毕竟自己也写了两版代码,后边还会出一个多线程版本的。就是在这个版本上修改一个支持多线程的,这个算是V2.0了。
说明
前面已经阐述了malloc的分配思想,这里我接合线程池原理和边界标识法,做了这个2.0 版本。
1.最小块的大小所为40B。
2.bins 只建立了40,48,56,64总共4个bin,但是同样支持更大内存分配。
3.分配区只有一个,下一个版本多个分配区,同时开启支持多线程模式。
图解
一图胜千言嘛。
这里大概说明一下,一个主分配区,其中包含一个头结构负责记录一些全局的信息,后边就是bins,free 的分配块会首先放在这里,暂时不给操作系统,以留后用,再后边是我们分配的主题空间。
代码
头文件和宏
这里我们按照8字节对齐,所以每一个大小的最后三个位始终为0,我们就可以利用这三个位来做一些标记,我们设置最后一位为是否使用的标记位,倒数第二位为是否从heap分配的空间,如果是就置为1,不是就为0。
#include<stdio.h>
#include<sys/mman.h>
#include<sys/types.h>
#include<unistd.h>
#include<string.h>
#include<fcntl.h>
#define HAFSIZE (1<<2)
#define SIZE (1<<3)
#define DSIZE (1<<4)
#define MAX_CHUNK 64
#define MIN_CHUNK (32 +8)
#define ALIGN 8
#define TEMP_MEM (1<<21) //2MB
#define SIZE_BITS 0x3
#define GET_SIZE(chunk) ((((mem_chunk *)(chunk))->size_tag)&=(~0x3))
//获取一个块的大小
//处理这个块的使用状态
#define GET_state(chunk) ((((mem_chunk *)(chunk))->size_tag)&(0x01))
#define SET_use(chunk) ((((mem_chunk *)(chunk))->size_tag)|=(0x01))
#define SET_free(chunk) ((((mem_chunk *)(chunk))->size_tag)&=(~0x01))
//处理是否被heap 分配
#define GET_MAP(chunk) ((((mem_chunk *)(chunk))->size_tag)&(0x02) >> 1)
#define SET_MAP(chunk) ((((mem_chunk *)(chunk))->size_tag)|=(0x02))
#define SET_UMAP(chunk) ((((mem_chunk *)(chunk))->size_tag)&=(~0x02))
//通过大小算出bins 的下标。
#define SIZE_GET_INDEX(size) (size == 40? 0 :size == 48? 1:size == 56?2:3)
//按照8字节对齐
#define ALIGN_SIZE(size) (((size + ALIGN -1)/(ALIGN))*(ALIGN))
#define PREV(list) (list->prev)
#define NEXT(list) (list->next)
#define CHUNK_MEM(chunk) ((void *)(((char *)(chunk)) + 24))
#define MEM_CHUNK(chunk) ((void *)(((char *)(chunk)) - 24))
//关于链表的一些操作,很简单
#define LIST_init(list){ \
(list)->next = (list)->prev = (list);\
}
#define INSERT(list,node){ \
(node)->next = (list)->next; \
(list)->next->prev = (node); \
(node)->prev = (list); \
(list)->next = (node); \
}
#define delete(list){ \
(list)->prev->next = (list)->next; \
(list)->next->prev = (list)->prev; \
}
#define for_each(list,pos) \
for((pos) = (list)->next ; \
(pos) != (list); \
(pos) = pos->next \
)
数据结构
static int GLOAB_INIT = 0; //标识是否初始化
typedef struct mem_pool{ //sizeof 32B
char *mm_pool; //指向mm_pool 的尾部
struct mem_map* mm_map; //指向map 的尾部
char *tail_pool; //mem_pool 的尾部
struct mem_chunk *chunk_start;
}mem_pool;
typedef struct mem_chunk{ //sizeof 32B
int8_t size_tag;
struct mem_chunk *prev;
struct mem_chunk *next;
char *user;
}mem_chunk;
typedef struct mem_map_index{
int8_t num_chunk;
mem_chunk *first;
}mem_maps;
typedef struct mem_map{ //sizeof 16B
mem_maps list[1];
//这里主要是为了动态扩充数组结构的实现,本来是可以写[0]的,但是为了编译的通用性还是写成[1]比较好。
}mem_map;
static char *MEM_POOL; //分配内存开始的地址
static mem_pool * head; //头控制结构
static mem_map * mem_bitmap; //bins 的位图
static char *MEM_POOL_TAIR ; //指向内存池的尾
static char *sub_heap ; //堆内存
static char *chunk_pad ; //下面这两个都是为了分配内存块的两个变量
static char *chunk_get ;
初始化模块
void mem_pool_init(){
int dev_zero = open("/dev/zero",O_RDWR);
MEM_POOL = mmap((void *)0x800000000,TEMP_MEM,PROT_WRITE,MAP_PRIVATE,dev_zero,0);
//照例使用mmap从内存映射一片内存
if(!MEM_POOL){
printf("mmap is error\n");
return ;
}
head = (mem_pool *)MEM_POOL; //设置控制头结构
head->mm_pool = MEM_POOL;
head->mm_map = ((mem_map *)((char *)MEM_POOL + 4*SIZE));
head->chunk_start = (mem_chunk *)(&(head->mm_map->list[HAFSIZE]));
chunk_pad = (char *)(head->chunk_start);
head->chunk_start = (mem_chunk *)((char *)(&(head->mm_map->list[HAFSIZE]))+ HAFSIZE*sizeof(mem_chunk));
sub_heap = (char*)(head->chunk_start);
chunk_pad = (char *)(head->chunk_start);
chunk_get = (char *)(head->chunk_start);
mem_bitmap = head->mm_map;
MEM_POOL_TAIR = (((char *)MEM_POOL) + TEMP_MEM);
GLOAB_INIT = 1; //全局标识只在这里初始化一次
//以上就是初始化一些全局变量
}
初始化内存池的链表结构(bins结构)
void mem_pool_create(){
//这里我的bins只有4个,体现原理
int index = 0;
int8_t size = 40;
char *pos = (char *)chunk_pad;
for(index = 0;index < 4;index++ ){
mem_bitmap->list[index].num_chunk = size;
mem_bitmap->list[index].first = (mem_chunk *)chunk_pad;
mem_bitmap->list[index].first->size_tag = 0;
mem_bitmap->list[index].first->user = NULL;
mem_bitmap->list[index].first->next = mem_bitmap->list[index].first->prev = mem_bitmap->list[index].first;
chunk_pad = ((char *)pos) + 32;
pos = chunk_pad;
printf("the bit %p \n",chunk_pad);
size += 8;
}
}
分配系列函数
void * mem_malloc(size_t size){
int size_n;
if(0 >= size || MEM_POOL == NULL){
printf("init_fail or size is not real :%d \n",__LINE__);
return (void *)-1;
}
//如果数据不合法直接退出
size_n = ALIGN_SIZE(size);
//算出这个块对齐后的大小
if(size > 64){
return (void *)mem_malloc_map(size_n);
//如果大于64调用mmap()给他映射一片内存空间
}else if(size <= 64){
if(GLOAB_INIT == 0){
mem_pool_init();
//判断有没有初始化
}
size_t index ;
index = SIZE_GET_INDEX(size_n);
//算出index
if(mem_bitmap->list[index].first->size_tag == 0){
return (void *)mem_malloc_heap(size_n);
//如果这个index没有分配大小,就从堆空间先分配
}else{
mem_chunk *pos;
pos = mem_bitmap->list[index].first->next;
delete(mem_bitmap->list[index].first->next);
return (void *)((char *)pos + 3*SIZE);
}
}
}
char *mem_malloc_map(size_t size){
//使用mmap()函数从内存中映射一片空间来
//值的注意的是这里每次都从我们初始化堆结构的尾部来分配一片内存
char *new_map;
int dev_zero = open("/dev/zero",O_RDWR);
new_map = mmap((void *)MEM_POOL_TAIR,size+32,PROT_WRITE,MAP_PRIVATE,dev_zero,0);
((mem_chunk *)new_map)->size_tag = size+32;
SET_MAP(new_map);
SET_use(new_map);
//返回用户区域的首地址
return (((char *)new_map) + 8);
}
char *mem_malloc_heap(size_t size){
//如果开始bin中并没有空闲的空间,就从堆中分配空间进来
char *temp;
temp = chunk_get;
((mem_chunk *)chunk_get)->size_tag = size;
SET_use(chunk_get);
SET_UMAP(chunk_get);
sub_heap = (char *)(chunk_get) + 32 + size;
chunk_get = sub_heap;
return (temp + 24);
}
回收
void mem_free(void *temp){
char *pos;
size_t size;
int index;
pos = MEM_CHUNK(temp);
size = GET_SIZE(pos);
if(size >= 65){
munmap((void *)pos,size);
}
index = SIZE_GET_INDEX(size);
SET_free(pos);
INSERT(mem_bitmap->list[index].first,((mem_chunk *)pos));
}
这里的回收函数我处理的比较简单,首先看看它的大小如果大于65这里自然就是mmap()函数分配的,调用munmap()函数进行释放,否则加进我们的bins 以留后用,但是这里应该由一个heap的紧缩操作判断,我没有写,有兴趣的朋友可以尝试下,我说下原理,很简单的。
这里大家应该能看到,释放的内存现在并没有还给操作系统,而是再我们的bins 中,紧缩操作就是找到堆的底部,向上查看,如果底层上边的块是空闲的并且他们的大小之和到达某一个阀值就使用munmap()函数来释放这片内存,不然会发生“内存暴增“的问题。
版权声明:本文为博主原创文章,未经博主允许不得转载。