malloc从原理到实践

简介

使用过c语言的都知道malloc是一个动态分配内存的函数
malloc的全称是memory allocation,中文叫动态内存分配,用于申请一块连续的指定大小的内存块区域以void*类型返回分配的内存区域地址

  • malloc分配的内存大小至少为size参数所指定的字节数
  • malloc的返回值是一个指针,指向一段可用内存的起始地址

使用示例

char *ptr = (char *)malloc(10);  //分配10个字节的内存空间,用来存放字符

数据结构

  • arena
    • mem_block_desc-内存块描述符指针
      • block_size,规格
      • block_per_arena,容量
      • free_list
    • 由free_list指向的内存块数组(内存池区域)
arena结构.png

arena是个提供内存分配的数据结构,它分为两部分,一部分是元信息,用来描述自己内存池中空闲内存块的数量,这其中包括内存块描述符指针,通过它可以间接获知本arena所包含内存块的规格大小,此部分占用的内存空间是固定的,约为12字节。另一部分就是内存池区域,这里面有无数的内存块,此部分占用arena大量的空间。我们把每个内存块命名为mem_block,他们是内存分配粒度更细的资源。

内存块描述符代表内存分配的规则,从16字节,到32,64,,,1024字节共7种规格。结构上的区别就是block_size不同,free_list中指向的内存块规则不同。

malloc函数实际上就是通过arena申请这些内存块。arena是个内存仓库,并不直接对外提供内存分配,只有内存块描述符才对外提供内存块,内存块描述符将同类的空闲内存块汇聚到一起,作为某一规格内存块的分配入口。因此,内存块描述符与arena是一对多的关系,每个arena都要与唯一的内存块描述符关联起来,多个同一规格的arena为同一规格的内存块描述符供应内存块,他们各自元信息中用内存块描述符指针指向同一个内存块描述符。

arena.png

结构定义

memory.h

/*内存块*/
struct mem_block{
    struct list_elem free_elem;
}
/*内存块描述符*/
struct mem_block_desc{
    //内存块大小
    unit32_t block_size;
    //本arena中容纳此mem_block的数量
    unit32_t block_per_arena;
    //目前可用的mem_block链表
    struct list free_list;
}

#define DESC_CNT 7

memory.c

struc arena{
    //此arena关联的mem_block_desc
    struct mem_block_desc* desc;
    //large为true时,cnt表示的是页框数量。否则cnt表示空闲mem_block数量
    unit32_t cnt;
    bool large;
}


//内核内存块描述符数组
struct mem_block_desc k_block_desc[DESC_CNT];
//生成内核内存池和用户内存池
struct pool kernel_pool,user_pool;
//此结构用来给内核分配虚拟地址
struct virtual_addr kernal_vaddr;
/*为malloc做准备*/
void block_desc_init(struct mem_block_desc* desc_array){
    uint16_t desc_idx, block_size = 16;
    //初始化每个mem_block_desc描述符
    for(desc_idx = 0; desc_idx < DESC_CNT; desc_idx++){
        desc_array[desc_idx].block_size = block_size;
        //减去arena大小再向下整除
        desc_array[desc_idx].block_per_arena = (PG_SIZE - sizeof(struct arena)) / block_size;
        list_init(&desc_array[desc_idx].free_list);
        block_size * = 2;
    }
}

void mem_init(){
    put_str("mem_init start\n");
    unit32_t mem_byte_total = (*(unit32_t*)(0xb00));
    //初始化内存池
    mem_pool_init(mem_byte_total);
    //初始化mem_block_desc数组,为malloc做准备
    block_desc_init(k_block_desc);
    put_str("mem_init done\n");

}

PG_SIZE = 4KB 4096

sys_malloc的功能是分配并维护内存资源,动态创建arena以满足对内存块的分配

代码实现

sys_malloc的功能是分配并维护内存资源,动态创建arena以满足对内存块的分配

  1. 根据是内核线程调用和用户线程调用判断应该使用哪个内存池
  2. 加锁
  3. 分配的内存大于1024,直接进行内存分配,初始化arena,不需要用到内存块描述符,最后解锁
  4. 小于等于1024
    1. 先找到应该使用哪种规格的内存块描述符
    2. free_list是否为空,如果为空,分配1页框4096作为arena,
    3. 并将arena中的内存池拆解到free_list中。
    4. 到这一步,free_list肯定不为空了,从free_list中取出一个mem_block
    5. 解锁
