本节需要掌握以下内容:
1,FreeRTOS内存管理简介(了解)
2,FreeRTOS内存管理算法(熟悉)
3,FreeRTOS内存管理相关API函数介绍(熟悉)
4,FreeRTOS内存管理实验(掌握)
5,课堂总结(掌握)
一、FreeRTOS内存管理管理(了解)
在使用FreeRTOS创建任务、队列、信号量等对象的时候,一般都提供了两种方法:
动态方法创建:自动的从FreeRTOS管理的内存堆中申请创建对象所需的内存,并在对象删除后,可将这块内存释放回FreeRTOS管理的内存堆
静态方法创建:需要用户提供各种内存空间,并且使用静态方式占用的内存一般固定下来了,即使任务、队列被删除后,这些被占用的内存空间一般没有其他途径。
总结:动态方式管理内存相比静态方式更加灵活。
除了FreeRTOS提供的动态内存管理方法,标准的C库也提供了malloc()函数和free()来实现动态的申请和释放内存。
疑问:为啥不用标志的C库自带的内存管理算法?
- 因为标准的C库的动态内存管理方法有如下几个缺点:
- 占用大量的代码空间,不适用在资源紧缺的嵌入式系统中
- 没有线程安全的相关机制
- 运行有不确定性,每次调用这些函数花费的事件可能都不相同
- 内存碎片化
因此FreeRTOS提供了多种动态内存管理的算法,可针对不同的嵌入式系统!
二、FreeRTOS内存管理算法(熟悉)
FreeRTOS提供了5种动态内存管理算法,分别为: heap_1、heap_2、heap_3、heap_4、heap_5 。如下所示:
算法 | 优点 | 缺点 |
heap_1 | 分配简单,时间确定 | 只允许申请内存,不允许释放内存 |
heap_2 | 允许申请和释放内存 | 不能合并相邻的空闲内存块会产生碎片、时间不定 |
heap_3 | 直接调用C库函数malloc()和 free() ,简单 | 速度慢、时间不定 |
heap_4 | 相邻空闲内存可合并,减少内存碎片的产生 | 时间不定 |
heap_5 | 能够管理多个非连续内存区域的 heap_4 | 时间不定 |
本专栏中使用的均为heap_4内存管理算法。
2.1 heap_1内存管理算法
heap_1只实现了pvPortMalloc,没有实现vPortFree;也就是说,它只能申请内存,无法释放内存!
如果你的工程,创建好的任务、队列、信号量等都不需要被删除,那么可以使用heap_1内存管理算法
heap_1的实现最为简单,管理的内存堆是一个数组,在申请内存的时候, heap_1 内存管理算法只是简单地从数组中分出合适大小的内存,内存堆数组的定义如下所示 :
/* 定义一个大数组作为 FreeRTOS 管理的内存堆 */
static uint8_t ucHeap[ configTOTAL_HEAP_SIZE ];
heap_1内存管理算法的分配过程如下图所示:
注意: heap_1内存管理算法,只能申请无法释放!
2.2 heap_2内存管理算法
相比于heap_1内存管理算法,heap_2内存管理算法使用最适应算法,并且支持释放内存;
heap_2内存管理算法并不能将相邻的空闲内存块合并成一个大的空闲内存块;因此 heap_2 内存管理算法不可避免地会产生内存碎片;
最适应算法:
假设heap有三块空闲内存(按内存大小由小到大排序):5字节,25字节,50字节
现在新创建一个任务需要申请20字节的内存
第一步:找出最小大的,能满足pvPortMalloc的内存:25字节
第二步:把它划分为20字节、5字节;返回这20字节的地址,剩下的5字节仍然是空闲状态,留给后续的pvPortMalloc使用
内存碎片化是由于多次申请和释放内存,但是释放的内存无法于相邻的空闲内存合并而产生的
适用场景:频繁的创建和删除任务,且所创建的任务堆栈都相同,这类场景下Heap_2没有碎片化的问题。
2.3 heap_4内存管理算法
heap内存管理算法使用了首次适应算法,也支持内存的申请于释放,并且能够将空闲且相邻的内存进行合并,从而减少内存碎片的现象。
首次适应算法:
- 假设heap由3块空闲内存(按内存地址由低到高排序):5字节、50字节、25字节
- 现在新创建一个任务需要申请20字节的内存
- 第一步:找出一个能满足pvPortMalloc的内存:50字节
- 第二步:把它划分成20字节、30字节;返回这20字节的地址,剩下的30字节依然是空闲状态,留给后续的pvPortMalloc使用
heap_4内存管理算法会把相邻的空闲内存合并成一个更大的空闲内存,这有助于内存的碎片问题。
适用于这种场景:频繁的分配、释放不同大小的内存。
2.4 heap_5内存管理算法
heap_5内存管理算法是在heap_4内存管理算法的基础上实现的,但是heap_5内存管理算法再heap_4内存管理算法的基础上实现了管理多个非连续内存区域的能力
heap_5内存管理算法默认并没有定义内存堆,需要用户手动指定内存区域的信息,对其进行初始化。
问题:怎么指定一块内存?
使用如下结构体:
typedef struct HeapRegion
{
uint8_t * pucStartAddress; /* 内存区域的起始地址 */
size_t xSizeInBytes; /* 内存区域的大小,单位:字节 */
} HeapRegion_t;
怎么指定多块不连续的内存?
Const HeapRegion_t xHeapRegions[] =
{
{ (uint8_t *)0x80000000, 0x10000 }, /* 内存区域 1 */
{ (uint8_t *)0x90000000, 0xA0000 }, /* 内存区域 2 */
{ NULL, 0 } /* 数组终止标志 */
};
vPortDefineHeapRegions(xHeapRegions);
适用场景:在嵌入式系统中,那些内存的地址并不连续的场景。
三、FreeRTOS内存管理相关API函数介绍(熟悉)
函数 | 描述 |
void * pvPortMalloc( size_t xWantedSize ); | 申请内存 |
void vPortFree( void * pv ); | 释放内存 |
size_t xPortGetFreeHeapSize( void ); | 获取当前空闲内存的大小 |
void * pvPortMalloc( size_t xWantedSize ); 申请内存
l xWantedSize :申请的内存大小,以字节为单位;返回值:返回一个指针 ,指向已分配大小的内存。如果申请内存失败,则返回 NULL;
void vPortFree( void * pv ); 释放内存* pv:指针指向一个要释放内存的内存块
size_t xPortGetFreeHeapSize( void ); 获取当前空闲内存的大小
返回值:返回当前剩余的空闲内存大小
四、FreeRTOS内存管理实验(掌握)
4.1、实验目的:
学习 FreeRTOS 内存管理,并观察内存在申请和释放过程中内存大小的变化情况
4.2、实验设计:
将设计三个任务:start_task、task1
两个任务的功能如下:
- start_task:用来创建task1任务
- task:用来按键扫描,当KEY0按下则申请内存,当KEY1按下则释放内存,并打印剩余内存信息
4.3 实验代码
demo.c
/******************************************************************************************************/
/*FreeRTOS配置*//* START_TASK 任务 配置* 包括: 任务句柄 任务优先级 堆栈大小 创建任务*/
#define START_TASK_PRIO 1
#define START_TASK_STACK_SIZE 128
TaskHandle_t start_task_handler;
void start_task( void * pvParameters );/* TASK1 任务 配置* 包括: 任务句柄 任务优先级 堆栈大小 创建任务*/
#define TASK1_PRIO 2
#define TASK1_STACK_SIZE 128
TaskHandle_t task1_handler;
void task1( void * pvParameters );/******************************************************************************************************//*** @brief FreeRTOS例程入口函数* @param 无* @retval 无*/
void freertos_demo(void)
{ xTaskCreate((TaskFunction_t ) start_task,(char * ) "start_task",(configSTACK_DEPTH_TYPE ) START_TASK_STACK_SIZE,(void * ) NULL,(UBaseType_t ) START_TASK_PRIO,(TaskHandle_t * ) &start_task_handler );vTaskStartScheduler();
}void start_task( void * pvParameters )
{taskENTER_CRITICAL(); /* 进入临界区 */xTaskCreate((TaskFunction_t ) task1,(char * ) "task1",(configSTACK_DEPTH_TYPE ) TASK1_STACK_SIZE,(void * ) NULL,(UBaseType_t ) TASK1_PRIO,(TaskHandle_t * ) &task1_handler );vTaskDelete(NULL);taskEXIT_CRITICAL(); /* 退出临界区 */
}/* 任务一,申请内存以及释放内存,并显示空闲内存大小 */
void task1( void * pvParameters )
{uint8_t key = 0, t = 0;uint8_t * buf = NULL;while(1) {key = key_scan(0);if(key == KEY0_PRES){buf = pvPortMalloc(30); /* 申请内存 */if(buf != NULL){printf("申请内存成功!\r\n");}else printf("申请内存失败\r\n");}else if(key == KEY1_PRES){if(buf != NULL){vPortFree(buf); /* 释放内存 */printf("释放内存!!\r\n");} }if(t++ > 50){t = 0;printf("剩余的空闲内存大小为:%d\r\n",xPortGetFreeHeapSize());}vTaskDelay(10);}
}
初始化后的内存堆,如下:
插入新的空闲内存块,如下:
五、总结
FreeRTOS的知识点总结就暂时先告一段落了,后面将给大家带来几个相关的项目!
未完待预~