自己实现堆的动态分配——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]。 #### 对象及其操作 在C++中,用于动态内存分配的主要工具是`new``delete`运算符。这些运算符分别负责创建上的对象以及销毁它们。通过这种方式,开发者可以在运行时决定所需的数据结构大小生命周期[^1]。 ```cpp int* ptr = new int(10); // 使用new分配单个整数变量 delete ptr; // 删除由ptr指向的对象并释放其占用的空间 ``` 对于更复杂的场景如数组,则有对应的扩展形式: ```cpp double* arrayPtr = new double[5]; // 分配包含五个双精度浮点数的连续存储区 delete[] arrayPtr; // 清理整个数组所占位置 ``` --- ### 实现方式技术细节 不同编程环境提供了多种方法来进行动态内存管理,在标准库层面最常见的是利用`malloc()`/`free()`组合完成基本功能支持;而在高级语言里则封装成了更加易用的形式比如前面提到过的C++关键字。 #### Malloc Free 的工作流程 - **Malloc**: 当请求一定字节数目的可用区块时,它会寻找一块足够大且未使用的区域返回给调用者作为起始地址。如果没有找到合适的片段或者剩余容量不足以满足当前申请量级的要求,则触发错误处理逻辑。 - **Free**: 将先前经由 `malloc`, `calloc`, 或其他关联函数获取到的一片特定范围交还至自由列表以便后续重复利用。注意这里并不意味着立刻回收对应的实际硬件资源而是更新内部状态标记而已[^2]。 另外值得注意的一个特性在于:即使没有显式调用任何清理手段(即从未执行过 free),只要进程本身尚未结束那么之前借助 malloc 所开辟出来的那些部分依旧保持有效直至终止那一刻为止才彻底消失不见。 #### Linux 下的具体实现——Brk/Sbrk 机制 当应用程序尝试获取较小规模(<128KB)的新空间时,Linux 内核倾向于采用调整数据段边界的方式来达成目标。具体而言就是增加 break point 参数从而扩大用户模式下可访问的有效地址区间长度[^3]。 然而这只是表面上的变化罢了,因为直到真正触碰到新近划定界限内的某些位址前都不会涉及到具体的物理帧绑定动作。换句话说也就是所谓的懒加载策略:仅当发生 page fault 后才会促使 OS 完成最终一步即将空闲 RAM 页面映射至此处供进一步读写操作之需[^3]。 以下是简单的伪代码展示如何基于 Brk 构建简易版本的 Heap Manager: ```c void *my_malloc(size_t size){ static char *heap_end; void *result; if(heap_end == NULL){ /* First call */ heap_end = sbrk(0); } result = (void *)heap_end; heap_end += ((size + ALIGNMENT - 1) & ~(ALIGNMENT - 1)); if(sbrk((intptr_t)(heap_end - (char*)result)) == (void*)(-1)){ return NULL; } return result; } ``` 此例展示了最基本的思路框架,实际应用中的情况往往更为复杂还需要考虑诸如碎片整理等问题。 --- ### 总结 综上所述,无论是哪种平台或是何种语法糖衣包裹下的接口本质都是围绕着相似的核心理念展开讨论研究探索实践验证总结归纳提炼升华推广普及教育传承发展创新突破超越极限追求卓越不断进步永不停歇勇往直前百折不挠千锤百炼万古流芳!
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值