本次进行分享的是FreeRtos中的内存管理,通过此篇的学习,对内存的使用会有新的认识,也会对FreeRtos下的5中内存的分配方式有一定的路径,自己在实际运用中根据自己的需求,进行合理的内存分配和方式的选择。
FreeRTOS 创建任务、队列、信号量等的时候有两种方法,一种是动态的申请所需的 RAM。一种是由用户自行定义所需的 RAM,这种方法也叫静态方法
使用动态内存管理的时候 FreeRTOS 内核在创建任务、队列、信号量的时候会动态的申请RAM。标准 C 库中的 malloc()和 free()也可以实现动态内存管理,但是如下原因限制了其使用:
● 在小型的嵌入式系统中效率不高。
● 会占用很多的代码空间。
● 它们不是线程安全的。
● 具有不确定性,每次执行的时间不同。
● 会导致内存碎片。
● 使链接器的配置变得复杂。
当内核需要 RAM 的时候可以使用 pvPortMalloc()来替代 malloc()申请内存,不使用内存的 时候可以使用
vPortFree()函数来替代 free()函数释放内存。函数 pvPortMalloc()、vPortFree()与函 数
malloc()、free()的函数原型类似
FreeRTOS 提供了 5 种内存分配方法,FreeRTOS 使用者可以其中的某一个方法,或者自己的内存分配方法。这 5 种方法是 5 个文件,分别为:heap_1.c、heap_2.c、heap_3.c、heap_4.c 和heap_5.c。这 5 个文件再 FreeRTOS 源码中,路径:FreeRTOS->Source->portable->MemMang
什么是内存碎片,内存碎片是如何产生的从下面这幅图片可以形象的看出来,当我们不断进行内存的申请与释放就会产生内存碎片。
主要分享一下5种分配方式的各自特征,具体内部分配和释放函数内部不做介绍,可以自己看看源码(自己也没搞的很清楚)。
动 态 内 存 分 配 需 要 一 个 内 存 堆 , FreeRTOS 中 的 内 存 堆 为 ucHeap[] ,大小为configTOTAL_HEAP_SIZE。不管是哪种内存分配方法,它们的内存堆都为 ucHeap[],而且大小都是 configTOTAL_HEAP_SIZE。内存堆在文件heap_x.c(x 为 1~5)中定义的。
/**********************heap_1.c-----------------------*/
#if( configAPPLICATION_ALLOCATED_HEAP == 1 )
extern uint8_t ucHeap[ configTOTAL_HEAP_SIZE ]; //需要用户自行定义内存堆
#else
static uint8_t ucHeap[ configTOTAL_HEAP_SIZE ]; //编译器决定
#endif
当宏 configAPPLICATION_ALLOCATED_HEAP 为 1 的时候需要用户自行定义内存堆,否则的话由编译器来决定,默认都是由编译器来决定的。如果自己定义的话就可以将内存堆定义到外部 SRAM 或者 SDRAM 中。
heap_1 实现起来就是当需要 RAM 的时候就从一个大数组(内存堆)中分一小块出来,大数组(内存堆)的容量为 configTOTAL_HEAP_SIZE,上面已经说了。使用函数 xPortGetFreeHeapSize()可以获取内存堆中剩余内存大小。
Heap_1特性
1、适用于那些一旦创建好任务、信号量和队列就再也不会删除的应用,实际上大多数的FreeRTOS 应用都是这样的。
2、具有可确定性(执行所花费的时间大多数都是一样的),而且不会导致内存碎片。
3、代码实现和内存分配过程都非常简单,内存是从一个静态数组中分配到的,也就是适合于那些不需要动态内存分配的应用。
heap_2提供了一个更好的分配算法,不像heap_1那样,heap_2提供了内存释放函数。 heap_2不会把释放的内存块合并成一个大块,这样有一个缺点,随着你不断的申请内存,内存堆就会被分为很多个大小不一的内存(块),也就是会导致内存碎片!
Heap_2 特性
1、可以使用在那些可能会重复的删除任务、队列、信号量等的应用中,要注意有内存碎片
产生!
2、如果分配和释放的内存 n 大小是随机的,那么就要慎重使用了,比如下面的示例:
● 如果一个应用动态的创建和删除任务,而且任务需要分配的堆栈大小都是一样的,
那么 heap_2 就非常合适。如果任务所需的堆栈大小每次都是不同,那么 heap_2 就
不适合了,因为这样会导致内存碎片产生,最终导致任务分配不到合适的堆栈!不过 heap_4 就很适合这种场景了。
● 如果一个应用中所使用的队列存储区域每次都不同,那么 heap_2 就不适合了,和上 面一样,此时可以使用 heap_4。
● 应用需要调用 pvPortMalloc()和 vPortFree()来申请和释放内存,而不是通过其他
FreeRTOS 的其他 API 函数来间接的调用,这种情况下 heap_2 不适合。
3、如果应用中的任务、队列、信号量和互斥信号量具有不可预料性(如所需的内存大小不能确定,每次所需的内存都不相同,或者说大多数情况下所需的内存都是不同的)的话可能会导致内存碎片。虽然这是小概率事件,但是还是要引起我们的注意!
4、具有不可确定性,但是也远比标准 C 中的 mallo()和 free()效率高!
heap_2 基本上可以适用于大多数的需要动态分配内存的工程中,而 heap_4 更是具有将内存碎片合并成一个大的空闲内存块(就是内存碎片回收)的功能。
这个分配方法是对标准 C 中的函数 malloc()和 free()的简单封装,FreeRTOS 对这两个函数做了线程保护,看内部实现源码可以知道。
heap_4 提供了一个最优的匹配算法,不像 heap_2,heap_4 会将内存碎片合并成一个大的可用内存块,它提供了内存块合并算法。内存堆为 ucHeap[],大小同样为 configTOTAL_HEAP_SIZE。可以通过函数 xPortGetFreeHeapSize()来获取剩余的内存大小。
Heap_4 特性
1、可以用在那些需要重复创建和删除任务、队列、信号量和互斥信号量等的应用中。
2、不会像 heap_2 那样产生严重的内存碎片,即使分配的内存大小是随机的。
3、具有不确定性,但是远比 C 标准库中的 malloc()和 free()效率高。
heap_4 非常适合于那些需要直接调用函数 pvPortMalloc()和 vPortFree()来申请和释放内存的应用,注意,我们移植 FreeRTOS 的时候就选择的 heap_4!
heap_4 也使用链表结构来管理空闲内存块,链表结构体与 heap_2 一样。heap_4 也定义了两个局部静态变量 xStart 和 pxEnd 来表示链表头和尾,其中 pxEnd 是指向 BlockLink_t 的指针。
heap_5 使用了和 heap_4 相同的合并算法,内存管理实现起来基本相同,但是 heap_5 允许内存堆跨越多个不连续的内存段。比如 STM32 的内部 RAM 可以作为内存堆,但是 STM32 内部 RAM 比较小,遇到那些需要大容量 RAM 的应用就不行了,如音视频处理。不过 STM32 可以外接 SRAM 甚至大容量的 SDRAM,如果使用 heap_4 的话你就只能在内部 RAM 和外部SRAM 或 SDRAM 之间二选一了,使用 heap_5 的话就不存在这个问题,两个都可以一起作为内存堆来用。 如果使用 heap_5 的话,在调用 API 函数之前需要先调用函数 vPortDefineHeapRegions ()来对内存堆做初始化处理,在 vPortDefineHeapRegions()未执行完之前禁止调用任何可能会调用
pvPortMalloc()的 API 函数!比如创建任务 、信号量、队列等函数。函数 vPortDefineHeapRegions()只有一个参数,参数是一个 HeapRegion_t 类型的数组,HeapRegion 为一个结构体,此结构体在portable.h 中有定义。
typedef struct HeapRegion
{
uint8_t *pucStartAddress; //内存块的起始地址
size_t xSizeInBytes; //内存段大小
} HeapRegion_t;
heap_5 允许内存堆跨越多个不连续的内存段,这些不连续的内存段就是由结构
体 HeapRegion_t 来定义的。比如以 STM32F103 开发板为例,现在有连个内存段:内部 SRAM、外部 SRAM,起始分别为:0X20000000、0x68000000,大小分别为:64KB、1MB,那么数组就如下:
HeapRegion_t xHeapRegions[] =
{
{ ( uint8_t * ) 0X20000000UL, 0x10000 },//内部 SRAM 内存,起始地址 0X20000000,
//大小为 64KB
{ ( uint8_t * ) 0X68000000UL, 0x100000},//外部 SRAM 内存,起始地址 0x68000000,
//大小为 1MB
{ NULL, 0 } //数组结尾
};
注意,数组中成员顺序按照地址从低到高的顺序排列,而且最后一个成员必须使用 NULL。heap_5 允许内存堆不连续,说白了就是允许有多个内存堆。在 heap_2 和 heap_4 中只有一个内存堆,初始化的时候只也只需要处理一个内存堆。 heap_5 有多个内存堆,这些内存堆会被连接在一起,和空闲内存块链表类似,这个处理过程由函数 vPortDefineHeapRegions()完成。
在malloc_task任务中 使用三个按键分别进行内存的分配、释放与内存的使用
main.c
#include "sys.h"
#include "delay.h"
#include "usart.h"
#include "led.h"
#include "FreeRTOS.h"
#include "task.h"
#include "key.h"
#include "semphr.h"
#include "string.h"
#include "event_groups.h"
//任务优先级
#define START_TASK_PRIO 1
//任务堆栈大小
#define START_STK_SIZE 128
//任务句柄
TaskHandle_t StartTask_Handler;
//任务函数
void start_task(void *pvParameters);
#define MALLOC_TASK_PRIO 2
//任务堆栈大小
#define MALLOC__STK_SIZE 50
//任务句柄
TaskHandle_t MALLOC_Task_Handler;
//任务函数
void malloc_task(void *pvParameters);
int main(void)
{
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);//设置系统中断优先级分组4
delay_init(); //延时函数初始化
uart_init(115200); //初始化串口
LED_Init(); //初始化LED
KEY_GPIO_INIT(); //按键初始化
printf("-----------------内存申请管理测试----------------\r\n");
//创建开始任务
xTaskCreate((TaskFunction_t )start_task, //任务函数
(const char* )"start_task", //任务名称
(uint16_t )START_STK_SIZE, //任务堆栈大小
(void* )NULL, //传递给任务函数的参数
(UBaseType_t )START_TASK_PRIO, //任务优先级
(TaskHandle_t* )&StartTask_Handler); //任务句柄
vTaskStartScheduler(); //开启任务调度
}
//开始任务任务函数
void start_task(void *pvParameters)
{
taskENTER_CRITICAL(); //进入临界区
xTaskCreate((TaskFunction_t )malloc_task,
(const char* )"malloc_task",
(uint16_t )MALLOC__STK_SIZE,
(void* )NULL,
(UBaseType_t )MALLOC_TASK_PRIO,
(TaskHandle_t* )&MALLOC_Task_Handler);
vTaskDelete(StartTask_Handler); //删除开始任务
taskEXIT_CRITICAL(); //退出临界区
}
//malloc_task
void malloc_task(void *pvParameters)
{
u8 keyvalue = 0;
u8 *buffer;
u8 time = 0;
//size_t FreeHeap = 0;
while(1)
{
keyvalue = KEY_Scan();
switch(keyvalue)
{
case 1:
//开辟内存
buffer = pvPortMalloc(30);
printf("申请到的内存地址为:0X%x\r\n",(int)buffer);
break;
case 2:
//回收内存
vPortFree(buffer);
//必须清零
buffer = NULL;
printf("内存释放后的地址为:0X%x\r\n",(int)buffer);
break;
case 3:
if(buffer != NULL) //申请的空间有效
{
time++;
sprintf((char *)buffer,"User Timers:%d\r\n",time);
printf("%s",buffer);
}
else
{
printf("申请的内存无效,请申请内存\r\n");
}
}
// FreeHeap = xPortGetFreeHeapSize(); //获取剩余内存
// printf("剩余内存为:%d\r\n",FreeHeap);
// delay_ms(3000);
//
// LED0 = ~LED0;
vTaskDelay(10);
}
}