目录
本章讲解RT_Thread Nano实时操作系统的动态内存(内存堆)部分,RT-Thread提供三种动态内存管理算法:小堆内存管理算法、堆内存管理方法、SLAB内存管理算法。小堆内存管理模块主要针对系统资源比较少,一般用于小于2M内存空间的系统;堆内存可以应用于多块不连续的内存堆,SLAB内存管理模块则主要是在系统资源比较丰富时,提供了一种近似多内存池管理算法的快速算法。三种内存管理模块在系统运行时只能选择其中之一或者完全不使用动态堆内存管理器。
三种管理模块提供的API接口完全相同,小堆内存管理算法接口在mem.c,堆内存管理方法在memheap.c文件中定义,SLAB内存管理模块在slab.c文件中定义。
内存管理方法 | 适用场景 | 宏定义设置 | 接口定义位置 |
小堆内存 | 小于2M内存空间 | RT_USING_HEAP RT_USING_SMALL_MEM |
mem.c |
堆内存 | 多块不连续的内存堆 | RT_USING_MEMHEAP_AS_HEAP RT_USING_MEMTRACE |
memheap.c |
SLAB | 系统资源比较丰富 | RT_USING_HEAP RT_USING_SLAB |
slab.c |
本章主要讲解静态小堆内存管理算法,其特点是内存利用率高,适用于小存储空间。
本章基于RT_Thread Nano V3.1.5版本分析
1、内存堆控制
1.1 内存堆控制器
内存堆控制器由一些全局变量定义,如下所示:
static rt_uint8_t *heap_ptr; // 内存堆首地址
static struct heap_mem *heap_end; // 内存堆尾部节点
static struct heap_mem *lfree; // 指向最低地址空闲块节点,用于管理空闲内存块
static struct rt_semaphore heap_sem; // 内存信号,用于临界资源权限保护
static rt_size_t mem_size_aligned; // 对齐后小堆内存总大小
static rt_size_t used_mem; // 记录已使用字节数
static rt_size_t max_mem; // 记录已使用字节数的最大值
1.2 内存块节点
内存堆链表节点如下所示,宏定义ARCH_CPU_64BIT表示系统内核为64位。
struct heap_mem
{
rt_uint16_t magic; /*内存块标志字,默认0x1ea0*/
rt_uint16_t used; /*=0表示内存块空闲,=1表示内存块被申请*/
#ifdef ARCH_CPU_64BIT
rt_uint32_t resv;
#endif
rt_size_t next, prev; /*next表示上个内存块节点的相对位置,prev表示下个内存块节点的相对位置*/
/*内存监视使能*/
#ifdef RT_USING_MEMTRACE
#ifdef ARCH_CPU_64BIT
rt_uint8_t thread[8]; /* 记录线程名称*/
#else
rt_uint8_t thread[4]; /* 记录线程名称*/
#endif
#endif
};
1.3 内存堆管理
小堆内存中通过一个双向链表管理内存块,每个内存块首定义一个节点,节点按照地址从小到大顺序双向连接。
内存节点参数next, prev非指针变量,而是表示节点地址相对于堆首地址heap_ptr的偏移,通过偏移定位和连接上下内存块节点位置,例如一内存节点指针struct heap_mem *mem,其下个相邻节点位置为heap_ptr[mem->next],上个相邻节点位置为heap_ptr[mem->prev],同时,可计算出其内存块大小为
size=(&heap_ptr(mem->next)-(char*)mem-sizeof(struct heap_mem));
空闲内存块指针*lfree指向地址最低的空闲内存块,内存申请时,从lfree指向位置开始向高地址遍历所有内存块,直至找到合适的空闲内存块。当内存块释放后,需要重新判断和定位lfree位置。
系统内核为64位时,最小内存堆默认24字节,否则默认12字节。最小长度可调。
#ifdef ARCH_CPU_64BIT
#define MIN_SIZE 24
#else
#define MIN_SIZE 12
#endif
2、内存堆初始化
小内存堆通过全局变量管理,将内存区对齐后,首尾各定义一个内存块节点,堆首内存节点为空闲内存块节点,堆尾节点属性是已使用,将2个节点双向连接,并将空闲内存块指针指向堆首节点。
2.1 初始化接口
/**
* 系统堆初始化.
* @param begin_addr 系统堆起始地址
* @param end_addr 系统堆结束地址
*/
void rt_system_heap_init(void *begin_addr, void *end_addr)
2.2 初始化示例
代码示例:
unsigned char heap[1024*1024];
rt_system_heap_init(heap,heap+sizeof(heap));
内存结构图示:初始化完成后lfree指向首节点,heap_end指向尾部节点,收尾节单向链接。已使用字节数used_mem为0,已使用字节数的最大值max_mem为0。
2.3 源码分析
/**
* 系统堆初始化.
* @param begin_addr 系统堆起始地址
* @param end_addr 系统堆结束地址
*/
void rt_system_heap_init(void *begin_addr, void *end_addr)
{
struct heap_mem *mem;
/*内存堆首尾地址对齐,默认4字节对齐*/
rt_ubase_t begin_align = RT_ALIGN((rt_ubase_t)begin_addr, RT_ALIGN_SIZE);
rt_ubase_t end_align = RT_ALIGN_DOWN((rt_ubase_t)end_addr, RT_ALIGN_SIZE);
RT_DEBUG_NOT_IN_INTERRUPT;
/*内存堆大小检验*/
if ((end_align > (2 * SIZEOF_STRUCT_MEM)) &&((end_align - 2 * SIZEOF_STRUCT_MEM) >= begin_align))
{
/* 有效内存大小 */
mem_size_aligned = end_align - begin_align - 2 * SIZEOF_STRUCT_MEM;
}
else
{
rt_kprintf("mem init, error begin address 0x%x, and end address 0x%x\n",
(rt_ubase_t)begin_addr, (rt_ubase_t)end_addr);
return;
}
/* 内存头 */
heap_ptr = (rt_uint8_t *)begin_align;
/* 调试代码*/
RT_DEBUG_LOG(RT_DEBUG_MEM, ("mem init, heap begin address 0x%x, size %d\n",
(rt_ubase_t)heap_ptr, mem_size_aligned));
/* 初始化内存头节点,next链接尾部节点位置 */
mem = (struct heap_mem *)heap_ptr;
mem->magic = HEAP_MAGIC;
mem->next = mem_size_aligned + SIZEOF_STRUCT_MEM;// 指向内存相对位置(数组下标),非指针
mem->prev = 0;
mem->used = 0;
/*记录线程名称"INIT"*/
#ifdef RT_USING_MEMTRACE
rt_mem_setname(mem, "INIT");
#endif
/*初始化内存堆尾部节点*/
heap_end = (struct heap_mem *)&heap_ptr[mem->next];
heap_end->magic = HEAP_MAGIC;
heap_end->used = 1;
heap_end->next = mem_size_aligned + SIZEOF_STRUCT_MEM;/*与尾部链表连接*/
heap_end->prev = mem_size_aligned + SIZEOF_STRUCT_MEM;
#ifdef RT_USING_MEMTRACE
rt_mem_setname(heap_end, "INIT");
#endif
/*初始化内存权限信号*/
rt_sem_init(&heap_sem, "heap", 1, RT_IPC_FLAG_FIFO);
/* 初始化空闲块指针,指向堆首节点 */
lfree = (struct heap_mem *)heap_ptr;
}
3、内存堆操作
内存堆操作可以分为内存申请、内存伸缩、内存释放。
3.1 内存块申请
3.1.1 相关接口
接口rt_malloc仅申请一定大小的内存,并不进行初始化,所以申请的内存数据值不确定。接口rt_calloc审请内存后,会初始化为0。可以类比于C库的malloc、calloc接口。
/*动态内存申请接口,size--申请内存大小,申请成功后返回内存块首地址,否则返回Null*/
void *rt_malloc(rt_size_t size)
/*动态内存申请接口,count--申请内存单元个数,size--单个单元大小
内存申请完成后初始化为0,申请成功后返回内存块首地址,否则返回NULL*/
void *rt_calloc(rt_size_t count, rt_size_t size)
3.1.2 原理分析
内存控制块申请,是从最低地址空闲内存块节点lfree指向的位置开始向高地址遍历所有内存块,直至找到满足申请大小的空闲内存块,将该内存块标记为已使用,如果该内存块比较大,从该内存块中切割出所需的内存,剩余部分形成新的内存块,并定义新的内存块节点,插入到内存块双向链表中。
3.1.3 示例分析
unsigned char heap[1024*1024];
// 初始化调用
rt_system_heap_init(heap,heap+sizeof(heap));
// 线程1调用,假设线程名称"Led1"
void Thread1()
{
char *p1=rt_malloc(1200);
}
// 线程2调用,假设线程名称"Led2"
void Thread1()
{
char *p2=rt_malloc(1000);
}
3.1.4 代码分析
/*动态内存申请基础接口*/
void *rt_malloc(rt_size_t size)
{
rt_size_t ptr, ptr2;
struct heap_mem *mem, *mem2;
if (size == 0) return RT_NULL;
RT_DEBUG_NOT_IN_INTERRUPT;
/*调试代码,长度是否对齐判断, */
if (size != RT_ALIGN(size, RT_ALIGN_SIZE))
RT_DEBUG_LOG(RT_DEBUG_MEM, ("malloc size %d, but align to %d\n",
size, RT_ALIGN(size, RT_ALIGN_SIZE)));
else
RT_DEBUG_LOG(RT_DEBUG_MEM, ("malloc size %d\n", size));
/*长度对齐,默认4字节 */
size = RT_ALIGN(size, RT_ALIGN_SIZE);
/*分配长度size超过可用内存长度,返回失败*/
if (size > mem_size_aligned)
{
RT_DEBUG_LOG(RT_DEBUG_MEM, ("no memory\n"));
return RT_NULL;
}
/* 数据块长度容错 */
if (size < MIN_SIZE_ALIGNED)
size = MIN_SIZE_ALIGNED;
/* 获取信号,阻塞方式为永久阻塞 */
rt_sem_take(&heap_sem, RT_WAITING_FOREVER);
/*根据相对位置,遍历空闲链表,即从空闲表头开始遍历,一直找到符合大小的控制块*/
for (ptr = (rt_uint8_t *)lfree - heap_ptr;ptr < mem_size_aligned - size;ptr = ((struct heap_mem *)&heap_ptr[ptr])->next)
{
/*取节点*/
mem = (struct heap_mem *)&heap_ptr[ptr];
/*判断内存块属性,未使用,且大小满足size*/
if ((!mem->used) && (mem->next - (ptr + SIZEOF_STRUCT_MEM)) >= size)
{
/* 判断是否需要分割内存块 */
if (mem->next - (ptr + SIZEOF_STRUCT_MEM) >=(size + SIZEOF_STRUCT_MEM + MIN_SIZE_ALIGNED))
{
/*插入新节点*/
ptr2 = ptr + SIZEOF_STRUCT_MEM + size;
/* 设置新节点 create mem2 struct */
mem2 = (struct heap_mem *)&heap_ptr[ptr2];
mem2->magic = HEAP_MAGIC;
mem2->used = 0;
mem2->next = mem->next;
mem2->prev = ptr;
#ifdef RT_USING_MEMTRACE
/*内存监视使能*/
rt_mem_setname(mem2, " ");
#endif
/*按照地址顺序 插入节点and insert it between mem and mem->next */
mem->next = ptr2;
mem->used = 1;
/**/
if (mem2->next != mem_size_aligned + SIZEOF_STRUCT_MEM)
{
((struct heap_mem *)&heap_ptr[mem2->next])->prev = ptr2;
}
#ifdef RT_MEM_STATS
/*记录当前使用内存和最大使用内存*/
used_mem += (size + SIZEOF_STRUCT_MEM);
if (max_mem < used_mem)
max_mem = used_mem;
#endif
}
/* 不需要分割内存块 */
else
{
/*设置为已使用*/
mem->used = 1;
#ifdef RT_MEM_STATS
/*记录当前使用内存和最大使用内存*/
used_mem += mem->next - ((rt_uint8_t *)mem - heap_ptr);
if (max_mem < used_mem) max_mem = used_mem;
#endif
}
/* 设置新节点标志字 set memory block magic */
mem->magic = HEAP_MAGIC;
#ifdef RT_USING_MEMTRACE
/*内存监视,记录线程名称*/
if (rt_thread_self())
rt_mem_setname(mem, rt_thread_self()->name);
else
rt_mem_setname(mem, "NONE");
#endif
/*lfree指向的节点已被分配,调整空闲链表指针*/
if (mem == lfree)
{
/* 指向下一个未被分配的内存块 */
while (lfree->used && lfree != heap_end)
lfree = (struct heap_mem *)&heap_ptr[lfree->next];
/*断言*/
RT_ASSERT(((lfree == heap_end) || (!lfree->used)));
}
/*释放信号*/
rt_sem_release(&heap_sem);
/*断言*/
RT_ASSERT((rt_ubase_t)mem + SIZEOF_STRUCT_MEM + size <= (rt_ubase_t)heap_end);
RT_ASSERT((rt_ubase_t)((rt_uint8_t *)mem + SIZEOF_STRUCT_MEM) % RT_ALIGN_SIZE == 0);
RT_ASSERT((((rt_ubase_t)mem) & (RT_ALIGN_SIZE - 1)) == 0);
/*调试代码*/
RT_DEBUG_LOG(RT_DEBUG_MEM,
("allocate memory at 0x%x, size: %d\n",
(rt_ubase_t)((rt_uint8_t *)mem + SIZEOF_STRUCT_MEM),
(rt_ubase_t)(mem->next - ((rt_uint8_t *)mem - heap_ptr))));
RT_OBJECT_HOOK_CALL(rt_malloc_hook,
(((void *)((rt_uint8_t *)mem + SIZEOF_STRUCT_MEM)), size));
/* 返回块地址 */
return (rt_uint8_t *)mem + SIZEOF_STRUCT_MEM;
}
}
rt_sem_release(&heap_sem);
return RT_NULL;
}
void *rt_calloc(rt_size_t count, rt_size_t size)
{
void *p;
/* 申请内存,大小size */
p = rt_malloc(count * size);
/* 将申请的内存初始化为0 */
if (p)
rt_memset(p, 0, count * size);
return p;
}
3.2 内存块伸缩
3.2.1 相关接口
接口rt_realloc可以将已申请的内存大小进行调整或新申请一块内存。可以类比于C库的realloc接口。
/*动态内存长度伸缩,rmem--内存块首地址,newsize--内存块新长度,申请成功后返回内存块首地址,否则返回NULL*/
void *rt_realloc(void *rmem, rt_size_t newsize)
3.2.2 原理分析
使用rt_realloc分配或调整内存块分以下几种情况,newsize先进行对齐:
(1)newsize对齐后为0,直接释放内存块。
(2)内存块rmem未被分配,直接按照newsize对齐后大小分配内存,等同于rt_malloc(newsize)。
(3)rmem已被分配,当newsize对齐后小于等于原内存块大小时,可以对原内存块进行部分释放:若newsize与原内存块差不足以划分一个新内存块时,维持原内存结构不变。若足以划分一个新内存块时,则划分出新的空闲内存块进行释放。
(4)rmem已被分配,当newsize对齐后大于原内存块大小时,则根据newsize重新申请内存,并将原内存的数据复制到新内存块,并释放原内存块。
3.2.3 示例分析
3.2.4 源码分析
void *void *rt_realloc(void *rmem, rt_size_t newsize)
{
rt_size_t size;
rt_size_t ptr, ptr2;
struct heap_mem *mem, *mem2;
void *nmem;
RT_DEBUG_NOT_IN_INTERRUPT;
/*新尺寸对齐,默认4字节 */
newsize = RT_ALIGN(newsize, RT_ALIGN_SIZE);
/*新尺寸过大,返回失败*/
if (newsize > mem_size_aligned)
{
RT_DEBUG_LOG(RT_DEBUG_MEM, ("realloc: out of memory\n"));
return RT_NULL;
}
/*新尺寸为0,释放内存块*/
else if (newsize == 0)
{
rt_free(rmem);
return RT_NULL;
}
/* rmem 未被分配,直接按照newsize大小分配内存 */
if (rmem == RT_NULL)
return rt_malloc(newsize);
/*获取信号权限*/
rt_sem_take(&heap_sem, RT_WAITING_FOREVER);
/*内存块容错*/
if ((rt_uint8_t *)rmem < (rt_uint8_t *)heap_ptr ||
(rt_uint8_t *)rmem >= (rt_uint8_t *)heap_end)
{
/* 释放信号 */
rt_sem_release(&heap_sem);
return rmem;
}
/*获取当前内存块节点*/
mem = (struct heap_mem *)((rt_uint8_t *)rmem - SIZEOF_STRUCT_MEM);
/*新旧大小相同,直接释放信号后返回原内存块首地址*/
ptr = (rt_uint8_t *)mem - heap_ptr;
size = mem->next - ptr - SIZEOF_STRUCT_MEM;
if (size == newsize)
{
/*释放信号*/
rt_sem_release(&heap_sem);
return rmem;
}
/*新长度小于原长度,并且满足划分新块的条件*/
if (newsize + SIZEOF_STRUCT_MEM + MIN_SIZE < size)
{
/* 划分新内存块 */
#ifdef RT_MEM_STATS
used_mem -= (size - newsize);
#endif
/*设置新内存块节点*/
ptr2 = ptr + SIZEOF_STRUCT_MEM + newsize;
mem2 = (struct heap_mem *)&heap_ptr[ptr2];
mem2->magic = HEAP_MAGIC;
mem2->used = 0;
mem2->next = mem->next;
mem2->prev = ptr;
/*设置线程名称*/
#ifdef RT_USING_MEMTRACE
rt_mem_setname(mem2, " ");
#endif
mem->next = ptr2;
if (mem2->next != mem_size_aligned + SIZEOF_STRUCT_MEM)
{
((struct heap_mem *)&heap_ptr[mem2->next])->prev = ptr2;
}
/*调整空闲内存块指针*/
if (mem2 < lfree)
{
/* the splited struct is now the lowest */
lfree = mem2;
}
/**/
plug_holes(mem2);
/*释放信号*/
rt_sem_release(&heap_sem);
return rmem;
}
/*新长度大于原长度*/
/*释放信号*/
rt_sem_release(&heap_sem);
/* 重新分配内存 */
nmem = rt_malloc(newsize);
if (nmem != RT_NULL) /* check memory */
{
/*原内存区数据复制到新内存区,然后释放原内存*/
rt_memcpy(nmem, rmem, size < newsize ? size : newsize);
rt_free(rmem);
}
return nmem;
}
3.3 内存快释放
3.3.1 相关接口
/** 释放内存块,rmem指向内存块首地址*/
void rt_free(void *rmem)
3.3.2 原理分析
使用rt_free释放内存块:
(1)设置内存块节点为空闲状态。
(2)如果下个相邻内存块为空闲状态,则合并内存块,将下个内存块的节点从链表删除。
(3)如果上个相邻内存块为空闲状态,则合并内存块,将当前内存块的节点从链表删除。
3.3.3 示例分析
3.3.4 源码分析
/** 释放内存块*/
void rt_free(void *rmem)
{
struct heap_mem *mem;
if (rmem == RT_NULL)
return;
RT_DEBUG_NOT_IN_INTERRUPT;
/*断言*/
RT_ASSERT((((rt_ubase_t)rmem) & (RT_ALIGN_SIZE - 1)) == 0);
RT_ASSERT((rt_uint8_t *)rmem >= (rt_uint8_t *)heap_ptr &&
(rt_uint8_t *)rmem < (rt_uint8_t *)heap_end);
/*执行钩子*/
RT_OBJECT_HOOK_CALL(rt_free_hook, (rmem));
/*地址检查*/
if ((rt_uint8_t *)rmem < (rt_uint8_t *)heap_ptr ||(rt_uint8_t *)rmem >= (rt_uint8_t *)heap_end)
{
RT_DEBUG_LOG(RT_DEBUG_MEM, ("illegal memory\n"));
return;
}
/* 获取当前内存块节点*/
mem = (struct heap_mem *)((rt_uint8_t *)rmem-SIZEOF_STRUCT_MEM);
/*调试代码*/
RT_DEBUG_LOG(RT_DEBUG_MEM,
("release memory 0x%x, size: %d\n",
(rt_ubase_t)rmem,
(rt_ubase_t)(mem->next - ((rt_uint8_t *)mem - heap_ptr))));
/* 获取内存信号权限 */
rt_sem_take(&heap_sem, RT_WAITING_FOREVER);
/* 检查内存块节点*/
if (!mem->used || mem->magic != HEAP_MAGIC)
{
rt_kprintf("to free a bad data block:\n");
rt_kprintf("mem: 0x%08x, used flag: %d, magic code: 0x%04x\n", mem, mem->used, mem->magic);
}
/*断言,检查内存块节点*/
RT_ASSERT(mem->used);
RT_ASSERT(mem->magic == HEAP_MAGIC);
/*修改节点(修改为未使用)*/
mem->used = 0;
mem->magic = HEAP_MAGIC;
#ifdef RT_USING_MEMTRACE
/*复归线程名称为空格*/
rt_mem_setname(mem, " ");
#endif
/*调整空闲头部lfree,指向地址最低的空闲控制块*/
if (mem < lfree)
{
lfree = mem;
}
#ifdef RT_MEM_STATS
/*计算已使用内存*/
used_mem -= (mem->next - ((rt_uint8_t *)mem - heap_ptr));
#endif
/* 合并内存块,前后内存块空闲判断*/
plug_holes(mem);
/*释放信号*/
rt_sem_release(&heap_sem);
}
/* 合并内存块,前后内存块空闲判断*/
static void plug_holes(struct heap_mem *mem)
{
struct heap_mem *nmem;
struct heap_mem *pmem;
/*断言*/
RT_ASSERT((rt_uint8_t *)mem >= heap_ptr);
RT_ASSERT((rt_uint8_t *)mem < (rt_uint8_t *)heap_end);
RT_ASSERT(mem->used == 0);
/* 判断下一个内存块,如果是空闲块,则合并 */
nmem = (struct heap_mem *)&heap_ptr[mem->next];
if (mem != nmem &&nmem->used == 0 &&(rt_uint8_t *)nmem != (rt_uint8_t *)heap_end)
{
/*纠正空闲节点lfree,lfree指向地址最低的空闲内存块*/
if (lfree == nmem)
{
lfree = mem;
}
/*删除下一个内存块节点*/
mem->next = nmem->next;
((struct heap_mem *)&heap_ptr[nmem->next])->prev = (rt_uint8_t *)mem - heap_ptr;
}
/* 判断上一个内存块,如果是空闲块,则合并 */
pmem = (struct heap_mem *)&heap_ptr[mem->prev];
if (pmem != mem && pmem->used == 0)
{
/* 纠正空闲节点lfree,lfree指向地址最低的空闲内存块 */
if (lfree == mem)
{
lfree = pmem;
}
/*删除当前节点*/
pmem->next = mem->next;
((struct heap_mem *)&heap_ptr[mem->next])->prev = (rt_uint8_t *)pmem - heap_ptr;
}
}