IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 嵌入式 -> FreeRTOS内存管理 -> 正文阅读

[嵌入式]FreeRTOS内存管理

一、内存管理简介

FreeRTOS创建任务、队列、信号量等的时候有两种方法,一种是动态的申请所需的RAM。一种是由用户自行定义所需的RAM,这种方法也叫静态方法,使用静态方法的函数一般以"Static"结尾,比如任务创建函数xTaskCreateStatic(),使用此函数创建任务的时候需要由用户
定义任务堆栈。使用动态内存管理的时候FreeRTOS内核在创建任务、队列、信号量的时候会动态的申请RAM。标准C库中的malloc()和free()还是也可以实现动态内存管理,但是如下原因限制了其使用:

  • 1、在小型的嵌入式系统中其并不总是有效的。
  • 2、会占用很多的代码空间。
  • 3、它们不是线程安全的。
  • 4、具有不确定性,每次执行的时间不同。
  • 5、会导致内存碎片。
  • 6、使链接器的配置变得复杂。

不同的嵌入式系统对于内存分配和时间要求不同,因此一个内存分配算法可能仅作为一个应用的子集。所以FreeRTOS将内存分配作为移植层的一部分,这样FreeRTOS使用者就可以使用自己的合适的内存分配方法。当内核需要RAM的时候可以使用pvPortMalloc()来替代malloc()申请内存,不使用内存的时候可以使用vPortFree()函数来替代free()函数释放内存。函数pvPortMalloc()、vPortFree0与函数malloc0、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种方法有何区别。

二、内存碎片

内存碎片通常分为内部碎片和外部碎片。

  • 1、内部碎片是由于采用固定大小的内存分区,当一个进程不能完全使用分给它的固定内存区域时就产生了内部碎片,通常内部碎片难以完全避免。
  • 2、外部碎片是由于某些未分配的连续内存区域太小,以至于不能满足任意进程的内存分配请求,从而不能被进程利用的内存区域。

在这里插入图片描述

  • (1)、此时内存堆还没有经过任何操作,为全新的。
  • (2)、此时经过第一次内存分配,一共分出去了4块内存块,大小分别为80B、80B、10B和100B。
  • (3)、有些应用使用完内存,进行了释放,释放的内存就是米黄色部分的内存块。如果此时有个应用需要50B的内存,那么它可以从两个地方来获取到,一个是浅绿色的还没被分配过的剩余内存块,另一个就是刚刚释放出来的80B的内存块。但是很明显,刚刚释放出来的这个10B的内存块就没法用了,除非此时有另外一个应用所需要的内存小于10B。
  • (4)、经过很多次的申请和释放以后,内存块被不断的分割、最终导致大量很小的内存块!

也就是图中浅蓝色部分的内存块,这些内存块由于太小导致大多数应用无法使用,这些没法使用的内存块就沦为了内存碎片!内存碎片是内存管理算法重点解决的一个问题,否则的话会导致实际可用的内存越来越少,最终应用程序因为分配不到合适的内存而奔溃!FreeRTOS的heap_4.c就给我们提供了一个解决内存碎片的方法,那就是将内存碎片进行合并组成一个新的可用的大内存块。

三、FreeRTOS内存分配方法

1、heap_1.c中的内存分配
动态内存分配需要一个内存堆,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.c仅实现了内存申请函数pvPortMalloc(),并未实现内存释放函数pvFree(),可以看一下pvFree()的源码,如下:

void vPortFree(void*pv)
{
	(void)pv;
	configASSERT(pv==NULL);
}

可以看出vPortFree()并没有释放内存的过程。可以看出如果使用heap_1.c,一旦申请内存成功就不允许释放!但是heap_1.c的内存分配过程简单,如此看来heap_1.c似乎毫无任何使用价值啊。千万不能这么想,有很多小型的应用在系统一开始就创建好任务、信号量或队列等,而在程序运行的整个过程中都不会删除,那么这个时候使用heap_1.c就很合适的。heap_1.c实现起来就是当需要RAM的时候就从一个大数组(内存堆)中分一小块出来,大数组(内存堆)的容量为configTOTAL_HEAP_SIZE。使用函数xPortGetFreeHeapSize()
可以获取内存堆中剩余内存大小。

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