void sys_malloc(unit32_t size){
    enum pool_flags PF;
    struct pool* mem_pool;
    unit32_t pool_size;
    struct mem_block_desc* descs;
    struct task_struct* cur_thread = running_thread();
    
    /*根据申请者判断用哪个线程池*/
    if(cur_thread->pgdir == NULL){
        PF = PF_KENEL;
        pool_size = kernel_pool.pool_size;
        mem_pool = &kernel_pool;
        descs = k_block_descs;
    }else{
        PF = PF_USER;
        pool_size = user_pool.pool_size;
        mem_pool = &user_pool;
        descs = cur_thread->u_block_desc;
    }
    //若申请的内存不在内存容量范围内,则直接返回null
    if(!(size > 0 && size < pool_size)){
        return null;
    }
    
    struct arena* a;
    struct mem_block* b;
    lock_acquire(&mem_pool->lock);
    //超过1024,就分配页框
    if(size > 1024){
        //向上取整需要的页框数
        unit32_t page_cnt = DIV_ROUND_UP(size + sizeof(struc arena), PG_SIZE);
        //从堆中创建arena
        a = malloc_page(PF, page_cnt);
        if(a != NULL){
            //将分配的内存清零
            memset(a, 0, page_cnt * PG_SIZE);
            //对于分配的大块页框,将desc置为null,cnt置为页框数,large置为true
            a->desc = NULL;
            a->cnt = page_cnt;
            a->large = true;
            lock_release(&mem_pool->lock);
            //跨过arena大小,把剩下的内存返回
            //arena中可被用户使用的是内存池部分,需要跨过arean元信息部分
            return (void*)(a+1);
        }else{
            lock_release(&mem_pool->lock);
            return NULL;
        }
    }else{
        //若申请的内存小于等于1024,可在各种规格的mem_block_desc中适配
        //如申请内存120,那128规格的就是合适的
        uint8_t desc_idx;
        for(desc_idx = 0; desc_idx < DESC_CNT; desc_idx++){
            if(size <= descs[desc_idx].block_size){
                //从小往大找,找到后退出
                break;
            }
        }
        //若是mem_block_desc的free_list中已经没有可用的mem_block,就创建新的arena提供mem_block
        //如果为空,说明供货商arena已经被分配光了
        if(list_empty(&descs[desc_idx].free_list)){
            //分配1页框作为arena
            a = malloc_page(PF, 1);
            if(a == NULL){
                lock_release(&mem_pool->lock);
                return NULL;
            }
            memset(a, 0, PG_SIZE);
            //对于分配的小块内存,将desc置为相应内存块描述符
            a->desc = &descs[desc_idx];
            //cnt置为此arena可用的内存块数量,large置为false
            a->large = false;
            //前面初始化计算好的值,本arena中可容纳规格为block_size的内存块数量
            a->cnt = descs[desc_idx].blocks_per_arena;
            
            uint32_t block_idx;
            enum intr_status old_status = intr_disable();
            //开始将arena拆分为内存块,并添加到内存块描述符的free_list中。
            for(block_idx=0; block_idx<descs[desc_idx].blocks_per_arena;block_idx++){
                //返回arena中第idx个内存块的首地址
                b = arena2block(a, block_idx);
                ASSERT(!elem_find(&a->desc->free_list, &b->free_elem));
                //添加到内存块描述符的free_list中
                list_append(&a->desc->free_list, &b->free_elem);
                //画图
            }
            intr_set_status(old_status);
        }
        //将内存块mem_block中list_elem的地址转换为mem_block的地址。
        b = elem2entry(struct mem_block, free_elem, list_pop(&(descs[desc_idx].free_list)));
        memset(b, 0, descs[desc_idx].block_size);
        a = block2arena(b);
        a->cnt--;
        lock_release(&mem_pool->lock);
        return (void*)b;
    }
}
/*在arean指针指向的页框中,跳过元信息部分,即struct arena的大小,再用idx乘以该arena中内存块大小,最终的地址便是arena中第idx个内存块的首地址,将其转换为mem_block类型后返回*/
static struct mem_block* arena2block(struct arena* a, uint32+t idx){
    return (struct mem_block*)((uint32_t)a + sizeof(struct arena) + idx * a->desc->block_size);
}
//用于将7中规格的内存块转为内存块所在的arena
//内存块的高20位地址便是arena所在的地址
//0x12aab000
//0x12aabfff
static struct arena* block2arena(struct mem_block* b){
    return (struct arena*)((unit32_t)b & 0xfffff000);
}

可能有用户线程或者内核线程调用此函数

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,544评论 6 501
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,430评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,764评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,193评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,216评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,182评论 1 299
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,063评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,917评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,329评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,543评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,722评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,425评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,019评论 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,671评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,825评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,729评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,614评论 2 353

推荐阅读更多精彩内容