STM32 ARM平台动态内存管理的改进

猪小花1号2018-09-05 15:45

作者:刘国建


嵌入式开发中,由于系统的内存有限,系统的堆栈往往需要自己管理,而堆栈,一般分成堆和栈,两者是有区别的:

1.heap是堆,stack是栈。

2.stack的空间由操作系统自动分配和释放,heap的空间是手动申请和释放的,heap常用newmalloc关键字来分配。

3.stack空间有限,heap的空间是很大的自由区,一般会设置为系统的未使用的内存区域。

本文主要介绍stm32 ARM平台上的heap使用策略:


1.1    Heap管理方案

1.1.1    Heap_1.c

是所有方案中最简单的方案。当内存分配后,就不允许释放。适用于程序需要大量数目内存。

算法仅仅将单一阵列分解为RAM需要的小块。阵列整个大小由onfigTOTAL_HEAP_SIZE 定义。

这个方案:

如果应用程序中从不删除任务或队列(不调用vTaskDelete() vQueueDelete()

总是确定的(总是以相同的时间返回代码块)

适用与PIC,AVR8051演示程序——因为这些,在调用vTaskStartScheduler() 后不再不断的创建和删除任务。


heap_1.c 适合于许多小实时系统,在内核开始之前提供了所有任务和队列。

1.1.2    Heap_2.c

不像方案1,允许预先分配要释放的块。然而,它不兼备将邻近的空闲块合并成单一大块。

同样,可用RAM的数量由configTOTAL_HEAP_SIZ定义。

这个方案:

能够适用于应用程序多次调用vTaskCreate()/vTaskDelete()vQueueCreate()/ vQueueDelete()的情况(导致多次调用 pvPortMalloc() and vPortFree()

不适用于内存分配和释放随机大小的情况——删除任务产生了不同大小的堆栈,或 删除不同长度的队列

在不可预知的情况下,应用程序创建队列和任务块,可能产生内存碎片问题。

1.1.3    Heap_3.c

对于标准的malloc() and free()函数来说,这仅仅是个包装。让他们线程安全。

这个方案:

需要连接一个分配的堆,编译器库提供mall0c()free()来完成。

不确定

大幅度增加内核代码占用的空间

适用于PCx86单板机)演示程序

1.1.4    Heap_4.c

这个方案和方案2类似,但是加入了对回收的内存块进行合并,减少碎片的出现。适合单块heap内存管理。

1.1.5    Heap_5.c

这个方案内存的管理和方案4类似,但是加入了多个内存块管理.

多个内存块管理其实和方案4一个内存块分配后变成N个内存链表其实是一个道理。

只是这个方案主要针对了ST部分芯片的内存有SRAM196KB)和SRAM232KB)不连续情况下,初始化heap时候,能支持多个不连续内存。

1.1.6   方案选择

对于一般的嵌入式系统采用方案5,可以管理多块内存,并且可以合并回收的内存,减少内存碎片  

具体的code请看MemMang.rar 


2举个栗子

STM32L476 芯片内存有两块:SRAM1(96KB)和SRAM2(32KB)

图1 MCU内存分配情况

SRAM1开头部分用来R/W区域使用,接着是size为0x400的CSTACK,后面剩余区域为user heap,大小为除了上述的size之后,剩余的size。SRAM2的32KB都用来作为user heap。User heap两块区域分别为user heap1,user heap2。CSTACK给MSP使用。


3内存接口

3.1内部数据结构

1. Heap1heap2定义

/*heap regions define,
Note 0x10000000 is the lower address so appears in the array first.*/
HeapRegion_t xHeapRegions[] =
{
{ ( uint8_t * ) 0x10000000UL, 0x8000 }, /* Defines a block of 0x8000 bytes starting at address 0x10000000, 32KB*/
{ ( uint8_t * ) NULL, 0 }, /*USER_HEAP_INDEX == 1,Defines a block of USER_HEAP_SIZE bytes starting at address of USER_HEAP_BASE*/
{ NULL, 0 } /*Terminates the array.*/
};

第一个块为user heap1,第二块为user heap2USER_HEAP_INDEX  1,这里定义为NULL0,当系统运行时会去动态修改。最后必须填NULL0表示内存块结束。

2.小内存动态内存池定义

