FreeRTOS操作系统——内存管理(二十一)

FreeRTOS操作系统学习

前言

内存管理是一个系统基本组成部分,FreeRTOS 中大量使用到了内存管理,比如创建任务、信号量、队列等会自动从堆中申请内存。用户应用层代码也可以 FreeRTOS 提供的内存管理函数来申请和释放内存

一、内存管理简介

FreeRTOS 创建任务、队列、信号量等的时候有两种方法,一种是动态的申请所需的 RAM。一种是由用户自行定义所需的 RAM,这种方法也叫静态方法,使用静态方法的函数一般以“Static”结尾,比如任务创建函数 xTaskCreateStatic(),使用此函数创建任务的时候需要由用户定义任务堆栈(基本上不用),静态创建和内存管理没关系。

使用动态内存管理的时候 FreeRTOS 内核在创建任务、队列、信号量的时候会动态的申请RAM。标准 C 库中的 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、内存分配方法

1.heap_1.c

五种方法最简单的一个。一旦分配内存之后,不允许释放分配的内存。heap_1.c适用于大部分嵌入式应用程序。因为大多数嵌入式应用只是在系统启动时创建所有任务、队列、信号量等,并且直到程序结束都会一直使用它们,不需要删除。

heap_1 特性如下:
1、适用于那些一旦创建好任务、信号量和队列就再也不会删除的应用,实际上大多数的
FreeRTOS 应用都是这样的。
2、具有可确定性(执行所花费的时间大多数都是一样的),而且不会导致内存碎片。
3、代码实现和内存分配过程都非常简单,内存是从一个静态数组中分配到的,也就是适合
于那些不需要动态内存分配的应用

2.heap_2.c

heap_2提供了一个更好的分配算法,不像heap_1那样,heap_2提供了内存释放函数。heap_2
不会把释放的内存块合并成一个大块(这会造成内存碎片)。 heap_2.c适用于需要动态创建任务的大多数小型实时系统。

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()效率高!

3.heap_3.c

这个分配方法是对标准 C 中的函数 malloc()和 free()的简单封装

heap_3 的特性如下:
1、需要编译器提供一个内存堆,编译器库要提供 malloc()和 free()函数。比如使用 STM32的话可以通过修改启动文件中的 Heap_Size 来修改内存堆的大小

在这里插入图片描述
2、具有不确定性
3、可能会增加代码量。
注意,在 heap_3 中 configTOTAL_HEAP_SIZE 是没用的!

4.heap_4.c

heap_4 提供了一个最优的匹配算法,不像 heap_2,heap_4 会将内存碎片合并成一个大的可用内存块,它提供了内存块合并算法。内存堆为 ucHeap[],大小同样为 configTOTAL_HEAP_SIZE。可以通过函数 xPortGetFreeHeapSize()来获取剩余的内存大小。

heap_4 特性如下:
1、可以用在那些需要重复创建和删除任务、队列、信号量和互斥信号量等的应用中。
2、不会像 heap_2 那样产生严重的内存碎片,即使分配的内存大小是随机的。
3、具有不确定性,但是远比 C 标准库中的 malloc()和 free()效率高。

5.heap_5.c

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 ()来
对内存堆做初始化处理。

//使用 heap_5 的时候在开启任务调度器、创建任务、创建信号量之前一定要先
//调用函数 vPortDefineHeapRegions()初始化内存堆!
vPortDefineHeapRegions((const HeapRegion_t *)xHeapRegions);

heap_1 最简单,但是只能申请内存,不能释放。heap_2 提供了内存释放函数,用户代码也可以直接调用函数 pvPortMalloc()和vPortFree()来申请和释放内存,但是 heap_2 会导致内存碎片的产生!heap_3 是对标准 C 库中的函数 malloc()和 free()的简单封装,并且提供了线程保护。heap_4 相对与 heap_2 提供了内存合并功能,可以降低内存碎片的产生,我们移植 FreeRTOS 的时候就选择了 heap_4。heap_5 基本上和 heap_4 一样,只是 heap_5 支持内存堆使用不连续的内存块。

三、内存管理实验

学习使用 FreeRTOS 的内存申请和释放函数:pvPortMalloc()、vPortFree(),并且观察申请和释放的过程中内存大小的变化情况

//MALLOC 任务函数
void malloc_task(void *pvParameters)
{
u8 *buffer;
u8 times,i,key=0;
u32 freemem;
LCD_ShowxNum(110,170,configTOTAL_HEAP_SIZE,5,16,0);//显示内存总容量 (1)显示内存堆的总容量,内存堆的容量由宏 configTOTAL_HEAP_SIZE 来确定的,所以直接显示 configTOTAL_HEAP_SIZE 的值就行了。
 while(1)
 {
key=KEY_Scan(0);
switch(key)
{
case WKUP_PRES:
buffer=pvPortMalloc(30); //申请内存,30 个字节 (2)按下 KEY_UP 键,调用函数 pvPortMalloc()申请内存,大小为 30 字节。
printf("申请到的内存地址为:%#xrn",(int)buffer);
break;
case KEY1_PRES:
if(buffer!=NULL)vPortFree(buffer); //释放内存 (3)按下 KEY1 键,释放(2)中申请到的内存。
buffer=NULL; //(4)释放内存以后将 buffer 设置为 NULL
break;
case KEY0_PRES:
if(buffer!=NULL) //buffer 可用,使用 buffer (5) 判断 buffer 是否有效,有效的话就是用 buffer。
{
times++;
sprintf((char*)buffer,"User %d Times",times);//向 buffer 中填写一些数据
LCD_ShowString(94,210,200,16,16,buffer);
}
break;
}
freemem=xPortGetFreeHeapSize(); //获取剩余内存大小 (6)调用函数 xPortGetFreeHeapSize()获取当前剩余内存大小并且显示到 LCD 上。
LCD_ShowxNum(110,190,freemem,5,16,0);//显示内存总容量
i++;
if(i==50)
{
i=0;
LED0=~LED0;
}
 vTaskDelay(10);
 }
}

(4)、释放内存以后将 buffer 设置为 NULL,函数 vPortFree()释放内存以后并不会将 buffer清零,此时 buffer 还是上次申请到的内存地址,如果此时再调用指针 buffer 的话你会发现还是可以使用的!感觉好像没有释放内存,所以这里将 buffer 清零!

总结

在使用内存管理的时候最常遇到的一个问题就是内存泄露,内存泄露的原因是没有正确的释放内存!内存申请和释放是要成对出现的,在一段内存没有被释放之前绝对不能再调用一次函数 pvPortMalloc()为其再次分配内存!连续 5 次按 KEY_UP 为 buffer申请内存,就犯了这个错误,正确的方法应该是,按一次 KEY_UP 按键申请内存成功以后,先按 KEY1 释放掉 buffer 的内存,然后再按 KEY_UP 为其重新分配内存。忘记释放内存,内存泄露严重的话应用可能因为申请不到合适的内存而导致死机!

本图文内容来源于网友网络收集整理提供,作为学习参考使用,版权属于原作者。
THE END
分享
二维码
< <上一篇
下一篇>>