前言
- 为什么需要使用动态内存分配?
在嵌入式应用开发中,常常会遇到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!