内存分配的种类
本文转载自https://zhuanlan.zhihu.com/p/115276865
堆栈的关系

如上图的示例程序所示,全局变量和常量属于静态区(Static),由编译器事先分配好,生命周期贯穿整个程序;函数的参数值,局部变量的值属于栈(Stack),由编译器自动分配和释放;程序员用malloc函数动态请求分配的内存空间属于堆(Heap)。值得注意的是,如果在动态分配的内存用完之后忘记使用free函数释放内存,则会导致内存泄漏,并且当堆和栈无止境的增长到互相覆盖对方区域时则会出现很多无法预料的问题。程序可能运行运行着就跑飞了。
FreeRtos 任务空间构成

如上图所示,当调用FreeRTOS的创建任务API函数xTaskCreate()时,FreeRTOS会在堆中开辟出一块空间,用于存放任务的控制信息TCB块和栈区Stack用于储存任务相关的变量。图中创建的两个任务Task1和Task2都有各自独立的内存空间,互相独立。如果想在静态区建立任务的话可以调用xTaskCreateStatic()函数。
- 注意,程序运行过程中动态创建的线程的内存位置在堆中,堆中分配的空间包括程序自身的TCB块和自身的栈,也就是程序的Stack
TCB块的大小
TCB块的大小取决于FreeRTOSConfig.h头文件中的设置
在最小的设置下TCB块的大小是96字节
如果configUSE_TASK_NOTIFICATIONS是1的话再增加8个字节
如果configUSE_TRACE_FACILITY是1的话再增加8个字节
如果configUSE_MUTEXES是1的话再增加8个字节
上一章节中的任务TCB块因为这三个选项都为1,所以大小为96+8+8+8=120字节。
总结下来 任务占用字节数 = TCB_size + (4 x Task stack size)
TCB块的大小
TCB块的大小取决于FreeRTOSConfig.h头文件中的设置
- 在最小的设置下TCB块的大小是96字节
- 如果configUSE_TASK_NOTIFICATIONS是1的话再增加8个字节
- 如果configUSE_TRACE_FACILITY是1的话再增加8个字节
- 如果configUSE_MUTEXES是1的话再增加8个字节
上一章节中的任务TCB块因为这三个选项都为1,所以大小为96+8+8+8=120字节。
总结下来 任务占用字节数 = TCB_size + (4 x Task stack size)
MSP和PSP栈指针
在FreeRTOS中维护着两个栈的指针,分别是MSP主堆栈指针(Main stack pointer)和PSP进程堆栈指针(Process stack pointer)。
两个栈指针的区别是
MSP指针
- 用于操作内核以及处理异常和中断
- 由编译器分配
PSP指针
用于每个任务的独立的栈指针
在任务调度上下文切换(context switch)中,PSP会初始化为相对应的任务的栈指针,如下图所示

通常MSP指针用于系统内核和中断服务函数,PSP指针用于用户的任务。
Heap_1
本方案适用于小型的嵌入式系统,并且这个系统只能在调度器启动之前创建任务和其它内核对象。内存只需要在程序启动调度器前采用first fit算法对内存进行动态分配,之后任务的内存分配在程序的运行周期中保持不变并且无法被释放。heap_1.c实现了一个基础版本的pvPortMalloc函数,并没有实现vPortFree这个函数。如果系统运行后不用删除任务或者内核对象就可以采用这个方案。一些不需要动态分配内存的安全相关的系统也可以采用这个方案,因为这个方案是可确定性的(deterministic)所以不会导致内存碎片化。这个方案中堆由一个数组实现,数组的大小由FreeRTOSConfig.h文件中configTOTAL_HEAP_SIZE定义

示例
- 内存分配示例如下,A表示没有任何任务创建时的内存;B表示一个任务(每个任务有自己的TCB块和栈区)被创建时的内存分配情况;C表示三个任务被创建时的分配情况。

Heap_2
这个方案用于保持FreeRTOS的向下兼容性,并不推荐使用。内存管理也由一个数组实现,大小由FreeRTOSConfig.h文件中configTOTAL_HEAP_SIZE定义。它通过一套 优化算法 (best fit algorithm)对内存进行分配,并允许释放内存。Heap_4是Heap_2的功能强化版本。
best fit 算法确保pvPortMalloc函数分配大小最接近所需要字节的内存空间。它会对大的内存块进行分割,但无法合并相连的内存块。Heap_2适用于重复添加和删除相同任务的系统,但这种系统应该十分少见。
实例
- 内存分配示例如下,A表示有三个任务被创建时的内存分配情况;B表示有一个任务被删除时的分配情况,此时有两个小的内存块空闲出来;C表示另一个相同任务被创建时的分配情况,因为这个对TCB块和栈区大小的要求和之前被删除任务的大小一样,best fit 算法便把之前被释放的内存块分配给它。

Heap_3
本方案使用标准库里的malloc和free函数,所以堆的大小由链接器配置决定,不受configTOTAL_HEAP_SIZE大小影响。因为这个方案使用的场景不多,所以在这里不作详细介绍。
实例

可以看到Heap_3使用标准库里的malloc和free函数对任务进行内存分配和释放
Heap_5
这个方案使用类似于Heap_4的内存分配技术,但不同于Heap_4只用一个连续的数组表示堆,Heap_5可以用不同的数组空间对内存进行分配。在本方案要使用vPortDefineHeapRegions这个函数对不同的数组进行申明。
Heap_5 示例
下面这张图定义了三个不同内存空间用于模拟堆。程序如下,首先定义了每个区域的开始地址START_ADDRESS和空间大小SIZE,然后用一个结构体xHeapRegions指向了这些区域,最后使用vPortDefineHeapRegions函数申明堆的空间。


- 上图为声明空间的举例
内存管理相关函数
size_t xPortGetFreeHeapSize( void );这个函数会返回当前堆中的空闲空间,可以用来优化堆空间大小。比如在系统运行起来后调用xPortGetFreeHeapSize如果返回了3000,就可以把堆大小configTOTAL_HEAP_SIZE设置为3000。
size_t xPortGetMinimumEverFreeHeapSize( void );这个函数会返回在系统运行过程中堆空间的最小空闲空间,如果最小空闲空间很小的话可以考虑提高堆大小configTOTAL_HEAP_SIZE的值。
void vApplicationMallocFailedHook( void );这是一个回调函数,需要用户自己实现。如果配置文件中configUSE_MALLOC_FAILED_HOOK 设置为1的话,当堆分配内存失败时会调用此函数。用户可以在此函数中进行错误处理。