如果全部使用方案5,即heap_5.c里,pvPortMallocvPortFree接口管理内存,对于小内存和大内存混合使用,还是会出现中间有些小内存得不到使用情况,造成浪费,为了解决这个问题,又在方案5的基础上加了一层小内存管理,即:

/*Memory Pool, not free in the heap linked list*/
static OsMemPoolT OsMemPoolInfo[MEM_POOL_NUM] =
{
{"MemPool1", NULL, MEM_POOL_1_BLOCK_SIZE, MEM_POOL_1_BLOCK_NUM, MEM_POOL_1_BLOCK_NUM},
{"MemPool2", NULL, MEM_POOL_2_BLOCK_SIZE, MEM_POOL_2_BLOCK_NUM, MEM_POOL_2_BLOCK_NUM},
{"MemPool3", NULL, MEM_POOL_3_BLOCK_SIZE, MEM_POOL_3_BLOCK_NUM, MEM_POOL_3_BLOCK_NUM},
{"MemPool4", NULL, MEM_POOL_4_BLOCK_SIZE, MEM_POOL_4_BLOCK_NUM, MEM_POOL_4_BLOCK_NUM}
};

他们的定义目前如下,后续会去考虑根据实际使用频率来调整各个区块的个数以及每个区块的大小

#define MEM_POOL_1_BLOCK_NUM (16)
#define MEM_POOL_1_BLOCK_SIZE (64)
#define MEM_POOL_2_BLOCK_NUM (16)
#define MEM_POOL_2_BLOCK_SIZE (128)
#define MEM_POOL_3_BLOCK_NUM (16)
#define MEM_POOL_3_BLOCK_SIZE (260)
#define MEM_POOL_4_BLOCK_NUM (6)
#define MEM_POOL_4_BLOCK_SIZE (520)


这些内存,在初始化的时候,就在user heap中申请,并且运行过程中,对于heap内存来说,是不释放的,而是交由cmsis os层统一管理,注意对于用户来说,调用free接口是去操作cmsis os层接口,而不是去heap_5.c接口中去freee。小于等于520字节的,使用小内存申请接口,使用大内存则用pvPortMallocvPortFree接口管理。对于使用者,不用关心这个细节。  

3.2外部调用接口

1.OsMalloc(uint16 Size)

内存申请接口,申请大小为size大小的内存,申请不到为NULL

对于小于等于520个字节的内存申请,调用者不用初始化内存,在这个接口内部已经初始化为0了。

对于大于520个字节的需要调用者自己初始化。

2.OsFree(ptr)

内存释放接口。

调用不用在释放后,去把指针设为NULL,接口内部已经实现过。

3.OsMsgBuffGet

Task msg buff申请接口,默认已经在size里加上OS_MSG_HEADER_SIZE,调用者只关心msg buff内容的大小。具体参考OsMsgT结构。

4.OsMsgBuffFree

Task msg buff释放接口,需要释放的是OsMsgT型指针,这个接口和OsMsgBuffGet只是对OsMallocOsFree接口的二次封装。只是为了msg buf管理更清晰。

5.OsMemorySizeGet

主要debug用,打印OsMemPoolInfo内存的使用情况。

需要打开MEMORY_POOL_DEGUG宏。

6.OsGetFreeHeapSize

xPortGetFreeHeapSize接口的封装,查看user heap free空间。

7.OsGetMinimumEverFreeHeapSize

xPortGetMinimumEverFreeHeapSize接口的封装,查看user heap 历史最小空间情况。

8.Debug_printf

Debug_printf内部采用了MEM_POOL_3_BLOCK_SIZE 大小的内存进行打印,后续可以改动,即打印一帧数据的大小。

#define DEBUG_PRINTF_BUFF_SIZE  MEM_POOL_3_BLOCK_SIZE

注意:

1.OsMalloc之后,必须在适合的位置调用OsFree,否则会造成内存泄露。遇到这个问题,可以使用OsMemorySizeGetOsGetFreeHeapSizedebug,看相应模块的调用前后,内存是否相同。

2.OsGetMinimumEverFreeHeapSize:对于使用过程中内存不够情况debug用。

3.使用者需要考虑调用OsMalloc后内存申请不到,返回值为NULL的情况,否则就会出现操作0地址而死机。


网易云大礼包:https://www.163yun.com/gift

本文来自网易实践者社区,经作者刘国建授权发布