本文介绍内存管理的基础知识,详细源码分析见《 FreeRTOS高级篇7---FreeRTOS内存管理分析》
FreeRTOS提供了几个内存堆管理方案,有复杂的也有简单的。其中最简单的管理策略也能满足很多应用的要求,比如对安全要求高的应用,这些应用根本不允许动态内存分配的。
FreeRTOS也允许你自己实现内存堆管理,甚至允许你同时使用两种内存堆管理方案。同时实现两种内存堆允许任务堆栈和其它RTOS对象放置到快速的内部RAM,应用数据放置到低速的外部RAM。
每当创建任务、队列、互斥量、软件定时器、信号量或事件组时,RTOS内核会为它们分配RAM。标准函数库中的malloc()和free()函数有些时候能够用于完成这个任务,但是:
因此,提供一个替代的内存分配方案通常是必要的。
嵌入式/实时系统具有千差万别的RAM和时间要求,因此一个RAM内存分配算法可能仅属于一个应用的子集。
为了避免这个问题,FreeRTOS在移植层保留内存分配API函数。移植层在RTOS核心代码源文件之外(不属于核心源代码),这使得不同的应用程序可以提供适合自己的应用实现。当RTOS内核需要RAM时,调用pvPortMallo()函数来代替malloc()函数。当RAM要被释放时,调用vPortFree()函数来代替free()函数。
FreeRTOS下载包中提供5种简单的内存分配实现,本文稍后会进行描述。用户可以适当的选择其中的一个,也可以自己设计内存分配策略。
FreeRTOS提供的内存分配方案分别位于不同的源文件(heap_1.c、heap_2.c、heap_3.c、heap_4.c、heap_5.c)之中,源文件位于下载包\FreeRTOS\Source\portable\MemMang文件夹中。其它实现方法可以根据需要增加。如果要使用FreeRTOS提供的内存堆分配方案,选中的源文件必须被正确的包含到工程文件中。
这是所有实现中最简单的一个。一旦分配内存之后,它甚至不允许释放分配的内存。尽管这样,heap_1.c还是适用于大部分嵌入式应用程序。这是因为大多数深度嵌入式(deeplyembedded)应用只是在系统启动时创建所有任务、队列、信号量等,并且直到程序结束都会一直使用它们,永远不需要删除。
当需要分配RAM时,这个内存分配方案只是简单的将一个大数组细分出一个子集来。大数组的容量大小通过FreeRTOSConfig.h文件中的configTOTAL_HEAP_SIZE宏来设置。
API函数xPortGetFreeHeapSize()返回未分配的堆栈空间总大小,可以通过这个函数返回值对configTOTAL_HEAP_SIZE进行合理的设置。
heap_1功能简介:
和方案1不同,这个方案使用一个最佳匹配算法,它允许释放之前分配的内存块。它不会把相邻的空闲块合成一个更大的块(换句话说,这会造成内存碎片)。
有效的堆栈空间大小由位于FreeRTOSConfig.h文件中的configTOTAL_HEAP_SIZE宏来定义。
API函数xPortGetFreeHeapSize()返回剩下的未分配堆栈空间的大小(可用于优化设置configTOTAL_HEAP_SIZE宏的值),但是不能提供未分配内存的碎片细节信息。
heap_2功能简介:
heap_2.c适用于需要动态创建任务的大多数小型实时系统(smallreal time)。
heap_3.c简单的包装了标准库中的malloc()和free()函数,包装后的malloc()和free()函数具备线程保护。
heap_3.c功能简介:
注:使用heap_3时,FreeRTOSConfig.h文件中的configTOTAL_HEAP_SIZE宏定义没有作用。
这个方案使用一个最佳匹配算法,但不像方案2那样。它会将相邻的空闲内存块合并成一个更大的块(包含一个合并算法)。
有效的堆栈空间大小由位于FreeRTOSConfig.h文件中的configTOTAL_HEAP_SIZE来定义。
API函数xPortGetFreeHeapSize()返回剩下的未分配堆栈空间的大小(可用于优化设置configTOTAL_HEAP_SIZE宏的值),但是不能提供未分配内存的碎片细节信息。
heap_4.c功能简介:
heap_4.c还特别适用于移植层代码,可以直接使用pvPortMalloc()和 vPortFree()函数来分配和释放内存。
这个方案同样实现了heap_4.c中的合并算法,并且允许堆栈跨越多个非连续的内存区。
Heap_5通过调用vPortDefineHeapRegions()函数实现初始化,在该函数执行完成前不允许使用内存分配和释放。创建RTOS对象(任务、队列、信号量等等)会隐含的调用pvPortMalloc(),因此必须注意:使用heap_5创建任何对象前,要先执行vPortDefineHeapRegions()函数。
vPortDefineHeapRegions()函数只需要单个参数。该参数是一个HeapRegion_t结构体类型数组。HeapRegion_t在portable.h中定义,如下所示:
typedef struct HeapRegion
{
/* 用于内存堆的内存块起始地址*/
uint8_t *pucStartAddress;
/* 内存块大小 */
size_t xSizeInBytes;
} HeapRegion_t;
这个数组必须使用一个NULL指针和0字节元素作为结束,起始地址必须从小到大排列。下面的代码段提供一个例子。MSVCWin32模拟器演示例程使用了heap_5,因此可以当做一个参考例程。
/* 在内存中为内存堆分配两个内存块.第一个内存块0x10000字节,起始地址为0x80000000,
第二个内存块0xa0000字节,起始地址为0x90000000.起始地址为0x80000000的内存块的
起始地址更低,因此放到了数组的第一个位置.*/
const HeapRegion_t xHeapRegions[] =
{
{ ( uint8_t * ) 0x80000000UL, 0x10000 },
{ ( uint8_t * ) 0x90000000UL, 0xa0000 },
{ NULL, 0 } /* 数组结尾. */
};
/* 向函数vPortDefineHeapRegions()传递数组参数. */
vPortDefineHeapRegions( xHeapRegions );