freertos堆栈

注意堆栈空间分配方向相反,若两区域相互覆盖会出现无法预料的问题。
任务如何分配空间

当调用创建任务api函数xTaskCreate()时,freertos会在堆中开辟出一片空间,用于存放任务控制信息TCB块和用于存储任务相关变量的栈区Stack。
使用xTaskCreateStatic()在静态区创建任务
TCB的大小构成
TCB信息包括:任务当前执行到哪里了(程序计数器、堆栈指针)、任务的状态(运行、就绪、阻塞、挂起)、任务的优先级、任务的名字、任务等待的事件(比如信号量、队列、通知)等等。
TCB的大小构成取决于FreeRTOSConfig.h头文件的设置,启用了哪些功能
- 最小设置下96字节
- configUSE_TASK_NOTIFICATIONS是1,增加8字节
- configUSE_TRACE_FACILITY是1,增加8字节
- configUSE_MUTEXES是1,增加8字节
| 参数 | 含义 | 目的 |
|---|---|---|
| configUSE_TASK_NOTIFICATIONS | 启用任务通知 | 轻量级任务间通信/同步,一个任务可以给另一个任务“发通知”(设置通知值/状态),另一个任务可以“等待通知”(阻塞直到收到通知 |
| configUSE_TRACE_FACILITY | 启用跟踪调试设施 | 系统调试分析(任务状态、事件) |
| configUSE_MUTEXES | 启用互斥锁 | 启用互斥锁 |
MSP和PSP栈指针
MSP主堆栈指针(Main stack pointer):
- 系统启动时默认使用的堆栈指针
- 用于处理中断和异常
- 操作系统内核(如FreeRTOS内核)使用
PSP进程堆栈指针(Process stack pointer)
- 用于每个任务独立的栈指针
- 每个FreeRTOS任务都有自己的PSP堆栈

示例:
-
系统启动:使用MSP初始化所有硬件和FreeRTOS
-
任务切换:
- LED任务使用自己的PSP,包含其局部变量ledState
- 温度任务使用自己的PSP,包含局部变量temperature和数组samples
- 任务切换时,FreeRTOS保存当前PSP和恢复下一个任务的PSP
-
中断处理:
- 当按键中断发生时,无论哪个任务在运行,处理器立即切换到MSP
- 中断处理完毕后,返回到被中断的任务(使用其PSP)或切换到更高优先级的任务
内存池分配
内存池分配是指在程序编译阶段就分配一定数量的内存块留作备用。当有新的内存需求时,就从内存块分出一部分内存块,若内存块不够了就继续申请新的内存。
当FreeRTOS需要RAM的时候,它会调用pvPortMalloc这个函数而不是Malloc这个系统函数;当它需要释放内存的时候,会调用vPortFree这个函数而不是free这个系统函数。各提供了5种实现方案(heap_1.c,heap_2.c,heap_3.c,heap_4.c,heap_5.c),同时支持定制。stm32cubemx默认采用4。
heap_1
适用于小型嵌入式系统。且只能在调度器启动之前创建任务和其他内核对象。
内存只需要在程序启动调度器前采用first fit算法对内存进行动态分配,之后任务的内存分配在程序的运行周期中保持不变并且无法被释放。
确定性的,不会导致内存碎片化,执行时间固定。
使用静态数组作为堆内存。数组大小由FreeRTOSConfig.h文件中ConfigTOTAL_HEAP_SIZE定义。
1 | xTaskCreate(myTask, "Task1", 100, NULL, 1, NULL); |

heap_2(不推荐使用?)
内存管理也由一个数组实现,大小由FreeRTOSConfig.h文件中configTOTAL_HEAP_SIZE定义
使用best fit算法对内存进行动态分配,允许释放内存。对大的内存块进行分割,但无法合并相连的内存块,容易导致内存的碎片化。
1 | void *buffer = pvPortMalloc(50); // 分配50字节 |

heap_3
使用标准C库的malloc() free()函数
添加了互斥锁使其线程安全,调用malloc函数和free函数的时候会临时挂起FreeRTOS的调度器。
1 | // 和标准C库函数名相同,但内部添加了互斥保护 |

heap_4
内存可以自由分配和合并空闲的内存块,并使得整体的性能最优。
使用"首次匹配"算法(使用第一个足够大的空闲块)
Heap_4的分配方案所需要时间是不确定性的,但速度要比malloc和free函数快。
1 | // 频繁分配释放也不会导致严重的内存碎片 |

heap_5
类似于Heap_4的内存分配技术,但不同于Heap_4只用一个连续的数组表示堆,Heap_5可以用不同的数组空间对内存进行分配。要使用vPortDefineHeapRegions这个函数对不同的数组进行申明。
1 | // 初始化多区域内存 |

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