heap-and-stack
2025-09-07 19:41:33

freertos堆栈

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

任务如何分配空间

任务如何分配空间
当调用创建任务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 PSP

示例:

  1. 系统启动:使用MSP初始化所有硬件和FreeRTOS

  2. 任务切换:

    • LED任务使用自己的PSP,包含其局部变量ledState
    • 温度任务使用自己的PSP,包含局部变量temperature和数组samples
    • 任务切换时,FreeRTOS保存当前PSP和恢复下一个任务的PSP
  3. 中断处理:

    • 当按键中断发生时,无论哪个任务在运行,处理器立即切换到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
2
xTaskCreate(myTask, "Task1", 100, NULL, 1, NULL);
xQueueCreate(10, sizeof(uint32_t));

heap1

heap_2(不推荐使用?)

内存管理也由一个数组实现,大小由FreeRTOSConfig.h文件中configTOTAL_HEAP_SIZE定义
使用best fit算法对内存进行动态分配,允许释放内存。对大的内存块进行分割,但无法合并相连的内存块,容易导致内存的碎片化。

1
2
3
void *buffer = pvPortMalloc(50);  // 分配50字节
// 使用内存...
vPortFree(buffer); // 释放内存

heap_2

heap_3

使用标准C库的malloc() free()函数
添加了互斥锁使其线程安全,调用malloc函数和free函数的时候会临时挂起FreeRTOS的调度器。

1
2
3
// 和标准C库函数名相同,但内部添加了互斥保护
void *data = malloc(100);
free(data);

heap_3

heap_4

内存可以自由分配和合并空闲的内存块,并使得整体的性能最优。
使用"首次匹配"算法(使用第一个足够大的空闲块)
Heap_4的分配方案所需要时间是不确定性的,但速度要比malloc和free函数快。

1
2
3
4
5
6
// 频繁分配释放也不会导致严重的内存碎片
for(int i=0; i<1000; i++) {
void *temp = pvPortMalloc(random_size());
// 使用内存...
vPortFree(temp);
}

heap_4

heap_5

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

1
2
3
4
5
6
7
8
9
10
11
12
// 初始化多区域内存
// 添加内部SRAM区域
HeapRegion_t xHeapRegions[] = {
{ (uint8_t*)0x20000000, 0x10000 }, // 内部SRAM: 64KB
{ (uint8_t*)0x60000000, 0x80000 }, // 外部SRAM: 512KB
{ NULL, 0 } // 终止标记
};
vPortDefineHeapRegions(xHeapRegions);

// 之后正常使用
void *buffer = pvPortMalloc(1000);

heap5

内存管理相关函数

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的话,当堆分配内存失败时会调用此函数。用户可以在此函数中进行错误处理。

2025-09-07 19:41:33
下一页