FreeRTOS的堆内存管理(heap_1 ~ heap_5)

FreeRTOS的堆管理

上文对FreeRTOs的目录结构进行了说明,其中提到了FreeRTOS\Source\portable\MemMang目录下的五个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函数。
posted @ 2021-05-24 23:35  ReaIms  阅读(1447)  评论(0编辑  收藏  举报