目录
1.1 为什么要自己实现内存管理
1.2 FreeRTOS 的内存管理方法
1.2.1 Heap_1
1.2.1 Heap_2
1.2.1 Heap_3
1.2.1 Heap_4
1.2.1 Heap_5
温故而知新
FreeRTOS 中的内核对象:task、queue、semaphores 和 event group 等一般都是动态分配,用到时分配,不使用时释放。使用内存的动态管理功能,简化了程序设计,不再需要小心翼翼地提前规划各类对象,简化 API 函数的涉及,甚至可以减少内存的使用。
内存的动态管理是 C 程序的知识范畴,并不属于 FreeRTOS 的知识范畴,但是它跟 FreeRTOS 的关系是如此地紧密,所以我们先讲解它。
注意:我们经常将“堆栈”混合着说,其实它们不是同一个东西:
free:用完后,再把它标记为“空闲的”,可以再次使用
FreeRTOS 中的内存管理的接口函数为:pvPortMalloc、vPortFree,对应 C 库的 malloc、free。文件在 FreeRTOS/Source/portable/MemMang 下,它也是放在 portable 目录下,表示你可以提供自己的函数
源码中默认提供了5个文件,对应内存管理的5种方法。
参考文章:FreeRTOS 说明书吐血整理【适合新手+入门】
文件 | 优点 | 缺点 |
heap_1.c | 分配简单,时间确定 | 只分配、不回收 |
heap_2.c | 动态分配、最佳分配 | 碎片、时间不定 |
heap_3.c | 调用标准库函数 | 速度慢、时间不定 |
heap_4.c | 相邻空闲内存可合并 | 可解决碎片问题、时间不定 |
heap_5.c | 在 heap_4基础上支持分隔的内存块 | 可解决碎片问题、时间不定 |
它只实现了 pvPortMalloc,没有实现 vPortFree。
如果你的程序不需要删除内核对象,那么可以使用 heap_1:
它的实现原理很简单,首先定义一个大数组:
/* Allocate the memory for the heap. */
##if ( configAPPLICATION_ALLOCATED_HEAP == 1 )
/* The application writer has already defined the array used for the RTOS
* heap - probably so it can be placed in a special segment or address. */
extern uint8_t ucHeap[ configTOTAL_HEAP_SIZE ];
##else
static uint8_t ucHeap[ configTOTAL_HEAP_SIZE ];
##endif /* configAPPLICATION_ALLOCATED_HEAP */
然后,对于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 函数并非线程安全的,Heap_3 中暂停 FreeRTOS 的调度器,再去调用这些函数,使用这种方法实现了线程安全。
跟Heap_1、Heap_2一样,Heap_4也是使用大数组来分配内存。
Heap_4 使用首次适应算法(first fit) 来分配内存。它还会把相邻的空闲内存合并为一个更大的空闲内存,这有助于较少内存的碎片问题。
首次适应算法:
Heap_4 会把相邻空闲内存合并为一个大的空闲内存,可以减少内存的碎片化问题。适用于这种场景:频繁地分配、释放不同大小的内存。
Heap_4 的使用过程举例如下:
Heap_4执行的时间是不确定的,但是它的效率高于标准库的malloc、free。
Heap_5 分配内存、释放内存的算法跟Heap_4是一样的。
相比于 Heap_4,Heap_5 并不局限于管理一个大数组:它可以管理多块、分隔开的内存。
在嵌入式系统中,内存的地址可能并不连续,这种场景下可以使用 Heap_5。
既然内存时分隔开的,那么就需要进行初始化:确定这些内存块在哪、多大:
怎么指定一块内存?使用如下结构体:
typedef struct HeapRegion
{
uint8_t *pucStartAddress; // 起始地址
size_t xSizeInBytes; // 大小
} HeapRegion_t;
怎么指定多块内存?使用一个 HeapRegion_t 数组,在这个数组中,低地址在前、高地址在后。
比如:
HeapRegion_t xHeapRegions[] =
{
{ ( uint8_t * ) 0x80000000UL, 0x10000 }, // 起始地址0x80000000,大小0x10000
{ ( uint8_t * ) 0x90000000UL, 0xa0000 }, // 起始地址0x90000000,大小0xa0000
{ NULL, 0 } // 表示数组结束
};
vPortDefineHeapRegions 函数原型如下:
void vPortDefineHeapRegions( const HeapRegion_t * const pxHeapRegions );
把 xHeapRegions 数组传给 vPortDefineHeapRegions 函数,即可初始化Heap_5。
=========================================================================
以上相关资料来源于“韦东山的FreeRTOS”的教程,这里只是本人的个人学习笔记,侵权删。
=========================================================================