FreeRTOS内存管理

FreeRTOS内存管理

FreeRTOS 内存管理模块管理用于系统中内存资源,它是操作系统的核心模块之一。主要包括内存的初始化、分配以及释放。
在嵌入式实时操作系统中,调用 malloc()和 free()却是危险的,原因有以下几点:
这些函数在小型嵌入式系统中并不总是可用的,小型嵌入式设备中的 RAM 不足。
它们的实现可能非常的大,占据了相当大的一块代码空间。
他们几乎都不是安全的。
它们并不是确定的,每次调用这些函数执行的时间可能都不一样
它们有可能产生碎片。
这两个函数会使得链接器配置得复杂
如果允许堆空间的生长方向覆盖其他变量占据的内存,它们会成为 debug 的灾难

在一般的实时嵌入式系统中,由于实时性的要求,很少使用虚拟内存机制。所有的内存都需要用户参与分配,直接操作物理内存,所分配的内存不能超过系统的物理内存,所有的系统堆栈的管理,都由用户自己管理。
同时,在嵌入式实时操作系统中,对内存的分配时间要求更为苛刻,分配内存的时间必须是确定的。一般内存管理算法是根据需要存储的数据的长度在内存中去寻找一个与这段数据相适应的空闲内存块,然后将数据存储在里面。而寻找这样一个空闲内存块所耗费的时间是不确定的,因此对于实时系统来说,这就是不可接受的,实时系统必须要保证内存块的分配过程在可预测的确定时间内完成,否则实时任务对外部事件的响应也将变得不可确定。
而在嵌入式系统中,内存是十分有限而且是十分珍贵的,用一块内存就少了一块内存,而在分配中随着内存不断被分配和释放,整个系统内存区域会产生越来越多的碎片,因为在使用过程中,申请了一些内存,其中一些释放了,导致内存空间中存在一些小的内存块,它们地址不连续,不能够作为一整块的大内存分配出去,所以一定会在某个时间,系统已经无法分配到合适的内存了,导致系统瘫痪。其实系统中实际是还有内存的,但是因为小块的内存的地址不连续,导致无法分配成功,所以我们需要一个优良的内存分配算法来避免这种情况的出现。
不同的嵌入式系统具有不同的内存配置和时间要求。所以单一的内存分配算法只可能适合部分应用程序。因此, FreeRTOS 将内存分配作为可移植层面(相对于基本的内核代码部分而言), FreeRTOS 有针对性的提供了不同的内存分配管理算法,这使得应用于不同场
景的设备可以选择适合自身内存算法。
FreeRTOS 对内存管理做了很多事情, FreeRTOS 的 V9.0.0 版本为我们提供了 5 种内存管理算法,分别是 heap_1.c、 heap_2.c、 heap_3.c、 heap_4.c、 heap_5.c,源文件存放于FreeRTOS\Source\portable\MemMang 路径下,在使用的时候选择其中一个添加到我们的工程中去即可。
FreeRTOS 的内存管理模块通过对内存的申请、释放操作,来管理用户和系统对内存的使用,使内存的利用率和使用效率达到最优,同时最大限度地解决系统可能产生的内存碎片问题。

内存管理的应用场景

内存管理的主要工作是动态划分并管理用户分配好的内存区间,主要是在用户需要使用大小不等的内存块的场景中使用, 当用户需要分配内存时,可以通过操作系统的内存申请函数索取指定大小内存块,一旦使用完毕,通过动态内存释放函数归还所占用内存,使之可以重复使用(heap_1.c 的内存管理除外)。
例如我们需要定义一个 float 型数组: floatArr[];
但是,在使用数组的时候, 总有一个问题困扰着我们:数组应该有多大?在很多的情况下,你并不能确定要使用多大的数组,可能为了避免发生错误你就需要把数组定义得足够大。即使你知道想利用的空间大小,但是如果因为某种特殊原因空间利用的大小有增加或者减少,你又必须重新去修改程序,扩大数组的存储范围。这种分配固定大小的内存分配方法称之为静态内存分配。这种内存分配的方法存在比较严重的缺陷,在大多数情况下会浪费大量的内存空间,在少数情况下,当你定义的数组不够大时,可能引起下标越界错误,甚至导致严重后果。
我们用动态内存分配就可以解决上面的问题。所谓动态内存分配就是指在程序执行的过程中动态地分配或者回收存储空间的分配内存的方法。动态内存分配不象数组等静态内存分配方法那样需要预先分配存储空间,而是由系统根据程序的需要即时分配,且分配的大小就是程序要求的大小

内存管理方案详解

FreeRTOS 规定了内存管理的函数接口,具体见,但是不管其内部的内存管理方案是怎么实现的,所以, FreeRTOS 可以提供多个内存管理方案,
FreeRTOS 提供的内存管理都是从内存堆中分配内存的。 从前面学习的过程中,我们也知道,创建任务、消息队列、事件等操作都使用到分配内存的函数,这是系统中默认使用内存管理函数从内存堆中分配内存给系统核心组件使用。
对于 heap_1.c、 heap_2.c 和 heap_4.c 这三种内存管理方案,内存堆实际上是一个很大的 数 组 , 定 义 为 static uint8_t ucHeap[ configTOTAL_HEAP_SIZE] , 而 宏 定 义configTOTAL_HEAP_SIZE 则表示系统管理内存大小,单位为字, 在 FreeRTOSConfig.h 中由用户设定。
对于 heap_3.c 这种内存管理方案, 它封装了 C 标准库中的 malloc()和 free()函数,封装后的 malloc()和 free()函数具备保护,可以安全在嵌入式系统中执行。因此, 用户需要通过编译器或者启动文件设置堆空间。
heap_5.c 方案允许用户使用多个非连续内存堆空间,每个内存堆的起始地址和大小由用户定义。 这种应用其实还是很大的,比如做图形显示、 GUI 等,可能芯片内部的 RAM是不够用户使用的,需要外部SDRAM,那这种内存管理方案则比较合适。

