FreeRTOS 内存管理

FreeRTOS 内存管理(Heap Memory Management)

动态内存分配(Dynamic Memory Allocation)

为了使FreeRTOS简单易用, 内核对象并不是静态分配内存空间,而是动态的。FreeRTOS在每个内核对象被创建时分配空间,在对象被删除时回收空间。
C语言标准库中的malloc()和free()是用来分配空间的,但是在某些情况下有些问题,比如说:

  • 在小型嵌入式系统上不一定适用
  • 库占用内存太多
  • 线程间不一定安全
  • 分配时间不是确定的(影响实时性)
  • 会被内存碎片影响

  • 在FreeRTOS中,用pvPortMalloc()代替malloc(),用vPortFree()代替free(),这两个函数都是public函数,可以被应用调用。
    FreeRTOS提供5个例程heap_1.c, heap_2.c, heap_3.c, heap_4.c, heap_5.c,他们的代码可以在FreeRTOS/Source/portable/MemMang目录下找到。

下面分别介绍这五个方法:

Heap 1

Heap1 仅仅使用了一个基本的pvPortMalloc(),并没有使用到vPortFree(),因此Heap1适用于只创建任务不删除的情况。比如说,有些对安全性要求较高的系统禁止使用动态内存分配,以减少不确定性、内存碎片、分配出错。Heap1是确定性较高的分配方式,不会产生内存碎片。
通过FreeRTOSConfig.h中的宏定义configTOTAL_HEAP_SIZE,可以设置队列长度。队列是静态创建的,设置较大的size会导致较多的内存消耗,即使队列并未装满。
每一个任务从heap中分配一定空间,作为TCB(Task control block)和Stack, 如下图所示:
在这里插入图片描述

  • A 代表没有任务创建时的情况,队列是空的
  • B 此时有一个任务被创建,队列中存放这个任务的TCB和Stack
  • C 多个任务创建时的情况

Heap 2

与Heap 1不同,Heap 2应用了一个最好的分配算法并且允许内存被回收。相似的是,由于队列是静态创建的,所以会消耗较多内存空间。
比如说,现在Heap中包含三块空闲内存,分别是5bytes, 25bytes, 100bytes。任务调用pvPortMalloc()函数请求分配20bytes的内存空间。这时,pvPortMalloc()判断应该使用25bytes的空闲区域,那么它会先分割这25bytes为20bytes,5bytes,然后返回20bytes空间首指针。剩余的5bytes在下次调用pvPortMalloc()时依然是空闲可用的。
这段过程可以用下图表示:
在这里插入图片描述
但是,Heap2并不会合并相邻内存块,所以他会造成内存碎片化。
Heap2常被用于需要重复创建和删除相同任务并且任务的内存大小固定的场景。
在这里插入图片描述

Heap 3

Heap3 使用C标准库的malloc(),free()函数,所以他的Heap大小取决于链接器配置,与configTOTAL_HEAP_SIZE无关。
Heap3确保了内存分配与回收时线程的安全,他会在内存分配时挂起FreeRTOS调度器。

Heap 4

与Heap1, 2相似,Heap4也是静态分配队列,并且通过configTOTAL_HEAP_SIZE定义大小。但是,Heap4会合并相邻空闲内存空间,防止内存碎片的风险。如图所示,
在这里插入图片描述
Heap4 适用于需要重复分配不同大小内存块的场景。

Heap4也支持为队列指定开始地址,默认情况下,队列开始地址是链接器自动分配的。也可以通过设置configAPPLICATION_ALLOCATED_HEAP宏定义来手动分配。
在这里插入图片描述

Heap 5

Heap5允许在多个、不相邻的内存块中分配空间。适用于非连续内存块的场景。
使用Heap_5时,每个内科对象被调用前必须调用vPortDefineHeapRegions()函数来指定内存的开始地址。
在这里插入图片描述

HeapRegion_t

每一个分散的内存空间都被描述为HeapRegion_t类型,他是一个结构体
在这里插入图片描述
注意:
最低的开始地址必须是队列中的第一个结构体,最高的开始地址必须是队列中的最后一个结构体。
队列结尾的结构体的pucStartAddress = NULL

举例:有一个内存A,分配了三个Block:RAM1, RAM2 ,RAM3
在这里插入图片描述
在这里插入图片描述
这段代码并不是一个可用的例子,因为他分配完了所有的RAM,导致没有空间容纳其他的变量。
当程序进行链接时,链接器会自动给每个变量分配空间。如图B所示,链接器将变量分配到了RAM1中,而RAM1剩余的部分和RAM2/3交由Heap5进行分配。但是,这样会产生一个问题:Heap5定义RAM1_START_ADDRESS起始地址为0x00010000,而这部分会与链接器分配的变量空间重叠,导致严重错误。

为了避免以上情况的发生,可以采用如下代码,会更加简洁并且可维护。
在这里插入图片描述
由于链接器到底使用了多少空间我们不便得知,若非空间非常有限,我们可以忽略RAM1这块空间,Heap5只指定RAM2,RAM3来分配。好处是:

  • 没必要知道链接器占用空间
  • 链接器自动分配HeapRegion_t的地址
  • 即使RAM1被链接器占用,也不会导致重叠
  • 如果ucHeap太大,应用不会工作

Heap 使用上的相关函数 (Heap Related Utility Functions)

The xPortGetFreeHeapSize()

这个函数可以用来优化堆栈大小。
比如说,在所有的内核对象都被创建以后,调用这个函数,返回值n就是剩余的堆栈大小。这样我们就可以将configTOTAL_HEAP_SIZE的数值减小n个
在这里插入图片描述

The xPortGetMinimumEverFreeHeapSize()

这个函数返回的是,应用从开始运行到当前时刻,发生过的最小的未分配空间的字节大小。这个函数只有当启用Heap4/5才有效。
比如说,返回200,代表从应用开始运行到当前,这其中某一时刻仅剩余200字节的空间了。这个能帮助我们及时调整堆栈大小,避免空间用尽。
在这里插入图片描述
在这里插入图片描述

Malloc Failed Hook Functions 内存分配失败的钩子函数

这个函数需要配置宏定义configUSE_MALLOC_FAILED_HOOK = 1开启。并且需要定义函数在这里插入图片描述
开启HOOK后,当分配失败时,pvPortMalloc() 返回 NULL并调用钩子函数

发布了85 篇原创文章 · 获赞 17 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/lun55423/article/details/105656073
今日推荐