RTOS-F429-HAL-Freertos内存管理(2026/8/5)

目录

一:Freertos内存管理

1:我们采用的内存

2:先理解动态内存管理的核心

二:heap_1:最简单,只分配不释放

1:工作原理

[2:为什么不能 free](#2:为什么不能 free)

3:优缺点

4:适合什么场景

[三:heap_2 --- 支持 free,但不合并碎片](#三:heap_2 — 支持 free,但不合并碎片)

[1:跟 heap_1 的区别](#1:跟 heap_1 的区别)

2:碎片怎么产生的

3:为什么不会合并

优缺点

4:适合什么场景

[四:heap_3 --- 标准库 malloc 的套壳](#四:heap_3 — 标准库 malloc 的套壳)

[五:heap_4 --- 工业首选](#五:heap_4 — 工业首选)

[六:heap_5 --- heap_4 的多内存区版](#六:heap_5 — heap_4 的多内存区版)

[七:rtos\23\ 改动清单](#七:rtos\23\ 改动清单)

freertos_demo.c

main.c

实验现象:


一:Freertos内存管理

1:我们采用的内存

FreeRTOS 不直接依赖 C 标准库 malloc/free,而是自己实现了一套简单的内存管理器。

对应源码:

复制代码
FreeRTOS/Source/portable/MemMang/
​
heap_1.c
heap_2.c
heap_3.c
heap_4.c
heap_5.c
   
你配置:
​
FreeRTOSConfig.h
​
#define configUSE_HEAP_4
​
然后工程里面加入:
​
heap_4.c
​
即可。

2:先理解动态内存管理的核心

假设 FreeRTOS 有一块堆:

复制代码
SRAM
​
0x20000000
      |
      v
​
+-------------------------+
|                         |
|       Free Heap         |
|                         |
+-------------------------+
 创建任务:xTaskCreate(...) 
    
 heap分配“
​
+---------+
|  TCB    |
+---------+
| stack   |
+---------+
| free    |
+---------+
  
 删除任务:vTaskDelete()
 
 释放 TCB
释放 stack,因此内存管理需要有申请和释放才合理。

二:heap_1:最简单,只分配不释放

1:工作原理

整个堆就是一块连续内存 + 一个指针 free_ptr,每次申请就把指针往后移:

复制代码
static uint8_t ucHeap[configTOTAL_HEAP_SIZE];  // 堆本体
static size_t  xNextFreeByte;                  // ★ 就一个指针,指向下一个空闲位置
​
// pvPortMalloc(100):
void *ptr = &ucHeap[xNextFreeByte];  // 从当前位置切一块
xNextFreeByte += 100;                // 指针往后移
return ptr;
复制代码
ucHeap ────────────────────────────────────────→
│ Task1栈+TCB │ Task2栈+TCB │ Queue数据区 │ ... │ ← 空闲
│             │             │             │     │
│ 已分配的     │ 已分配的     │ 已分配的     │     │
                              ↑
                         xNextFreeByte(用多少走多少)

2:为什么不能 free

没有记录谁申请的、多大、在哪xNextFreeByte 只知道往前走,不知道哪块可以回收。

3:优缺点

优点 缺点
分配极快(O(1),就移一次指针) 不能释放------内存只进不出
无碎片(永远连续分配) 不能动态创建删除任务/队列
代码极小 不适合需要动态生命周期的场景

4:适合什么场景

复制代码
启动阶段:
  创建 task1 → 申请
  创建 task2 → 申请
  创建全部分队列 → 申请
  启动调度器
​
运行阶段:
  永不创建新任务
  永不删除任务
  稳定跑几年无故障重启
​
→ 工业控制、传感器节点、不重启的嵌入式设备

一句话:heap_1 = 只分配不释放的线性分配器。适合永远不删任务的场景,不适合 FreeRTOS 通用场合。我们一直用的是 heap_4(支持 free + 碎片合并)。

三:heap_2 --- 支持 free,但不合并碎片

1:跟 heap_1 的区别

heap_2 多了空闲链表 ,释放的内存会被挂回链表供后续使用。但相邻的空闲块不合并

复制代码
heap_1:                  heap_2:
  只分配不释放              分配 + 释放(有碎片)
  O(1)                     O(1)
  无碎片                    有碎片!不合并!

2:碎片怎么产生的

复制代码
① 申请 3 块:
  ┌─────┬─────┬─────┐
  │ A   │ B   │ C   │
  │100B │200B │100B │
  └─────┴─────┴─────┘
​
② 释放 B:
  ┌─────┬─────┬─────┐
  │ A   │FREE │ C   │     ← 中间空了 200B
  │100B │200B │100B │
  └─────┴─────┴─────┘
​
③ 有人要 300B:
  空闲链表里最大块是 200B(B 那块),不够 300B
  虽然 A+B+C 总空闲 > 300B,但它们不连续
  → 分配失败!
​
④ 如果释放了 A 和 B:
  ┌─────┬─────┬─────┐
  │FREE │FREE │ C   │     ← A 和 B 相邻但不会合并!
  │100B │200B │100B │     还是两块独立的 100B + 200B
  └─────┴─────┴─────┘

3:为什么不会合并

heap_2 的算法只挂接vPortFree时归还的块,不检查相邻块是否也是空闲。所以空闲链表里永远是最初分配出去的原始块大小。

优缺点

优点 缺点
支持释放(比 heap_1 进了一步) 碎片越来越严重(运行越久内存越散)
分配释放都是 O(1) 大块可能申请失败(被小碎片卡死)
实现简单 不适合频繁 malloc/free 的场景

4:适合什么场景

任务和队列大多在创建后不删除,偶尔有小块临时内存的申请释放。运行周期不长、碎片不会累积到致命程度的应用。

一句话:heap_2 = 能用 free,但内存像揉碎的面包------中间留下的空位原地不动,不合并,碎片越用越多。我们用的 heap_4 解决了这个问题。

四:heap_3 --- 标准库 malloc 的套壳

复制代码
void *pvPortMalloc(size_t size) { return malloc(size); }
void  vPortFree(void *pv)       { free(pv); }

就是把 C 标准库的 malloc/free 包了一层,堆管理甩给 libc。

优点 缺点
代码少 速度不确定(libc 可能搜链表、合并碎片)
不用维护 STM32 裸机不一定有 malloc
线程安全依赖 libc 实现

适合:有完整 C 运行库的平台(Linux 上的 FreeRTOS 模拟器)。

五:heap_4 --- 工业首选

= heap_2 的分配释放 + 相邻空闲块自动合并

跟 heap_2 的区别就一点

复制代码
释放 A 再释放 B(A 和 B 相邻):

heap_2:                        heap_4:
  ┌──────┬──────┬─────┐           ┌──────┬──────┬─────┐
  │ FREE │ FREE │ C   │           │ FREE │ FREE │ C   │
  │ 100B │ 200B │100B │           │ 100B │ 200B │100B │
  └──────┴──────┴─────┘           └──────┴──────┴─────┘
  两小空洞,300B 申请失败                ↓ 检测到相邻 → 合并
                                      ┌──────────┬─────┐
                                      │   FREE    │ C   │
                                      │   300B    │100B │
                                      └──────────┴─────┘
                                       300B 的申请能通过了!

六:heap_5 --- heap_4 的多内存区版

跟 heap_4 算法完全一样,只是能从多处不连续的 RAM 里取内存:

复制代码
// F429 有三块 RAM:
// SRAM1  @ 0x20000000, 112KB(主战场)
// SRAM2  @ 0x2001C000,  16KB
// CCM    @ 0x10000000,  64KB(CPU 独享,DMA 用不了)

HeapRegion_t regions[] = {
    { (uint8_t *)0x20000000, 112 * 1024 },   // 区域1:SRAM1
    { (uint8_t *)0x10000000,  64 * 1024 },   // 区域2:CCM
    { NULL, 0 }                               // 结束标志
};

vPortDefineHeapRegions(regions);  // 必须调在 vTaskStartScheduler 之前
复制代码
heap_4 的堆:                       heap_5 的堆:
一块连续 RAM                        多块不连续 RAM 拼成逻辑上的大一整块

┌────────────┐                      ┌────────────┐
│   全部堆    │                      │  SRAM1     │
│  (36KB)    │                      │  112KB     │
│            │                      ├────────────┤
└────────────┘                      │  SRAM1     │
                                    │  16KB      │
                                    └────────────┘
                                    │  CCM       │
                                    │  64KB      │
                                    └────────────┘
                                    同一套算法,从三块池子里划

我们不用它 ------configTOTAL_HEAP_SIZE = 36KB 完全在 SRAM1 内部,一块就够。需要管理外部 SDRAM 或者要把 CCM 也用起来时才切到 heap_5。

三块 ram都能划分给堆heap,数组里写几个区就管几个:

复制代码
HeapRegion_t regions[] = {
    { (uint8_t *)0x20000000, 112 * 1024 },   // 区域1:SRAM1(112KB)
    { (uint8_t *)0x2001C000,  16 * 1024 },   // 区域2:SRAM2(16KB)
    { (uint8_t *)0x10000000,  64 * 1024 },   // 区域3:CCM(64KB)
    { NULL, 0 }                               // ★ 结束标志
};

三块不连续的物理内存 → heap_5 把它们当成一个逻辑大堆,算法跟 heap_4 完全一样。上限不限,只要有连续地址空间,写几块都行。

注意 CCM 的坑:CCM 是 CPU 独享的,DMA 访问不了。把任务栈放 CCM 没问题(CPU 用),DMA 缓冲区放 CCM 会挂。

七:rtos\23\ 改动清单

freertos_demo.c

行号 内容
99-107 pvPortMalloc(30) --- KEY1 申请 30 字节
113-120 vPortFree(buf) --- KEY2 释放
128-133 xPortGetFreeHeapSize() --- 每 500ms 打印堆剩余

main.c

行号 内容
26 printf 标题 → "FreeRTOS Memory Management Test!"

不需要开任何新宏 ------pvPortMalloc / vPortFree 配合 heap_4.c 一直可用,configSUPPORT_DYNAMIC_ALLOCATION = 1 早开着。

运行效果:按 KEY1 → 申请 30 字节 → 堆剩余减少。连按多次多申请几块。按 KEY2 → 释放最近一块 → 堆剩余恢复。每 500ms 自动打印当前堆剩余。

实验现象:

复制代码
堆剩余:36224 字节
堆剩余:36224 字节
申请内存成功!
堆剩余:36184 字节
堆剩余:36184 字节
堆剩余:36184 字节
堆剩余:36184 字节

30 字节是你要的,但 heap_4 内部还有管理开销

复制代码
你申请 30 字节,实际上切出来的是:

┌──────────────┬─────────────────┐
│ BlockLink_t  │   30 字节有效区  │
│   (8 字节)    │   (给你的)      │
└──────────────┴─────────────────┘
      ↑ 这 8 字节不算在 30 里

36184 - 36224 = -40 字节,多出来的 ~10 字节是块头 + 对齐(heap_4 按 8 字节对齐,30 会凑整到 32,加上 8 字节头 = 40)。

复制代码
// heap_4.c 里的块结构体
typedef struct A_BLOCK_LINK
{
    struct A_BLOCK_LINK *pxNextFreeBlock;  // 4 字节:指向下一个空闲块
    size_t               xBlockSize;       // 4 字节:块大小(含头 + 对齐)
} BlockLink_t;  // 共 8 字节

每个分配的块前面都有这个 8 字节头,FreeRTOS 用它在链表里管理和合并。你拿到的是跳过这个头的地址。

相关推荐
呜喵王阿尔萨斯1 小时前
extern “C“ in C++
c语言·c++·算法
zcmodeltech3 小时前
工业生产线沙盘模型多段灯带时序控制系统设计:基于STM32与Modbus RTU的连铸-热轧-镀锌全流程联动方案
stm32·单片机·嵌入式硬件·物联网·制造·嵌入式实时数据库·多分类
萌新源4 小时前
电赛C题:我用星闪+UWB做了一个数字钥匙门锁系统,开源了
c语言·华为·开源
小僧景贤4 小时前
STM32F407 时钟系统超详解
stm32·单片机·嵌入式硬件
西城微科方案开发4 小时前
胎压计方案开发
单片机·嵌入式硬件
国科安芯5 小时前
卫星星务计算机数据管理中SEU容错机制的技术实现研究——以AS32S601存储器ECC架构为例
网络·单片机·算法·fpga开发·架构·云计算·risc-v
JckOLF04Z5 小时前
自动开机调用迅雷下载数据库备份,完成后自动关机
数据库·单片机·嵌入式硬件
国科安芯5 小时前
卫星电推进系统阀门驱动控制中的宽温域高可靠MCU应用研究——基于AS32S601电气特性与辐照试验数据的工程分析
单片机·嵌入式硬件·fpga开发·云计算·信息与通信·卫星·抗辐射芯片
不正经学生5 小时前
C语言指针进阶:const 和野指针——给指针加锁,向野指针宣战
c语言·开发语言·c++·算法·c#