系列文章目录
rt-thread 之 fal移植
rt-thread 之 生成工程模板
STM32------串口理论篇
rt-thread------串口V1版本(一)配置
rt-thread------串口V1版本(二)发送篇
rt-thread------串口V1版本(三)接收篇
rt-thread内存管理
- 系列文章目录
- 前言
- 1、内存堆的初始化
- 2.内存申请API
- rt_malloc
- rt_realloc
- rt_calloc
- 3.内存释放API
- rt_free
- 4.内存堆的三种策略
- 4.1小内存管理法
- 4.2 slab 管理算法
- 4.3 memheap 管理算法
前言
简述堆和栈
堆(stack):由编译器自动分配释放
栈(heap):一般由程序员分配和释放
1、内存堆的初始化
rt-thread的内存分配是从ZI段结尾处到内存尾部的空间作为内存堆,如下图所示:
rt-thread中是通过下面函数实现的内存的初始化
void rt_system_heap_init(void *begin_addr, void *end_addr);
实际会在rt_hw_board_init()
函数中被调用
rt_system_heap_init((void *)HEAP_BEGIN, (void *)HEAP_END);
看看这个HEAP_BEGIN 和HEAP_END的定义
先看HEAP_END
的定义
#define HEAP_END STM32_SRAM_END
套了另外一个宏STM32_SRAM_END
实际上本质是下面两个宏定义,我的是STM32F103ZET6,根据芯片手册是64K的SRAM,所以结束地址就是芯片内存最后的地址这一点不难理解。
/* Internal SRAM memory size[Kbytes] <8-64>, Default: 64*/
#define STM32_SRAM_SIZE 64
#define STM32_SRAM_END (0x20000000 + STM32_SRAM_SIZE * 1024)
再看看HEAP_BEGIN
的定义
extern int Image$$RW_IRAM1$$ZI$$Limit;
#define HEAP_BEGIN ((void *)&Image$$RW_IRAM1$$ZI$$Limit)
Image$$RW_IRAM1$$ZI$$Limit
这是一个外部变量,但是我通过工程无法找到这个变量,但是我在kei工程的生成的map文件中找到了这个变量
所以我猜这是链接器分配ZI字段结束的地址。
以下只针对ARM_CC编译器
rt_system_heap_init函数展开的细节这里不深究了,如果非要刨根问底的话可以参考这篇文章
当使用memheap内存分配策略时还需要调用,对不连续内存块分别初始化,并且加入 memheap_item 链表。
rt_err_t rt_memheap_init(struct rt_memheap *memheap,
const char *name,
void *start_addr,
rt_uint32_t size)
2.内存申请API
rt_malloc
API
void *rt_malloc(rt_size_t nbytes);
使用方法和逻辑的malloc一样。rt_malloc函数会从系统堆空间中找到合适大小的内存块,若无法申请则返回RT_NULL
所以程序中对此函数的非空判断还是不能省略的。
rt_realloc
API
void *rt_realloc(void *rmem, rt_size_t newsize);
在进行重新分配内存块时,原来的内存块数据保持不变(缩小的情况下,后面的数据被自动截断)。若重新分配的内存块变大,则会在rmem内存后面增加未被初始化的内存块。
rt_calloc
API
void *rt_calloc(rt_size_t count, rt_size_t size);
从内存堆中分配连续内存地址的多个内存块。
3.内存释放API
rt_free
API
void rt_free (void *ptr);
rt_free 函数会把待释放的内存还回给堆管理器中。在调用这个函数时用户需传递待释放的内存块指针,如果是空指针直接返回
4.内存堆的三种策略
无论选择哪个策略,都会可以使用上面的API接口。对内存进行操作。
4.1小内存管理法
初始时,它是一块大的内存。当需要分配内存块时,将从这个大的内存块上分割出相匹配的内存块,然后把分割出来的空闲内存块还回给堆管理系统中。每个内存块都包含一个管理用的数据头,通过这个头把使用块与空闲块用双向链表的方式链接起来。
在rt_free()
时会把used设置成未使用,下次申请内存时会先遍历到没使用的内存块,然后判断申请的内存是否小于分配好的未使用的内存块。若小于直接使用,并且在数据后面将剩余内存生成一个链表头,标记为未使用。若大于则继续遍历,若没有找到合适大小的内存堆,则在最后未分配的内存堆中分配该大小的内存。
这种小内存管理方法若频繁rt_malloc()申请不同大小的内存堆然后再rt_free()必然产生大量内存碎片。再看看rt_free()函数。
/**
* @brief This function will release the previously allocated memory block by
* rt_mem_alloc. The released memory block is taken back to system heap.
*
* @param rmem the address of memory which will be released.
*/
void rt_smem_free(void *rmem)
{
struct rt_small_mem_item *mem;
struct rt_small_mem *small_mem;
if (rmem == RT_NULL)
return;
RT_ASSERT((((rt_ubase_t)rmem) & (RT_ALIGN_SIZE - 1)) == 0);
/* Get the corresponding struct rt_small_mem_item ... */
mem = (struct rt_small_mem_item *)((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 - small_mem->heap_ptr))));
/* ... which has to be in a used state ... */
small_mem = MEM_POOL(mem);
RT_ASSERT(small_mem != RT_NULL);
RT_ASSERT(MEM_ISUSED(mem));
RT_ASSERT(rt_object_get_type(&small_mem->parent.parent) == RT_Object_Class_Memory);
RT_ASSERT(rt_object_is_systemobject(&small_mem->parent.parent));
RT_ASSERT((rt_uint8_t *)rmem >= (rt_uint8_t *)small_mem->heap_ptr &&
(rt_uint8_t *)rmem < (rt_uint8_t *)small_mem->heap_end);
RT_ASSERT(MEM_POOL(&small_mem->heap_ptr[mem->next]) == small_mem);
/* ... and is now unused. */
mem->pool_ptr = MEM_FREED();
#ifdef RT_USING_MEMTRACE
rt_smem_setname(mem, " ");
#endif /* RT_USING_MEMTRACE */
if (mem < small_mem->lfree)
{
/* the newly freed struct is now the lowest */
small_mem->lfree = mem;
}
small_mem->parent.used -= (mem->next - ((rt_uint8_t *)mem - small_mem->heap_ptr));
/* finally, see if prev or next are free also */
plug_holes(small_mem, mem);
}
在rt_free()
的最后有个plug_hoes()
其作用就是检查释放内存的前面一个内存块和后面一个内存块。若未使用则将其合并,生成一个新的内存块。有了内存块合并可以大大减少内存碎片的产生。
4.2 slab 管理算法
RT-Thread 的 slab 分配器是在 DragonFly BSD 创始人 Matthew Dillon 实现的 slab 分配器基础上,针对嵌入式系统优化的内存分配算法。最原始的 slab 算法是 Jeff Bonwick 为 Solaris 操作系统而引入的一种高效内核内存分配算法。
RT-Thread 的 slab 分配器实现主要是去掉了其中的对象构造及析构过程,只保留了纯粹的缓冲型的内存池算法。slab 分配器会根据对象的大小分成多个区(zone),也可以看成每类对象有一个内存池
slab算法堆单片机sram要求比较高,一般>2M的才会使用,详细的算法以及说明可以看官方的手册
4.3 memheap 管理算法
当使用的芯片有两个块SRAM(如STM32F427)或者外挂SRAM时可以使用此种内存管理方法。
rt_err_t rt_memheap_init(struct rt_memheap *memheap,
const char *name,
void *start_addr,
rt_uint32_t size)
如果有多个不连续的 memheap 可以多次调用该函数将其初始化并加入 memheap_item 链表。