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

📅 2026/8/6 8:03:04
RTOS-F429-HAL-Freertos内存管理(2026/8/5)
目录一Freertos内存管理1我们采用的内存2先理解动态内存管理的核心二heap_1最简单只分配不释放1工作原理2为什么不能 free3优缺点4适合什么场景三heap_2 — 支持 free但不合并碎片1跟 heap_1 的区别2碎片怎么产生的3为什么不会合并优缺点4适合什么场景四heap_3 — 标准库 malloc 的套壳五heap_4 — 工业首选六heap_5 — heap_4 的多内存区版七rtos\23\ 改动清单freertos_demo.cmain.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 空闲链表里最大块是 200BB 那块不够 300B 虽然 ABC 总空闲 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 再释放 BA 和 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, 64KBCPU 独享DMA 用不了 HeapRegion_t regions[] { { (uint8_t *)0x20000000, 112 * 1024 }, // 区域1SRAM1 { (uint8_t *)0x10000000, 64 * 1024 }, // 区域2CCM { 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 }, // 区域1SRAM1112KB { (uint8_t *)0x2001C000, 16 * 1024 }, // 区域2SRAM216KB { (uint8_t *)0x10000000, 64 * 1024 }, // 区域3CCM64KB { NULL, 0 } // ★ 结束标志 };三块不连续的物理内存 → heap_5 把它们当成一个逻辑大堆算法跟 heap_4 完全一样。上限不限只要有连续地址空间写几块都行。注意 CCM 的坑CCM 是 CPU 独享的DMA 访问不了。把任务栈放 CCM 没问题CPU 用DMA 缓冲区放 CCM 会挂。七rtos\23\ 改动清单freertos_demo.c行号内容99-107pvPortMalloc(30)— KEY1 申请 30 字节113-120vPortFree(buf)— KEY2 释放128-133xPortGetFreeHeapSize()— 每 500ms 打印堆剩余main.c行号内容26printf 标题 →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 用它在链表里管理和合并。你拿到的是跳过这个头的地址。