FreeRTOS的堆管理
上文对FreeRTOs的目录结构进行了说明,其中提到了FreeRTOSSourceportableMemMang目录下的五个heap_n.c文件,本文将对这个五个文件的作用、差异、使用场景进行对比,以便选择出适合自己项目的堆管理模式。
- FreeRTOS使用pvPortMalloc()来分配内存。
- vPortFree()来释放内存。
Heap_1.c
主要用于小型专一嵌入式系统。内核在任何实时任务执行之前先分配内存,一次分配永久使用并不再改变,可靠性较高。
堆的总容量 configTOTAL_HEAP_SIZE 在 FreeRTOSConfig.h 文件中配置
每创建一个任务都会分配一个堆控制块(TCB:Task control block)和一个栈(Stack)
- A:代表整个可分配空间
- B:当一个任务被创建出来
- C:当三个任务被创建出来
Heap_2.c
- Heap_2 保留的主要目的是向后兼容,不推荐在新项目中使用。可使用Heap_4作为替代。
- Heap_2 采用最佳适配算法,适用于需要频繁创建和删除需要分配固定栈内存的任务。
Heap_3.c
- 在heap3.c中 configTOTAL_HEAP_SIZE的配置将不再生效。
- Heap_3通过暂时挂起FreeRTOS的调度来实现malloc()和free()的线程安全。(待补充)
Heap_4.c
- Heap_4采用首次适应算法来分配内存。heap4将相邻未分配的内存结合成为整个大内存来减少碎片内存。
Heap5.c
- heap_5和heap_4的使用完全一致。
- heap_5可以对任意位置的空间进行分配,
- heap_5在使用之前需要通过vPortDefineHeapRegions()函数进行初始化,之后才可以使用pvPortMalloc()进行内存分配。
- PortDefineHeapRegions()的作用是明确每个分散空间的初始位置和大小。
- 原型描述:
void vPortDefineHeapRegions( const HeapRegion_t * const pxHeapRegions );
- 返回值结构
- 原型描述:
typedef struct HeapRegion
{
/* 内存块的起始地址将成为堆的一部分.*/
uint8_t *pucStartAddress;
/* 堆的容量大小bytes. */
size_t xSizeInBytes;
} HeapRegion_t;
下图表示vPortDefineHeapRegions函数的具体使用场景RAM1,RAM2,RAM3分别代表三个空闲空间
/* 图最左侧堆:A 定以RAM1-3的基本信息. */
#define RAM1_START_ADDRESS ( ( uint8_t * ) 0x00010000 )
#define RAM1_SIZE ( 65 * 1024 )
#define RAM2_START_ADDRESS ( ( uint8_t * ) 0x00020000 )
#define RAM2_SIZE ( 32 * 1024 )
#define RAM3_START_ADDRESS ( ( uint8_t * ) 0x00030000 )
#define RAM3_SIZE ( 32 * 1024 )
const HeapRegion_t xHeapRegions[] =
{
{ RAM1_START_ADDRESS, RAM1_SIZE },
{ RAM2_START_ADDRESS, RAM2_SIZE },
{ RAM3_START_ADDRESS, RAM3_SIZE },
{ NULL, 0 } /* 标志数组的结尾. */
};
int main( void )
{
/* 初始化heap_5 */
vPortDefineHeapRegions( xHeapRegions );
/* 编码区域。*/
}
-
图A仅仅展示了RAM结构,图B 包含了堆分配的一些细节
-
由于RAM的管理需要链接脚本,图B RAM1包含了链接脚本,RAM2,和RAM3为空。RAM1被分为两个区域,0x10000-0x01nnnn用来存放连接脚本,只有0x01nnnn-0x01FFFF可用,即heap5的可用空间为0x01nnnn-0x01FFFF,RAM2,RAM3。此时如果起始位置依然以0x010000作为起点将覆盖存放变量的内存,所以必须从0x0001nnnn作为起点,可以在HeapRegion_t结构中使用xHeapRegions[] 数组作为起始地址,但由于起始地址难判定,后续结构的必要更新,堆重叠的问题此方案并不推荐。
-
完美推荐方案C
* 定以没有被链接器使用的两个起始地址和容量 */
#define RAM2_START_ADDRESS ( ( uint8_t * ) 0x00020000 )
#define RAM2_SIZE ( 32 * 1024 )
#define RAM3_START_ADDRESS ( ( uint8_t * ) 0x00030000 )
#define RAM3_SIZE ( 32 * 1024 )
/* 定义一个数组为heap_5使用的一部分,此数组将会被链接器放置于RAM1 */
#define RAM1_HEAP_SIZE ( 30 * 1024 )
static uint8_t ucHeap[ RAM1_HEAP_SIZE ];
/* 定义一个数组HeapRegion_t,第一个入口只定义了ucHeap数组。像之前一样HeapRegion_t结构定以仍需地址从小到大排列。*/
const HeapRegion_t xHeapRegions[] =
{
{ ucHeap, RAM1_HEAP_SIZE },
{ RAM2_START_ADDRESS, RAM2_SIZE },
{ RAM3_START_ADDRESS, RAM3_SIZE },
{ NULL, 0 } /* 标志数组的结束. */
};
优势
- 初始地址不再是常量
- 链接器自动设置HeapRegion_t结构
- 内存分配给heap_5的数据不会被链接器覆盖
- 如果ucHeap太大应用将不会链接
堆分配相关函数
size_t xPortGetFreeHeapSize( void );
当被调用时返回堆中可用字节,可用于优化堆大小。当时用heap_3分配方案时此函数不生效。size_t xPortGetMinimumEverFreeHeapSize( void );
当调用时返回FreeRTOS应自开始运行从未存在于堆中的最小未分配字节。 可用于了解是否存在堆溢出情况。 **只可用于heap_4,heap_5堆分配方案中。void vApplicationMallocFailedHook( void );
内存分配的情况无处不在,可在应用中直接调用,此外在freeRTOS创建任务,队列,信号量等操作时也会调用pvPortMalloc()函数。当pvPortMalloc()未找到符合大小的RAM空间时返回NULL,此时可调用回调函数vApplicationMallocFailedHook。- 当在FreeRTOSConfig.h配置了configUSE_MALLOC_FAILED_HOOK为1 则表示内存分配出错时必须执行vApplicationMallocFailedHook函数。