目录
3.2 Heap_2(Heap1_Pro、提供删除,产生碎片)
后续的章节涉及这些内核对象: task、 queue、 semaphores 和 event group 等。为了
让 FreeRTOS 更容易使用,这些内核对象一般都是动态分配:用到时分配,不使用时释放。
使用内存的动态管理功能,简化了程序设计:不再需要小心翼翼地提前规划各类对象,简
化 API 函数的涉及,甚至可以减少内存的使用。
内存的动态管理是 C 程序的知识范畴,并不属于 FreeRTOS 的知识范畴,但是它跟
FreeRTOS 关系是如此紧密,所以我们先讲解它。
在 C 语言的库函数中,有 mallc、 free 等函数,但是在 FreeRTOS 中,它们不适用:
注意:我们经常"堆栈"混合着说,其实它们不是同一个东西:
堆: heap,就是一块空闲的内存,需要提供管理函数
栈: stack,函数调用时局部变量保存在栈中,当前程序的环境也是保存在栈中
适用场景:创建的任务、队列、信号量等不需要删除。
它只实现了 pvPortMalloc,没有实现 vPortFree。如果你的程序不需要删除内核对象,那么可以使用 heap_1:
它的实现原理很简单,首先定义一个大数组:
然后,对于 pvPortMalloc 调用时,从这个数组中分配空间。
FreeRTOS 在创建任务时,需要 2 个内核对象: task control block(TCB)、 stack。
使用 heap_1 时,内存分配过程如下图所示:
适用场景:频繁的创建和删除任务,且所创建的任务堆栈都相同,此时不会出现碎片化的问题。
Heap_2 之所以还保留,只是为了兼容以前的代码。新设计中不再推荐使用 Heap_2。
建议使用 Heap_4 来替代 Heap_2,更加高效。
Heap_2 也是在数组上分配内存,跟 Heap_1 不一样的地方在于:
最佳匹配算法:
与 Heap_4 相比, Heap_2 不会合并相邻的空闲内存,所以 Heap_2 会导致严重的"碎片
化"问题。
但是,如果申请、分配内存时大小总是相同的,这类场景下 Heap_2 没有碎片化的问
题。所以它适合这种场景:频繁地创建、删除任务,但是任务的栈大小都是相同的(创建任
务时,需要分配 TCB 和栈, TCB 总是一样的)。
虽然不再推荐使用 heap_2,但是它的效率还是远高于 malloc、 free。
使用 heap_2 时,内存分配过程如下图所示:
Heap_3 使用标准 C 库里的 malloc、 free 函数,所以堆大小由链接器的配置决定,配
置项 configTOTAL_HEAP_SIZE 不再起作用。直接用C库的 malloc - free
C 库里的 malloc、 free 函数并非线程安全的, Heap_3 中先暂停 FreeRTOS 的调度器,
再去调用这些函数,使用这种方法实现了线程安全。(用 malloc 和 free 的效率不高),因为heap1和2是在数组中分配内存操作,数组操作比较快和效率高。
缺点:malloc-free存在碎片问题,一样也存在分配失败的风险。
跟 Heap_1、 Heap_2 一样, Heap_4 也是使用大数组来分配内存。
Heap_4 使用首次适应算法(first fit)来分配内存。它还会把相邻的空闲内存合并为一个
更大的空闲内存,这有助于较少内存的碎片问题。(heap2的升级版)
首次适应算法:
Heap_4 会把相邻空闲内存合并为一个大的空闲内存,可以减少内存的碎片化问题。适
用于这种场景:频繁地分配、释放不同大小的内存。
Heap_4 的使用过程举例如下:
Heap_4 执行的时间是不确定的,但是它的效率高于标准库的 malloc、 free。
Heap_5 分配内存、释放内存的算法跟 Heap_4 是一样的。
相比于 Heap_4, Heap_5 并不局限于管理一个大数组:它可以管理多块、分隔开的内存。
在嵌入式系统中,内存的地址可能并不连续,这种场景下可以使用 Heap_5。
既然内存时分隔开的,那么就需要进行初始化:确定这些内存块在哪、多大:
怎么指定一块内存?使用如下结构体:
怎么指定多块内存?使用一个 HeapRegion_t 数组,在这个数组中,低地址在前、高地址在后。
vPortDefineHeapRegions 函数原型如下:
把 xHeapRegions 数组传给 vPortDefineHeapRegions 函数,即可初始化 Heap_5。