2、heap_2.c中的内存分配
heap_2.c提供了一个更好的配算法,不像heap_1.c那样,heap_2.c提供了内存释放函数heap_2.c不会把释放调的内存块合并成一个大块,这样有一个缺点,随着你不断的申请内存,内存堆就会被分为很多个大小不一的内存(块),也就是会导致内存碎片!heap_4.c提供了空闲内存块合并的功能。同heap_1.c一样,整个内存堆为ucHeap[],大小为configTOTAL_HEAP_SIZE。可以通过函数xPortGetFreeHeapSize()来获取剩余的内存大小。
特点:
①、可以使用在那些可能会重复的删除任务、队列、信号量等的应用中,要注意有内存碎片产生!
②、如果分配和释放的内存大小是随机的,那么就要慎重使用了,比如下面的示例:如果一个应用动态的创建和删除任务,而且任务需要分配的堆栈大小都是一样的,那么heap_2.c就非常合适。如果任务所需的堆栈大小每次都是不同,那么heap_2.c就不适合了,因为这样会导致内存碎片产生,最终导致任务分配不到合适的堆栈!不过heap_4.c就很适合这种场景了。如果一个应用动态的创建和删除队列,而且队列的存储区域每次都是一样的,那么heap_2.c就非常适合。如果队列的存储区域每次都不同,那么heap_2.c就不适合了,和上面一样,此时可以使用heap_4.c。应用直接调用pvPortMalloc()和vPortFree(),而不是通过其他FreeRTOS的其他API函数来间接的调用。
③、如果应用中的任务、队列、信号量和互斥信号量具有不可预料性(如所需的内存大小不能确定,每次所需的内存都不相同,或者说大多数情况下所需的内存都是不同的)的话可能会导致内存碎片。
④、具有不可确定性,但是也远比标准C中的mallo()和free()效率高!heap_2.c基本上可以适用于大多数的需要动态分配内存的工程中,而heap_4.c更是具有将内存碎片合并成一个大的空闲内存块(就是内存碎片回收)的功能。
3、heap_3.c中的内存分配
这个分配方法是对标准C中的函数malloc()和free()的简单封装,FreeRTOS对这两个函数做了线程保护,可以看一下这两个函数的源码,如下:

void *pvPortMalloc( size_t xWantedSize )
{
void *pvReturn;

	vTaskSuspendAll();(1)
	{
		pvReturn = malloc( xWantedSize );(2)
		traceMALLOC( pvReturn, xWantedSize );
	}
	( void ) xTaskResumeAll();(3)

	#if( configUSE_MALLOC_FAILED_HOOK == 1 )
	{
		if( pvReturn == NULL )
		{
			extern void vApplicationMallocFailedHook( void );
			vApplicationMallocFailedHook();
		}
	}
	#endif

	return pvReturn;
}
/*-----------------------------------------------------------*/

void vPortFree( void *pv )
{
	if( pv )
	{
		vTaskSuspendAll();(4)
		{
			free( pv );
			traceFREE( pv, 0 );(5)
		}
		( void ) xTaskResumeAll();(6)
	}
}
  • (1)和(4)、挂起所有任务,为malloc()和free()提供线程保护
  • (2)、调用函数malloc()来申请内存。
  • (3)和(6)、恢复所有的任务。
  • (5)、调用函数free()释放内存。

特点:

  • ①、需要编译器提供一个内存堆,编译器库要提供malloc()和free()函数。比如STM32的话可以通过修改启动文件中的Heap_Size来修改内存堆的大小。
  • ②、具有不确定性。
  • ③、可能会增加代码量。
    注意:在heap_3.c中configTOTAL_HEAP_SIZE是没用的!

