自己实现堆的动态分配——my_malloc和my_free


前言

  • 为什么需要使用动态内存分配?

在嵌入式应用开发中,常常会遇到RAM空间大小有限的情况,这时需要在使用有限空间的过程中精打细算,比如整块RAM的空间只有100bytes,现在需要划分一段空间去存储来自串口接收的数据,然而接收的这段数据长度是不固定的,范围在[10bytes,70bytes]之间,如果不使用动态内存分配,为了保证接收到所有数据,则会直接划分固定70bytes的空间去存储数据,这样当只收到10bytes的数据时,将会造成60bytes的空间浪费。在使用动态内存分配后,可以使得需要多少空间就划分多少空间。

  • 为什么不使用库里自带的malloc,free函数

C库中实现的malloc,free过于复杂,执行时间相对而言太长,不适合应用在嵌入式系统中。另外,库里的malloc不可重入,在多线程的情况下并不适用,在freertos的heap3.c中,使用库里的malloc函数前后都会挂起任务调度器。

  • 本文中实现的动态内存分配方案具备的特点
    1 实现方案简洁
    2 可以管理多块逻辑上不连续的内存空间
    3 解决多次free后内存碎片化的问题

一、代码浅析

  • 定义
#define CONFIG_TOTAL_HEAP_SIZE      (30 * 1024)
static uint8_t heap_buf[CONFIG_TOTAL_HEAP_SIZE];		// 被管理的空间

typedef struct mem_heap {
    struct mem_heap  * volatile next;          // 指向下一个memory block(data + free)
    volatile uint32_t  data_len;               // length of data block(memory block中被user使用的空间长度)
} mem_heap_t;

typedef struct {
    uint32_t base[MEM_MAX];
    uint32_t end[MEM_MAX];
} mem_env_t;						// MEM_MAX即可供管理的内存空间的数量,base,end分别为对应空间的起始及终止地址
  • 管理方法
/* 将自己定义的大buffer注册,使得用前方的数据结构管理该空间 */
void mem_register(mem_type_t mem_type, __ALIGN4 void *pool, __ALIGN4 uint32_t size)
{
    mem_heap_t *mem_p;

    ASSERT(mem_type < MEM_MAX);
    ASSERT(IS_ALIGN4(pool));
    ASSERT(IS_ALIGN4(size));

    if (pool != NULL) {
        ASSERT(size > sizeof(mem_heap_t));

        mem_p = (mem_heap_t *)pool;
        /* start of list */
        mem_p->next = (mem_heap_t *)((uint32_t)pool + size - sizeof(mem_heap_t));
        mem_p->data_len         = 0U;

        /* end of list */
        mem_p->next->next       = NULL;
        mem_p->next->data_len   = 0U;
    }

    mem_env.base[mem_type] = (uint32_t)pool;
    mem_env.end[mem_type]  = (uint32_t)pool + size;
}

/* 申请内存 */
void *mem_malloc(mem_type_t mem_type, uint32_t wanted_size)
{
    void *p_return = NULL;
    mem_heap_t *p_iterator, *p_new;
    uint32_t free_size, pool;

    ASSERT(mem_type < MEM_MAX);
    pool = mem_env.base[mem_type];
    if (!pool) {
        return NULL;
    }

    p_iterator = (mem_heap_t *)pool;
    /* add header offset, make sure  */
    wanted_size += sizeof(mem_heap_t);
    wanted_size = ALIGN4_HI(wanted_size);

    ENTER_CRITICAL();
    while (1) {
        free_size = (uint32_t)p_iterator->next - (uint32_t)p_iterator - p_iterator->data_len;
        /* check if free size is big enough */
        if (free_size >= wanted_size) {
            break;
        }
        p_iterator = p_iterator->next;
        if (p_iterator->next == NULL) {
            /* failed, we are at the end of the list */
            return NULL;
        }
    }

    if (p_iterator->data_len == 0U) {
        /* no block is allocated, set the length of the first element */
        p_iterator->data_len = wanted_size;
        p_return = (void *)((uint32_t)p_iterator + sizeof(mem_heap_t));
    } else {
        p_new = (mem_heap_t *)((uint32_t)p_iterator + p_iterator->data_len);
        p_new->next = p_iterator->next;
        p_new->data_len = wanted_size;
        p_iterator->next = p_new;
        p_return = (void *)((uint32_t)p_new + sizeof(mem_heap_t));
    }
    EXIT_CRITICAL();

    return p_return;
}

/* 释放内存 */
void mem_free(mem_type_t mem_type, void *mem_addr)
{
    uint32_t pool;
    mem_heap_t *p_iterator, *p_previous, *p_return;

    ASSERT(mem_type < MEM_MAX);
    ASSERT(((uint32_t)mem_addr > mem_env.base[mem_type]) && ((uint32_t)mem_addr < mem_env.end[mem_type]));

    pool = mem_env.base[mem_type];
    ASSERT(pool != 0);

    ENTER_CRITICAL();
    p_return = (mem_heap_t *)((uint32_t)mem_addr - sizeof(mem_heap_t));

    /* find block for mem_addr*/
    p_previous = NULL;
    p_iterator = (mem_heap_t *)pool;
    while (p_iterator != p_return) {
        p_previous = p_iterator;
        p_iterator = p_iterator->next;
        if (p_iterator == NULL) {
            /* invalid mem_addr */
            goto _exit;
        }
    }

    if (p_previous == NULL) {
        /* first block to be released, only set length to 0 */
        p_iterator->data_len = 0U;
    } else {
        /* discard block from chain list */
        p_previous->next = p_iterator->next;
    }

_exit:
    EXIT_CRITICAL();
}

二、示例演示

提示:在以下动图中:

  • 黄色区域表示链表项,类型为mem_heap_t,用于串联前后两个memory block。
  • 绿色区域表示用于存放数据的空间,即用户使用malloc返回的空间
  • 白色区域表示free的空间,即未被使用的区域

接下来通过一部分代码及对应动图直观的体会下。

  • register空间
    mem_register(MEM_GENERAL, heap_buf, CONFIG_TOTAL_HEAP_SIZE);  // 注册自己定义的大数组作为heap,并初始化首位节点

在这里插入图片描述

  • malloc空间
	uint32_t *p1 = mem_malloc(MEM_GENERAL, 5);
    uint32_t *p2 = mem_malloc(MEM_GENERAL, 4);
    uint32_t *p3 = mem_malloc(MEM_GENERAL, 5);

在这里插入图片描述

  • free空间
    mem_free(MEM_GENERAL, p2);
    mem_free(MEM_GENERAL, p1);
    mem_free(MEM_GENERAL, p3);

在这里插入图片描述


总结

本文介绍的内存分配的方法的一个亮点在于,当释放地址时,只需要将该地址的节点去掉就行,相比freertos的实现方法简单了不少。结合前方的代码以及动图演示,各位应该都能比较容易理解,over!

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值