FreeRTOS 创建任务、队列、信号量等的时候有两种方法, 一种是动态的申请所需的 RAM 。 一种是由用户自行定义所需的 RAM,这种方法也叫静态方法,使用静态方法的函数一般以 “Static”结尾, 比如任务创建函数 xTaskCreateStatic(),使用此函数创建任务的时候需要由用户定义任务堆栈,本章我们不讨论这种静态方法。
使用动态内存管理的时候 FreeRTOS 内核在创建任务、队列、信号量的时候会动态的申请 RAM。标准 C 库中的 malloc()和 free()也可以实现动态内存管理, 但是如下原因限制了其使用:
● 在小型的嵌入式系统中效率不高。
● 会占用很多的代码空间。
● 它们不是线程安全的。
● 具有不确定性,每次执行的时间不同。
● 会导致内存碎片。
● 使链接器的配置变得复杂。
不同的嵌入式系统对于内存分配和时间要求不同,因此一个内存分配算法可以作为系统的可选选项。FreeRTOS 将内存分配作为移植层的一部分, 这样 FreeRTOS 使用者就可以使用自己 的合适的内存分配方法。当内核需要 RAM 的时候可以使用 pvPortMalloc()来替代 malloc()申请内存,不使用内存的时候可以使用 vPortFree()函数来替代 free()函数释放内存。函数 pvPortMalloc()、vPortFree()与函 数 malloc() 、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 。
总结:heap_ 1 最简单,但是只能申请 内存, 不能释放。 heap_2 提供了内存释放函数, 用户代码也可以直接调用函数 pvPortMalloc()和 vPortFree()来申请和释放内存, 但是 heap_2 会导致内存碎片的产生! heap_3 是对标准 C 库中的 函数 malloc()和 free()的简单封装,并且提供了线程保护。 heap_4 相对与 heap_2 提供了内存合 并功能,可以降低内存碎片的产生,我们移植 FreeRTOS 的时候就选择了 heap_4 。heap_5 基本 上和 heap_4 一样,只是 heap_5 支持内存堆使用不连续的内存块。
heap1
1、适用于那些一旦创建好任务、信号量和队列就再也不会删除的应用,实际上大多数的 FreeRTOS 应用都是这样的。
2、具有可确定性(执行所花费的时间大多数都是一样的),而且不会导致内存碎片。
3、代码实现和内存分配过程都非常简单, 内存是从一个静态数组中分配到的, 也就是适合 于那些不需要动态内存分配的应用。
heap2
1、可以使用在那些可能会重复的删除任务、队列、信号量等的应用中, 要注意有内存碎片 产生!
2、如果分配和释放的内存 n 大小是随机的,那么就要慎重使用了,比如下面的示例:
● 如果一个应用动态的创建和删除任务,而且任务需要分配的堆栈大小都是一样的, 那么 heap_2 就非常合适。如果任务所需的堆栈大小每次都是不同,那么 heap_2 就 不适合了, 因为这样会导致内存碎片产生,最终导致任务分配不到合适的堆栈! 不 过 heap_4 就很适合这种场景了。
● 如果一个应用中所使用的队列存储区域每次都不同, 那么 heap_2 就不适合了, 和上 面一样,此时可以使用 heap_4。
● 应用需要调用 pvPortMalloc()和 vPortFree()来申请和释放内存,而不是通过其他 FreeRTOS 的其他 API 函数来间接的调用,这种情况下 heap_2 不适合。
3、如果应用中的任务、队列、信号量和互斥信号量具有不可预料性(如所需的内存大小不能
确定, 每次所需的内存都不相同, 或者说大多数情况下所需的内存都是不同的)的话可能会导致 内存碎片。虽然这是小概率事件,但是还是要引起我们的注意!
4、具有不可确定性,但是也远比标准 C 中的 mallo()和 free()效率高!
heap_2 基本上可以适用于大多数的需要动态分配内存的工程中, 而 heap_4 更是具有将内存 碎片合并成一个大的空闲内存块(就是内存碎片回收)的功能。
heap3
1、需要编译器提供一个内存堆,编译器库要提供 malloc()和 free()函数。比如使用 STM32 的话可以通过修改启动文件中的 Heap_Size 来修改内存堆的大小,如图 1 所示。
2、具有不确定性
3 、可能会增加代码量。
注意,在 heap_3 中 configTOTAL_HEAP_SIZE 是没用的!
heap4
1 、可以用在那些需要重复创建和删除任务、队列、信号量和互斥信号量等的应用中。
2、不会像 heap_2 那样产生严重的内存碎片,即使分配的内存大小是随机的。
3、具有不确定性,但是远比 C 标准库中的 malloc()和 free()效率高。
heap_4 非常适合于那些需要直接调用函数 pvPortMalloc()和 vPortFree()来申请和释放内存 的应用,注意,我们移植 FreeRTOS 的时候就选择的 heap_4!
heap_4 也使用链表结构来管理空闲内存块,链表结构体与 heap_2 一样。heap_4 也定义了 两个局部静态变量 xStart 和 pxEnd 来表示链表头和尾,其中 pxEnd 是指向 BlockLink_t 的指针。
heap5
heap_5 使用了和 heap_4 相同的合并算法,内存管理实现起来基本相同,但是 heap_5 允许 内存堆跨越多个不连续的内存段。比如 STM32 的内部 RAM 可以作为内存堆,但是 STM32 内 部 RAM 比较小, 遇到那些需要大容量 RAM 的应用就不行了, 如音视频处理。不过 STM32 可 以外接 SRAM 甚至大容量的 SDRAM,如果使用 heap_4 的话你就只能在内部 RAM 和外部 SRAM 或 SDRAM 之间二选一了,使用 heap_5 的话就不存在这个问题,两个都可以一起作为 内存堆来用。
如果使用 heap_5 的话,在调用 API 函数之前需要先调用函数 vPortDefineHeapRegions ()来 对内存堆做初始化处理,在 vPortDefineHeapRegions()未执行完之前禁止调用任何可能会调用 pvPortMalloc()的 API 函数!比如创建任务、信号量、队列等函数。