FreeRTOS Heap Memory Management (1) - 内存分配介绍
原文链接:https://www.cnblogs.com/yanpio/p/14817341.html
1 介绍
FreeRTOS
没有直接采用C标准库中提供的malloc()
和 free()
函数进行内存分配,而是提供了5种内存分配方式,对应的源文件名分别为 heap1.c - heap5.c
,位于FreeRTOS/Source/portable/MemMang/
路径下。
不同的内存分配方式都提供了相同的接口函数,其中最重要的是如下两个接口函数:
/* 内存分配函数 */
void* pvPortMalloc( size_t *xWantedSize* );
/* 内存释放函数 */
void vPortFree( void * pv );
2 分配方式的特点
以下仅简单介绍各种分配方式的特点及适用范围,具体的源码分析会在后续的文章中逐个分析。
2.1 heap1
(1)特点:
heap1
只提供了pvPortMalloc()
接口,而没有vPortFree()
(保留了接口,但是调用无效)。也就意味着此种内存分配方式不能释放内存,已分配的空间将一直存在于内存中。
heap1
能够提供的最大内存由FreeRTOSConfig.h
中的宏configTOTAL_HEAP_SIZE
定义。内核预先分配一个尺寸为configTOTAL_HEAP_SIZE
的数组(被称为FreeRTOS heap
),每次调用pvPortMalloc()
接口时,内核都会从数组中拿出一块,直到空闲数组被耗尽。此种分配方式可以避免内存碎片的问题。
(2)适用:
heap1
适用于不需要删除任务的应用。一些重要的应用会禁止使用动态内存分配,因为动态分配会带来不确定性,内存碎片化,甚至会分配失败,这种应用可以考虑使用heap1。
(3)图示:
A为未分配时,内存的布局,B和C是创建一些Task后的内存布局。
2.2 heap2
FreeRTOS
内核手册指出:建议使用heap4
来替代heap2
,因为heap4
在heap2
的基础上做了功能增强(主要是减少了内存碎片的风险)。
(1)特点:
heap2
提供了pvPortMalloc()
和vPortFree()
接口,支持在应用中分配和释放内存。也是内核预先分配一个尺寸为configTOTAL_HEAP_SIZE
的数组,并使用best fit algorithm
(优先分配尺寸与所需尺寸最为接近的free block
)来进行分配。
heap2
可能导致内存碎片(memory fragmentation
)问题。例如,现在有2
个大小分别为 15, 25
的free block
,应用需要10
,则根据best fit algorithm
,将大小为15
的block
分为 10
和 5
,则大小为5
的碎片可能会浪费掉。
(2)适用:
heap2
适用于重复分配和释放大小相同的block
。因为每次分配和回收的尺寸都是相同的,也就避免了内存碎片的问题。
(3)图示:
下图说明了删除和添加任务时,内存的布局变化。
2.3 heap3
heap3
简单封装了C标准库函数malloc()
和 free()
,在调用库函数时挂起了其他任务,防止重入,使pvPortMalloc()
和vPortFree()
线程安全。而heap3
能够提供的最大分配空间由连接器设置决定,不受configTOTAL_HEAP_SIZE
宏值的影响。
此处顺便看一下heap3.c
的源码,由于代码很简单,后面不再单独分析。
void * pvPortMalloc( size_t xWantedSize )
{
void * pvReturn;
vTaskSuspendAll() /* 分配时挂起所有任务,防止函数被重入 */
{
pvReturn = malloc( xWantedSize );
traceMALLOC( pvReturn, xWantedSize );/* 调试使用 */
}
( void ) xTaskResumeAll(); /* 分配后,恢复所有任务 */
#if ( configUSE_MALLOC_FAILED_HOOK == 1 )
{
/* 如果定义了hook函数,则在分配失败时,调用hook函数 */
if( pvReturn == NULL )
{
extern void vApplicationMallocFailedHook( void );
vApplicationMallocFailedHook();
}
}
#endif
return pvReturn;
}
void vPortFree( void * pv )
{
if( pv )
{
/* 与pvPortMalloc()类似,在free时挂起所有任务 */
vTaskSuspendAll();
{
free( pv );
traceFREE( pv, 0 );
}
( void ) xTaskResumeAll();
}
}
2.4 heap4
(1)特点:
heap4
和heap2
的功能基本一致,不同之处在于,heap4
会将相邻的碎片合并起来。
此外,如果需要将FreeRTOS heap
,即前面提到的内核预先分配的数组,分配在固定的内存上,则可以使用以下语法:
/* GCC编译环境, 以下声明可以将数组分配在.my_heap的内存上 */
uint8_t ucHeap[ configTOTAL_HEAP_SIZE ] __attribute__ ( ( section( ".my_heap" ) ) );
(2)适用:
heap4
可以重复分配和释放大小不同的block
(而heap2
仅适合大小相同的block
)。
(3)图示:
下图说明了heap4
在分配时合并碎片的过程。
2.5 heap5
(1)特点:
heap5
使用的分配策略和heap4
完全一样,不同之处在于:heap4
(heap1
和heap2
也是如此)只能在预先分配好的数组中(即供pvPortMalloc()
分配的空间在内存中必须为连续空间)进行分配,而heap5
可以在非连续的空间上进行分配。当然,需要在分配之前显式地调用vPortDefineHeapRegions()
函数来进行内存初始化,主要是将各个分离的空间接合起来。
(2)适用:
适用于内存不连续的场景。
(3)图示:
heap5
可以在下图所示的非连续空间上进行内存分配。具体的操作细节将在后续的源码分析中进行详细说明。
3 其他接口函数
除了上述介绍的pvPortMalloc()
和vPortFree()
函数,FreeRTOS
还提供了如下几个相关的接口函数。
/* 获取空闲内存大小 */
size_t xPortGetFreeHeapSize( void );
/* 获取到目前最小剩余空间,可以用来了解任务实际使用内存的情况,以对内存分配的大小进行调节 */
size_t xPortGetMinimumEverFreeHeapSize( void );
/* 内存分配失败钩子函数,可以在内部分配失败时进行一些操作,如打印信息等 */
void vApplicationMallocFailedHook( void );