4 内存管理
4 内存管理
FreeRTOS 的内存管理文件位于:
Middlewares/Third_Party/FreeRTOS/Source/portable/MemMang
该目录下提供了 5 种内存管理实现方式:
heap_1.cheap_2.cheap_3.cheap_4.cheap_5.c
这些文件本质上是 FreeRTOS 提供的 5 种动态内存分配方案,用来实现内核中的动态申请和释放函数:
pvPortMalloc()vPortFree()
1 为什么需要内存管理
在 FreeRTOS 中,很多内核对象在创建时都需要占用 RAM,例如:
- 任务控制块 TCB
- 任务栈
- 队列
- 信号量
- 互斥锁
- 软件定时器
- 事件组
这些对象如果采用动态创建方式,就需要先申请一块内存。
因此,FreeRTOS 必须提供一套内存管理机制。
例如:
xTaskCreate();
xQueueCreate();
xSemaphoreCreateBinary();这些函数在内部通常都会调用:
pvPortMalloc()来申请内存。
所以可以理解为:
内存管理机制决定了 FreeRTOS 运行时如何从 RAM 中分配空间给各种内核对象。
2 FreeRTOS中的两个内存管理函数
FreeRTOS 并不是直接使用标准 C 库中的 malloc() 和 free(),而是封装了自己的接口:
1. pvPortMalloc()
用于申请一块指定大小的内存。
2. vPortFree()
用于释放之前申请的内存。
其中:
pv表示返回值是指针类型Port表示与移植层相关Malloc和Free分别对应内存申请和释放
不同的 heap_x.c 文件,实际上就是这两个函数的不同实现方式。
3 为什么 FreeRTOS 不直接用 malloc/free
主要有以下几个原因:
1. 可移植性更强
不同平台、不同编译器的 malloc/free 实现差异较大,而 FreeRTOS 需要在很多硬件平台上运行,因此更适合自己统一管理。
2. 实时性要求
标准库的 malloc/free 不一定适合实时系统,执行时间可能不稳定,难以满足实时性要求。
3. 便于控制内存来源
FreeRTOS 可以把内核对象的动态内存统一放在一块预先定义好的静态数组中,便于管理和调试。
4. 可根据项目需求选择方案
有的项目不需要释放内存,有的项目需要防止碎片,有的项目要支持多个内存区域,因此 FreeRTOS 提供了多种 heap_x.c 方案供选择。
4 五种内存管理方式
| 内存管理方式 | 特点 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|---|
heap_1.c | 只支持内存申请,不支持内存释放;实现最简单;不会产生内存碎片 | 简单、稳定、执行效率高 | 申请后的内存无法回收,灵活性最差 | 系统运行过程中只创建对象、不删除对象,例如任务和队列在启动时一次性创建完成,后续不再释放 |
heap_2.c | 在 heap_1.c 基础上增加了内存释放功能;支持申请和释放;支持重复使用已释放内存;但不合并相邻空闲块 | 比 heap_1.c 更灵活 | 容易产生内存碎片,长期运行后内存利用率可能下降 | 需要动态创建和删除对象,但对内存碎片问题要求不高的场景 |
heap_3.c | 本质上直接封装标准 C 库的 malloc() 和 free();FreeRTOS 自己不再管理堆 | 使用简单;可与系统中其他使用 malloc/free 的模块统一管理 | 实时性不可控;不同平台行为差异较大;通常不推荐在嵌入式实时系统中优先使用 | 系统本身已有成熟标准库内存管理机制,或用于教学、验证、移植测试 |
heap_4.c | 支持申请和释放;支持合并相邻空闲内存块;可有效减轻内存碎片问题 | 功能完整;内存利用率高;比 heap_2.c 更实用;适合大多数中小型嵌入式项目 | 实现比前几种稍复杂 | 大多数基于 FreeRTOS 的 STM32 项目;需要动态申请和释放内存,并兼顾灵活性和稳定性 |
heap_5.c | 支持申请和释放;支持合并相邻空闲块;支持多个不连续内存区域 | 最灵活;可管理分散的 RAM 区域;适合复杂芯片系统 | 配置和使用相对复杂 | MCU 具有多块 RAM,且需要将不同内存区域统一交给 FreeRTOS 管理的复杂项目 |
5 五种方式的对比总结
| 内存管理方式 | 是否支持释放 | 是否合并空闲块 | 是否依赖标准库 | 特点总结 |
|---|---|---|---|---|
heap_1.c | 否 | 否 | 否 | 只申请,不释放;最简单;无碎片 |
heap_2.c | 是 | 否 | 否 | 可申请、可释放;不合并空闲块;容易碎片化 |
heap_3.c | 是 | 由标准库决定 | 是 | 直接调用 malloc/free;依赖标准库;实时性较差 |
heap_4.c | 是 | 是 | 否 | 可申请、可释放;支持空闲块合并;最常用、最实用 |
heap_5.c | 是 | 是 | 否 | 功能最强;支持多个内存区;适合复杂系统 |
6 FreeRTOS堆空间从哪里来
在使用 heap_1.c、heap_2.c、heap_4.c 等方案时,FreeRTOS 通常会在源码中定义一块静态数组作为堆区,例如:
static uint8_t ucHeap[ configTOTAL_HEAP_SIZE ];其中:
ucHeap表示 FreeRTOS 用来管理的堆内存configTOTAL_HEAP_SIZE在FreeRTOSConfig.h中定义
例如:
#define configTOTAL_HEAP_SIZE ((size_t)10*1024)表示给 FreeRTOS 分配了 10KB 的堆空间。
也就是说,FreeRTOS 的动态内存并不是无限的,而是来自一块预先划定好的 RAM 区域。
7 与任务创建的关系
以任务创建为例:
xTaskCreate(Task1, "Task1", 128, NULL, 2, NULL);执行这个函数时,FreeRTOS 通常会动态申请两部分内存:
1. 任务控制块 TCB
用于保存任务的状态信息。
2. 任务栈
用于保存任务运行时的局部变量、函数调用现场等。
这些内存通常都来自 FreeRTOS 的堆区,也就是 pvPortMalloc() 申请得到的空间。
因此,若堆空间不足,就可能导致:
- 任务创建失败
- 队列创建失败
- 信号量创建失败
8 动态创建与静态创建
FreeRTOS 中很多对象既支持动态创建,也支持静态创建。
动态创建
例如:
xTaskCreate()
xQueueCreate()特点:
- 由 FreeRTOS 自动申请内存
- 使用方便
- 依赖堆管理
静态创建
例如:
xTaskCreateStatic()
xQueueCreateStatic()特点:
- 由用户自己提前提供内存
- 不依赖
pvPortMalloc() - 更适合对内存可控性要求高的场景
因此从本质上说,FreeRTOS 的“内存管理”主要影响的是动态创建方式。
9 学习时需要重点掌握什么
学习 FreeRTOS 内存管理时,建议重点掌握以下几点:
1. FreeRTOS 用 pvPortMalloc() 和 vPortFree() 管理内存
而不是直接用 malloc/free。
2. heap_1.c 到 heap_5.c 是五种不同的内存管理实现
它们的差别主要体现在:
- 是否支持释放
- 是否合并空闲块
- 是否依赖标准库
- 是否支持多个内存区
3. heap_4.c 是最常用的方案
大多数 STM32 + FreeRTOS 工程都会使用它。
4. configTOTAL_HEAP_SIZE 决定 FreeRTOS 可用堆大小
如果这个值太小,就会导致内核对象创建失败。
5. 动态创建依赖堆,静态创建不依赖堆
这是理解 FreeRTOS 内存管理非常关键的一点。
10 本节小结
FreeRTOS 的内存管理本质上是对内核对象运行时 RAM 分配方式的管理。
系统通过 pvPortMalloc() 和 vPortFree() 完成内存申请与释放,不同的 heap_x.c 文件提供了不同的实现方案。
其中:
heap_1.c最简单,但不支持释放heap_2.c支持释放,但容易碎片化heap_3.c直接使用标准库heap_4.c支持释放和空闲块合并,是最常用方案heap_5.c支持多个内存区域,最灵活
在实际开发中,选择合适的内存管理方案,对于系统的稳定性、实时性和资源利用率都有重要影响。
