内存管理是FreeRTOS非常重要的一项功能,前面章节所讲述的任务、消息队列、信号量、事件标志组及软件定时器等在创建时都有两种方法:一种是动态内存分配方法;另种是由用户指定内存的静态方法。使用动态内存分配方法所需要的内存就是从FreeRTOS所管理的内存区域进行分配的。
1 FreeRTOS内存分配方法
FreeRTOS支持5种动态内存管理方法,分别通过文件heap_1、heap_2、heap_3、heap_4
和heap_5实现,在实际使用时,只需使用其中的一种即可。除heap_3动态内存管理之外,FreeRTOS均通过内存堆ucHeap[]来管理内存,内存堆的大小为由宏configTOTAL_HEAP_SIZE所设定的大小。
#if( configAPPLICATION_ALLOCATED_HEAP == 1 )
//如果由用户管理内存,则需要由用户实现内存分配
extern uint8_t ucHeap[ configTOTAL_HEAP_SIZE ];
#else
static uint8_t ucHeap[ configTOTAL_HEAP_SIZE ];
#endif /* configAPPLICATION_ALLOCATED_HEAP */
在程序运行过程中,可用xPortGetFreeHeapSize()函数获取动态剩余内存堆大小。
1.1 heap_1.c动态内存管理方法
heap_1.c 动态内存管理方法是5种动态内存管理方法中最简单的一种,采用这种方法,动态内存一旦申请就不允许释放。尽管如此,这种方法还是能满足大部分嵌入式应用的要求。这种嵌入式应用在系统启动阶段就完成了任务创建,以及消息队列、信号量、事件标志组和软件定时器的创建,而且这些资源在系统运行中是一直要使用的,所以也就不需要删除以进行内存释放。
heap_1动态内存管理方法有如下特性。
(1)项目应用不需要删除任务、信号量、消息队列等已经创建好的资源。
(2)具有时间确定性,即申请动态内存的时间是固定的,并且不会产生内存碎片。
(3)代码实现和内存分配过程非常简单。
1.2 heap_2.c动态内存管理方法
与heap_1动态内存管理方法不同,heap2动态内存管理方法利用了最适应算法,并且支持内存释放。但是heap_2动态内存管理方法不支持内存碎片整理,随着内存的不断申请和释放,会出现大量的内存碎片——小块碎片化的内存,最后可能导致无内存可用。heap_2动态内存管理方法有如下特性。
(1)在不考虑内存碎片的情况下,heap_2.c动态内存管理方法支持重复的任务、信号量、事件标志组、软件定时器等内部资源的创建和删除。
(2)如果用户申请和释放的动态内存大小是随机、可变的,则不建议采用heap_2.c动态内存管理方法,因为采用这种方法容易产生内存碎片。
(3)如果用户需要随机创建和删除任务、消息队列、事件标志组、信号量等内部资源,也不建议采用heap_2.c 动态内存管理方法,因为采用这种方法容易产生内存碎片。
(4)不具有时间确定性,但是比C库中的malloc)函数效率高。
1.3 heap_3.c动态内存管理方法
heap_3.c 动态内存管理方法实现的动态内存管理是对编译器提供的malloc()函数和free()函数进行简单封装,额外做了线程保护。
heap_3动态内存管理方法有如下特性。
(1)需要编译器提供malloc()函数和free()函数,内存堆大小不由宏configTOTAL_HEAP_SIZE所决定,而由启动文件设置。STM32微控制器所使用的由启动文件设置内存堆大小所决定。
(2)不具有时间确定性,即申请动态内存的时间不是固定的。
(3)可能会增加FreeRTOS内核的代码量。
1.4 heap_4.c动态内存管理方法
与heap_2动态内存管理方法不同,heap_4动态内存管理方法使用了最优匹配算法,
并且支持内存碎片的回收,能将零散的内存碎片整理为一个大的内存块。
heap_4动态内存管理方法有如下特性。
(1)可以用于需要重复地创建和删除任务、信号量、事件标志组、软件定时器等内部
资源的场合。
(2)即使随机地调用pvPortMalloc()函数和vPortFree()函数,并且每次申请的内存大
小都不同,也不会像heap_2动态内存管理方法一样产生很多内存碎片。
(3)不具有时间确定性,即申请动态内存的时间不是固定的,但是比C库中的malloc()函数效率高。
基于以上特性,本书FreeRTOS移植、配套使用的例子,都使用了heap_4动态内存管理方法。
1.5 heap_5.c动态内存管理方法
heap_5动态内存管理方法使用了和heap_4动态内存管理方法相同的合并算法,内存管理实现也基本相同,但heap_5动态内存管理方法允许内存堆跨越多个不连续的内存段。另外,使用heap_5动态内存管理方法,要先通过vPortDefineHeapRegions()函数进行初始化,也就是说用户在创建任务等操作FreeRTOS 的内部资源前,要先调用vPortDefineHeapRegions()函数,否则无法通过 pvPortMalloc()函数申请到动态内存。
heap_5动态内存管理方法有如下特性。
(1)具有与heap_4动态内存管理方法一样的一些特性,如支持碎片回收等。
(2)能跨区域管理内存。
(3)使用稍显复杂。
2 FreeRTOS内存管理示例
本示例改写自任务运行时间信息示例,保存任务运行信息的内存区采用动态内存分配方法进行分配,增加检测FreeRTOS可用内存堆大小功能,其余代码与原示例基本相同。
增加一个按键KEY3,用于为获取到的任务信息动态申请内存空间,原Key1按键和Key2按键功能不变。
2.1 FreeRTOS内存堆
分配60kb的内存用于FreeRTOS任务创建,等消息队列,信号量,时间标志组和软件定时器创建等动态内存分配。
/*堆内存大小:供 FreeRTOS使用的总堆内存大小*/
#define configTOTAL_HEAP_SIZE ( ( size_t ) ( 60 * 1024 ) )
2.2 任务函数
#include "stm32f10x.h" // Device header
#include "appTask.h"
#include "LED.h"
#include "Delay.h"
#include "Serial.h"
#include "Key.h"
static TaskHandle_t Led0TaskHandle = NULL;//任务LED0任务句柄
static TaskHandle_t Led1TaskHandle = NULL;//任务LED1任务句柄
static TaskHandle_t tskInfoTaskHandle = NULL;//任务getTaskInfo任务句柄
/**********************************************************************
函 数 名:Led0Task
形 参:pvParameters 是在创建该任务时传递的参数
返 回 值:无
优 先 级: 3
**********************************************************************/
static void Led0Task(void *pvParameters)
{
uint32_t testArray[128] ; //任务中的局部变量
while(1)
{
GPIO_WriteBit(GPIOA,GPIO_Pin_4,(BitAction)(1 - GPIO_ReadOutputDataBit(GPIOA,GPIO_Pin_4))); //LED0闪烁
vTaskDelay(pdMS_TO_TICKS(500));//每秒闪1次
}
}
/**********************************************************************
函 数 名:Led1Task
形 参:pvParameters 是在创建该任务时传递的参数
返 回 值:无
优 先 级: 2
**********************************************************************/
static void Led1Task(void *pvParameters)
{
uint32_t testArray[128] = {0}; //任务中的局部变量
while(1)
{
GPIO_WriteBit(GPIOA,GPIO_Pin_5,(BitAction)(1 - GPIO_ReadOutputDataBit(GPIOA,GPIO_Pin_5))); //LED1闪烁
vTaskDelay(pdMS_TO_TICKS(250));//每秒闪2次
}
}
/**********************************************************************
函 数 名:getTaskInfo
功能说明:动态内存申请和释放,获取任务状态信息,并通过串口发送
形 参:pvParameters 是在创建该任务时传递的参数
返 回 值:无
优 先 级: 4
**********************************************************************/
void getTaskInfo(void *pvParameters)
{
uint8_t ucKeyValue = 0; //保存键值
uint32_t uHeapSize; //保存剩余内存堆的大小
char *pcTaskINfo = NULL; //指向保存任务状态信息的内存区
while(1)
{
ucKeyValue = Key_getNum();
if(ucKeyValue == 1)
{
if(pcTaskINfo != NULL) //申请了动态内存
{
vTaskList(pcTaskINfo);
printf("任务名 任务状态 优先级 剩余堆栈大小 任务号 \r\n");
printf("%s\r\n",pcTaskINfo);
vPortFree(pcTaskINfo); //释放内存
pcTaskINfo = NULL ;
}else
{
printf("请先通过按键1申请内存! \r\n");
}
}else if(ucKeyValue == 2)
{
if(pcTaskINfo != NULL) //申请了动态内存
{
vTaskGetRunTimeStats(pcTaskINfo);
printf("任务名\t\t运行时间\t\t百分比 \r\n");
printf("%s\r\n",pcTaskINfo);
vPortFree(pcTaskINfo); //释放内存
pcTaskINfo = NULL ;
}else
{
printf("请先通过按键1申请内存! \r\n");
}
}else if(ucKeyValue == 3)
{
if(pcTaskINfo != NULL) //已申请了动态内存
{
vPortFree(pcTaskINfo); //释放内存
pcTaskINfo = NULL;
}
uHeapSize = xPortGetFreeHeapSize(); //获取剩余内存堆大小
printf("Key1申请内存,内存堆剩余%8d字节\r\n",uHeapSize);
pcTaskINfo= pvPortMalloc(1024); //通过heap_4 动态内存管理方法申请内存
if(pcTaskINfo != NULL)
{
uHeapSize = xPortGetFreeHeapSize(); //获取剩余内存堆大小
printf("Key1申请内存,内存堆剩余%8d字节\r\n",uHeapSize);
printf("动态内存地址:%x \r\n\r\n",(uint32_t)pcTaskINfo);
}else
{
printf("动态内存申请失败!\r\n");
}
}
vTaskDelay(pdMS_TO_TICKS(100)); //阻塞100ms
}
}
/**********************************************************************
函 数 名:appStartTask
功能说明:任务开始函数,用于创建其他函数并且开启调度器
形 参:pvParameters 是在创建该任务时传递的参数
返 回 值:无
**********************************************************************/
void appStartTask(void)
{
taskENTER_CRITICAL(); /*进入临界段,关中断*/
xTaskCreate(Led0Task,"Led0Task",256,NULL,3,&Led0TaskHandle);
xTaskCreate(Led1Task,"Led1Task",256,NULL,2,&Led1TaskHandle);
xTaskCreate(getTaskInfo,"getTaskInfo",512,NULL,4,&tskInfoTaskHandle);
taskEXIT_CRITICAL(); /*退出临界段,关中断*/
vTaskStartScheduler();/*开启调度器*/
}
2.3 下载测试
本示例能获取到比较详细的FreeRTOS内存堆大小、任务堆栈、任务运行时间等信息对于系统设计是非常有用的。FreeRTOS 需要多大的内存堆,需要多大的任务堆栈,这些信息很难一开始就计算准确,可以通过本示例的办法,在系统设计、调试阶段测试这些信息,然后逐步调整,注意一定要让内存堆及任务堆栈有足够的余量,以免引起堆栈溢出,造成系统崩溃。
3 总结
FreeRTOS 提供了5种动态内存管理方法。heap_1动态内存管理方法是5种方法中最简单的,但是申请的内存不允许释放。heap_2动态内存管理方法支持动态内存的申请和释放,但是不支持内存碎片的整理。heap_3动态内存管理方法采用编译器自带的malloc()函数和firee()函数进行简单的封装,以支持线程安全,即支持多任务调用。heap_4动态内存管理方法支持动态内存的申请和释放,支持内存碎片整理。heap_5动态内存管理方法在heap_4动态内存管理方法的基础上支持将动态内存设置在不连续的区域上。