heap_1.c
heap_1.c 管理方案是 FreeRTOS 提供所有内存管理方案中最简单的一个,它只能申请内存而不能进行内存释放,并且申请内存的时间是一个常量,这样子对于要求安全的嵌入式设备来说是最好的,因为不允许内存释放,就不会产生内存碎片而导致系统崩溃,但是也有缺点,那就是内存利用率不高,某段内存只能用于内存申请的地方,即使该内存只使用一次,也无法让系统回收重新利用。
heap1.c 方案具有以下特点:
1、 用于从不删除任务、队列、信号量、互斥量等的应用程序(实际上大多数使用FreeRTOS 的应用程序都符合这个条件) 。
2、 函数的执行时间是确定的并且不会产生内存碎片。

heap_2.c
heap_2.c 方案与 heap_1.c 方案采用的内存管理算法不一样,它采用一种最佳匹配算法(best fit algorithm),比如我们申请 100 字节的内存,而可申请内存中有三块对应大小 200 字节, 500 字节和 1000 字节大小的内存块,按照算法的最佳匹配,这时候系统会把 200 字节大小的内存块进行分割并返回申请内存的起始地址,剩余的内存则插回链表留待下次申请。Heap_2.c 方案支持释放申请的内存, 但是它不能把相邻的两个小的内存块合成一个大的内存块, 对于每次申请内存大小都比较固定的,这个方式是没有问题的,而对于每次申请并不是固定内存大小的则会造成内存碎片,
heap_2.c 方案具有以下特点:

  1. 可以用在那些反复的删除任务、队列、信号量、等内核对象且不担心内存碎片的应用程序。
  2. 如果我们的应用程序中的队列、任务、信号量、 等工作在一个不可预料的顺序,这样子也有可能会导致内存碎片。
  3. 具有不确定性,但是效率比标准 C 库中的 malloc 函数高得多
  4. 不能用于那些内存分配和释放是随机大小的应用程序。

heap_3.c
heap_3.c 方案只是简单的封装了标准 C 库中的 malloc()和 free()函数, 并且能满足常用的编译器。 重新封装后的 malloc()和 free()函数具有保护功能,采用的封装方式是操作内存前挂起调度器、完成后再恢复调度器。
heap_3.c 方案具有以下特点:
1、 需要链接器设置一个堆, malloc()和 free()函数由编译器提供。
2、 具有不确定性。
3、 很可能增大 RTOS 内核的代码大小。
要 注 意 的 是 在 使 用 heap_3.c 方 案 时 , FreeRTOSConfig.h 文 件 中 的configTOTAL_HEAP_SIZE 宏定义不起作用。 在STM32 系列的工程中, 这个由编译器定义的堆都在启动文件里面设置, 单位为字节,FreeRTOS内存管理_第1张图片
heap_4.c
heap_4.c 方案与 heap_2.c 方案一样都采用最佳匹配算法来实现动态的内存分配,但是不一样的是 heap_4.c 方案还包含了一种合并算法,能把相邻的空闲的内存块合并成一个更大的块,这样可以减少内存碎片。 heap_4.c 方案特别适用于移植层中可以直接使用
pvPortMalloc()和 vPortFree()函数来分配和释放内存的代码。
内 存 分 配 时 需 要 的 总 的 堆 空 间 由 文 件 FreeRTOSConfig.h 中 的 宏configTOTAL_HEAP_SIZE 配置,单位为字。 通过调用函数 xPortGetFreeHeapSize() 我们可以知道还剩下多少内存没有使用, 但是并不包括内存碎片。 这样一来我们可以实时的调整和优化configTOTAL_HEAP_SIZE 的大小。
heap_4.c 方案具有以下特点:
1、可用于重复删除任务、队列、信号量、互斥量等的应用程序
2、 可用于分配和释放随机字节内存的应用程序, 但并不像 heap2.c 那样产生严重的内存碎片。
3、 具有不确定性,但是效率比标准 C 库中的 malloc 函数高得多。

heap_5.c
heap_5.c 方案在实现动态内存分配时与 heap4.c 方案一样, 采用最佳匹配算法和合并算法,并且允许内存堆跨越多个非连续的内存区,也就是允许在不连续的内存堆中实现内存分配,比如用户在片内 RAM 中定义一个内存堆,还可以在外部 SDRAM 再定义一个或多个内存堆,这些内存都归系统管理。
heap_5.c 方案通过调用 vPortDefineHeapRegions()函数来实现系统管理的内存初始化,在内存初始化未完成前不允许使用内存分配和释放函数。 如创建 FreeRTOS 对象(任务、队列、信号量等)时会隐式的调用 pvPortMalloc()函数,因此必须注意:使用 heap_5.c 内存管理方案创建任何对象前,要先调用 vPortDefineHeapRegions()函数将内存初始化。

xPortGetFreeHeapSize();获取当前内存大小
pvPortMalloc()申请内存
vPortFree()释放内存

你可能感兴趣的:(stm32,嵌入式,rtos)