FreeRTOS基础篇(五)——内存管理

前言:
   本篇文章介绍FreeRTOS的移植方法,以stm32f103举例。

1 介绍

  FreeRTOS作为一个轻量级的实时操作系统(RTOS),在资源受限的嵌入式系统中广泛应用。由于其不依赖标准c库的动态内存管理(如 malloc / free),FreeRTOS提供了多种灵活且可配置的内存管理方案,以满足不同应用场景对性能、安全性和内存碎片的要求。
  FreeRTOS不直接使用c标准库中的 malloc()free(),而是提供了一套可移植、可配置的内存管理机制,以适应资源受限的嵌入式环境。其核心思想是:将一块静态定义的内存区域作为堆(heap),由 FreeRTOS 的内存管理模块进行分配和释放。
  内存管理主要是为了解决内存碎片化的问题。

  • 碎片成因:频繁分配/释放不同大小的内存块,导致虽然总空闲内存足够,但无法分配大块连续内存。
  • 缓解措施:
    • 使用 heap_4heap_5(支持合并空闲块)。
    • 尽量使用固定大小的内存池(如静态队列、任务栈预分配)。
    • 避免频繁创建/删除内核对象。

2 五种内存管理方案(Heap_1 至 Heap_5)

  FreeRTOS提供了5种不同的内存管理实现,位于 Source/portable/MemMang/ 目录下:

2.1 heap_1.c

  最简单,仅支持分配,不支持释放

  • 特点:
    • 只能分配内存,不能释放。
    • 所有分配的内存一直存在,直到系统关闭。
  • 适用场景:
    • 所有任务、队列、信号量在启动时创建,运行期间不再删除。
  • 优点:简单、无碎片、效率高。
  • 缺点:无法动态释放内存。

适合小型固件,所有资源在初始化阶段创建。

2.2 heap_2.c

  支持释放,使用首次适配(First Fit)算法

  • 特点:
    • 支持 pvPortMalloc()vPortFree()
    • 使用“首次适配”策略从空闲块中分配内存。
    • 不会合并相邻的空闲块,容易产生内存碎片。
  • 适用场景:
    • 动态创建/删除任务或队列,但频率不高。

注意:不适合频繁分配/释放不同大小内存的场景。

2.3 heap_3.c

  使用标准 malloc()free()

  • 特点:
    • 包装了c库的 malloc()free()
    • 需要链接器提供堆空间(如 _sbrk 实现)。
    • 受限于底层c库的实现(可能不可重入、非线程安全)。
  • 优点:充分利用系统堆。
  • 缺点:
    • 需确保 malloc/free 是线程安全的(通常需加锁)。
    • 在某些嵌入式平台上不可靠或不可用。

常用于带有完整 C 运行时环境的平台(如 Linux 模拟环境)。

2.4 heap_4.c

  支持释放+合并空闲块(最佳选择之一)

  • 特点:
    • 基于 heap_2 改进,会合并相邻的空闲块,减少碎片。
    • 使用“首次适配”策略。
    • 分配大块内存更高效。
  • 优点:
    • 支持动态分配与释放。
    • 内存利用率高,适合长期运行的应用。
  • 推荐使用场景:
    • 大多数实际项目推荐使用 heap_4

注意:需要定义 configTOTAL_HEAP_SIZE 来指定堆大小。

2.5 heap_5.c

  支持多区域堆(适用于非连续内存)

  • 特点:
    • 允许堆分布在多个不连续的内存区域(例如:SRAM1 + SRAM2)。
    • 需要调用 vPortDefineHeapRegions() 显式定义内存区域。
    • 使用方式类似 heap_4,支持合并空闲块。
  • 适用场景:
    • MCU 有多个 RAM 区域(如 STM32 的 CCM RAM、DTCM RAM 等)。

  示例:

1
2
3
4
5
6
7
const HeapRegion_t xHeapRegions[] = {
{ (uint8_t*)0x20000000UL, 0x10000 }, // 64KB SRAM1
{ (uint8_t*)0x10000000UL, 0x8000 }, // 32KB CCM RAM
{ NULL, 0 } // 结束标记
};

vPortDefineHeapRegions(xHeapRegions);

注意:heap_5.c 要求在调用任何内存分配函数(如 pvPortMalloc)之前,必须先调用 vPortDefineHeapRegions() 初始化堆区域。

3 配置与使用方法

3.1 选择 heap 实现

  在项目中,只能包含一个 .c 文件heap_1.cheap_5.c 中选其一),通过编译时决定使用哪种策略。选择的建议整理如下。

场景 推荐方案
所有任务/队列静态创建,不删除 heap_1(最安全高效)
动态创建但不频繁释放 heap_2(谨慎使用)
需要完整 malloc/free 支持 heap_3(确保线程安全)
通用嵌入式应用(推荐) heap_4(首选)
多块非连续 RAM 区域 heap_5(高级用法)

3.2 配置项(FreeRTOSConfig.h

  • configTOTAL_HEAP_SIZE:定义可用堆的总大小(仅用于 heap_1/2/4)。
1
#define configTOTAL_HEAP_SIZE ((size_t)(1024 * 32)) // 32KB
  • 对于 heap_5,不需要此宏,因为堆大小由 vPortDefineHeapRegions() 指定。

4 API

  FreeRTOS提供以下内存管理 API:

函数 说明
void* pvPortMalloc(size_t xSize) 分配指定字节的内存(等价于 malloc)
void vPortFree(void* pv) 释放内存(等价于 free)
size_t xPortGetFreeHeapSize(void) 获取当前剩余堆空间(heap_4 和 heap_5 支持)
size_t xPortGetMinimumEverFreeHeapSize(void) 获取历史最小空闲堆大小(用于监控碎片)

注意:xPortGetMinimumEverFreeHeapSize() 只在 heap_4.cheap_5.c 中有效,需启用 configUSE_TRACE_FACILITYconfigUSE_STATS_FORMATTING_FUNCTIONS(或相关配置)。

5 查看内存使用情况

  FreeRTOS 提供了一些工具帮助分析内存使用情况。

1
2
3
4
5
6
// 查看剩余内存
size_t freeHeap = xPortGetFreeHeapSize();
size_t minFreeHeap = xPortGetMinimumEverFreeHeapSize();

printf("Free Heap: %d bytes\n", (int)freeHeap);
printf("Min Ever Free Heap: %d bytes\n", (int)minFreeHeap);

  可用于实时监控内存使用,判断是否存在内存泄漏或碎片化问题。
   minFreeHeap 越小,说明系统运行过程中内存碎片越严重,内存使用效率越低。反之, minFreeHeap 较大则表示系统在运行过程中保持了较多的可用内存,内存管理较为高效。因此,监控 minFreeHeap 的值有助于评估和优化FreeRTOS应用的内存使用情况。