4、heap_4.c中的内存分配
heap_4.c提供了一个最优的匹配算法,不像heap_2.c,heap_4.c会将内存碎片合并成一个大的可用内存块,它提供了合并算法。内存堆为ucHeap[],大小为configTOTAL_HEAP_SIZE。可以通过函数xPortGetFreeHeapSize()来获取剩余的内存大小。函数
xPortGetMinimumEverFreeHeapSize()用来返回堆栈历史(从上电起到现在)最小剩余大小,可以通过这个返回值来帮助我们调整内存堆的大小。
特点:

  • ①、可是用在那些需要重复创建和删除任务、队列、信号量和互斥信号量等的应用中。
  • ②、不会像heap_2.c那样产生严重的内存碎片,即使分配的内存大小是随机的。
  • ③、具有不确定性,但是远比C标准库中的malloc()和free()效率高。heap_4.c非常适合于那些需要直接调用函数pvPortMalloc()和vPortFree()来申请和释放内存的应用,注意,我们移植FreeRTOS的时候就选择的heap_4.c!
    5、heap_5.c中的内存分配
    heap_5.c使用了和heap_4.c相同的合并算法,内存管理实现起来基本相同,但是heap_5.c允许内存堆跨越多个不连续的内存段。比如STM32的内部RAM可以作为内存堆,但是STM32内部RAM比较小,遇到那些需要大容量RAM的应用就不行了,如音视频处理。不过STM32可以外接SRAM甚至大容量的SDRAM,如果使用heap_4.c的话你就只能在内部RAM和外部SRAM或SDRAM之间二选一了,使用heap_5.c的话就不存在这个问题,两个都可以一起作为内存堆来用。不过在使用heap_5.c之前需要先调用函数vPortDefineHeapRegions()来对其初始化,在vPortDefineHeapRegions()未执行完之前禁止调用任何可能会调用pvPortMalloc()的API函数比如创建任务、信号量、队列等函数。函数vPortDefineHeapRegions()只有一个参数,参数是一个
    HeapRegion_t类型的数组,HeapRegion为一个结构体,此结构体在portable.h中有定义,定义如下:
typedef struct HeapRegion
{
uint8_t *pucStartAddress;//内存块的起始地址
size_t xSizelnBytes;//内存段大小
}HeapRegion_t;

heap_5.c允许内存堆跨越多个不连续的内存段,这些不连续的内存段就是由结构体HeapRegion_t来定义的。比如以STM32F407开发板为例,现在有三个内存段:CCM、内部SRAM、外部SRAM,起始分别为:0X10000000、0X20000000、0×6800000,大小分别为:64KB、128KB、1MB,那么数组就如下:

HeapRegion_t xHeapRegions[]=
{
{(uint8_t*)0X10000000UL,0x10000},//CCM内存,起始地址0X10000000,大小64KB
{(uint8_t*)0X20000000UL,0x20000},//内部SRAM内存,起始地址0X20000000
//大小为128KB
{(uint8_t*)0X68000000UL,0x100000},
//外部SRAM内存,起始地址0×68000000,//大小为1MB
{NULL,0}//数组结尾
};

注意,数组中成员顺序按照地址从低到高的顺序排列,而且最后一个成员必须使用NULL。数组准备好以后就可以调用函数vPortDefineHeapRegions()完成初始化,如下:

vPortDefineHeapRegions(xHeapRegions);

四、总结

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

FreeRTOS的内存管理就介绍到这里啦!!!

  嵌入式 最新文章
基于高精度单片机开发红外测温仪方案
89C51单片机与DAC0832
基于51单片机宠物自动投料喂食器控制系统仿
《痞子衡嵌入式半月刊》 第 68 期
多思计组实验实验七 简单模型机实验
CSC7720
启明智显分享| ESP32学习笔记参考--PWM(脉冲
STM32初探
STM32 总结
【STM32】CubeMX例程四---定时器中断(附工
上一篇文章      下一篇文章      查看所有文章
加:2021-10-30 12:41:30  更:2021-10-30 12:42:35 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/26 8:53:00-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码