【FreeRtos内存管理】

FreeRtos内存管理



前言

本次进行分享的是FreeRtos中的内存管理,通过此篇的学习,对内存的使用会有新的认识,也会对FreeRtos下的5中内存的分配方式有一定的路径,自己在实际运用中根据自己的需求,进行合理的内存分配和方式的选择。


一、内存管理介绍

1.1 内存认识

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

在这里插入图片描述

1.2 内存碎片

什么是内存碎片,内存碎片是如何产生的从下面这幅图片可以形象的看出来,当我们不断进行内存的申请与释放就会产生内存碎片。

在这里插入图片描述

二、内存分配5种方式

主要分享一下5种分配方式的各自特征,具体内部分配和释放函数内部不做介绍,可以自己看看源码(自己也没搞的很清楚)。

2.1 heap_1内存分配方式

动 态 内 存 分 配 需 要 一 个 内 存 堆 , 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、代码实现和内存分配过程都非常简单,内存是从一个静态数组中分配到的,也就是适合于那些不需要动态内存分配的应用。

2.2 heap_2内存分配方式

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 更是具有将内存碎片合并成一个大的空闲内存块(就是内存碎片回收)的功能。

2.3 heap_3内存分配方式

这个分配方法是对标准 C 中的函数 malloc()和 free()的简单封装,FreeRTOS 对这两个函数做了线程保护,看内部实现源码可以知道。

2.4 heap_4内存分配方式

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 的指针。

2.5 heap_5内存分配方式

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()完成。

三、实验演示

3.1 实验需求

在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);
			
    }
}   

3.2 实验结果

在这里插入图片描述

  • 1
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小殷学长

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值