一、为什么要自己实现内存管理

为了让FreeRTOS更容易使用,这些内核对象一般都是动态分配:用到时分配,不使用时释放。 使用内存的动态管理功能,简化了程序设计:不再需要小心翼翼地提前规划各类对象,简化API函数的涉及,甚至可以减少内存的使用。
在C语言的库函数中,有mallc、 free等函数,但是在FreeRTOS中,它们不适用
1. 不适合用在资源紧缺的嵌入式系统中

2.这些函数的实现过于复杂、占据的代码空间太大

3. 并非线程安全的(thread-safe)

4. 运行有不确定性:每次调用这些函数时花费的时间可能都不相同

5. 内存碎片化

6.使用不同的编译器时,需要进行复杂的配置

7. 有时候难以调试

堆, heap,就是一块空闲的内存,需要提供管理函数
malloc:从堆里划出一块空间给程序使用
free:用完后,再把它标记为"空闲"的,可以再次使用
栈, stack,函数调用时局部变量保存在栈中,当前程序的环境也是保存在栈中
可以从堆中分配一块空间用作栈

二、Freertos中5种内存管理方法

1.Heap_1

它只实现了pvPortMalloc,没有实现vPortFree。

如果你的程序不需要删除内核对象,那么可以使用heap_1:
实现最简单

没有碎片问题

一些要求非常严格的系统里,不允许使用动态内存,就可以使用 heap_1
FreeRTOS在创建任务时,需要2个内核对象: task control block(TCB)、 stack。使用heap_1时,内存分配过程如下图所示

2.Heap_2

Heap_2也是在数组上分配内存,跟Heap_1不一样的地方在于:
Heap_2 使用最佳匹配算法(best fit)来分配内存
它支持 vPortFree
与Heap_4相比, Heap_2不会合并相邻的空闲内存,所以Heap_2会导致严重的"碎片化"问题。

但是,如果申请、分配内存时大小总是相同的,这类场景下Heap_2没有碎片化的问题。所以它适合这种场景:频繁地创建、删除任务,但是任务的栈大小都是相同的(创建任务时,需要分配TCB和栈, TCB总是一样的)。
使用heap_2时,内存分配过程如下图所示:

A:创建了 3 个任务

B:删除了一个任务,空闲内存有 3 部分:顶层的、被删除任务的 TCB 空间、被删除任务的 Stack 空间

C:创建了一个新任务,因为 TCB、栈大小跟前面被删除任务的 TCB、栈大小一致,所以刚好分配到原来的内存

3.Heap_3

Heap_3 使用标准 C 库里的 malloc、 free 函数,所以堆大小由链接器的配置决定,配置项 configTOTAL_HEAP_SIZE 不再起作用。

C库里的malloc、 free函数并非线程安全的, Heap_3中先暂停FreeRTOS的调度器,再去调用这些函数,使用这种方法实现了线程安全。

4.Heap_4

跟 Heap_1、 Heap_2 一样, Heap_4 也是使用大数组来分配内存。
Heap_4使用首次适应算法(first fit)来分配内存。它还会把相邻的空闲内存合并为一个更大的空闲内存,这有助于较少内存的碎片问题。

首次适应算法:
1.假设堆中有 3 块空闲内存: 5 字节、 200 字节、 100 字节

2.pvPortMalloc 想申请 20 字节

3. 找出第 1 个能满足 pvPortMalloc 的内存: 200 字节

4. 把它划分为 20 字节、 180 字节

5.返回这 20 字节的地址

6.剩下的 180 字节仍然是空闲状态,留给后续的 pvPortMalloc 使用
Heap_4会把相邻空闲内存合并为一个大的空闲内存,可以较少内存的碎片化问题。适用于这种场景:频繁地分配、释放不同大小的内存。

5.Heap_5

Heap_5 分配内存、释放内存的算法跟 Heap_4 是一样的。

相比于Heap_4, Heap_5并不局限于管理一个大数组:它可以管理多块、分隔开的内存。在嵌入式系统中,内存的地址可能并不连续,这种场景下可以使用Heap_5。
既然内存时分隔开的,那么就需要进行初始化,确定这些内存块在哪、多大。

三、总结

HEAP1、HEAP2、HEAP4都是使用一个大数组来分配内存,区别如下:

1.HEAP1只支持分配内存,没有实现释放内存

2.HEAP2实现了释放内存,不支持碎片内存合并

3.HEAP4支持合并相邻的空闲内存为一个更大的内存

HEAP3使用的是标准c库里面的函数

HEAP5在HEAP4的基础上,可以管理多块、分隔开的内存。