返回上一级
967 字
5 分钟
FreeRTOS 源码与内存管理

p16(11.6 分钟)、p17(11.5 分钟)

源码目录#

FreeRTOS 源码不用一次读完,先建立一张地图:

Source/
├── tasks.c 任务、调度、延时、状态
├── list.c 内核链表,任务队列的基础
├── queue.c 队列、队列集、信号量、互斥量
├── timers.c 软件定时器
├── event_groups.c 事件组
├── include/ API 与内核结构声明
└── portable/ 芯片和编译器相关实现

portable/ 是最容易出问题的地方,里面同时有编译器相关(RVDS、GCC、IAR)和芯片相关(Cortex-M3、M4)的代码,选错目录编译或链接阶段就会直接报错。这类问题不该靠改内核源码绕过去。

p16 这一集没有抓到字幕,上面这部分是按 FreeRTOS 官方源码结构整理的,不是视频里的原话。

堆就是一个数组#

CubeMX 的中间件配置里,内存分配有「动态分配」和「静态分配」两个选项,堆大小是 3072 字节,管理方案选 heap_4。把动态分配禁掉之后,配置文件里就不再去编译 heap_4 那个文件了。

堆在代码里没什么神秘的东西,就是一个数组:

uint8_t ucHeap[configTOTAL_HEAP_SIZE];

一个全局静态数组,大小由配置宏决定(这里是 3072),用数组巧妙地表示一整块空闲内存。宏开得非常大时编译会报错。

heap_1 到 heap_5#

五个文件都在 Third-party/FreeRTOS/Source/portable/MemMang 下面,每个都实现了同名函数(pvPortMalloc 之类),所以同一时间只能用其中一个。

heap_1 只分配、不回收,跟前面手写的最简 my_malloc 是一个路子。只分配不回收的应用用它是一点空间都不浪费——要 100 字节就给 100 字节,没有头部开销。

heap_2 能分配也能释放,但不合并相邻的空闲块,碎片问题很严重:

分配 100(头部 + 100)、分配 50(头部 + 50)
释放 buffer1、释放 buffer2
两块明明相邻,却不合并
此时想分配 120 → 失败

heap_3 只是去调用标准库函数(malloc/free)。嵌入式里一般不用标准库,所以这个方案也很少用。

heap_4 在 heap_2 基础上加了合并:释放 100 和 50 之后发现相邻就合并,再分配 120 就没问题了。

heap_5 支持分隔的内存。硬件上有 CPU、片内内存、Flash,可能还有片外 RAM,程序只用了其中一小块,剩下的都能当堆。heap_5 用空闲链表管理这些离散的堆,链表头指向第一个堆、再指向第二个,所以要先告诉它一共有几个离散堆,并把每块内存的起止地址和大小初始化进链表。

选哪个:一般用 heap_4,有多块内存用 heap_5,heap_2 很少用,heap_1 更少用。

heap_4 用起来要注意什么#

初始化堆的私有函数是 prvHeapInit,但不需要手工调用——第一次调 pvPortMalloc 的时候会自动调。

分配和释放就是 pvPortMalloc / vPortFree。

想知道 3072 够不够,跑一段时间后调这两个函数看:

xPortGetFreeHeapSize() 当前还剩多少空闲内存
xPortGetMinimumEverFreeHeapSize() 整个运行过程中空闲容量的最小值

第二个值更有用。如果它已经接近个位数或十位数,说明系统曾经紧张到这个程度,该把堆调大了。

pvPortMalloc 还留了个口子:用户可以自定义一个钩子函数,分配失败的时候被调用,用来打印调试信息。

坑#

  • configTOTAL_HEAP_SIZE 是 FreeRTOS 堆的大小,不是芯片全部 RAM。
  • 动态创建的任务、队列、信号量都从这个堆里拿内存。堆不够时表现为创建失败,不是运行出错。
  • 只看 xPortGetFreeHeapSize() 会误判。刚启动时剩得多不代表跑起来够用,要看历史最小值。
  • 五份 heap_x 文件只能挑一个编进工程,同时编进去会撞同名函数。
FreeRTOS 源码与内存管理
https://me.622168.xyz/posts/freertos/03-source-and-memory/
作者
Shaw 的小屋
发布于
2026-09-29
许可协议
CC BY-NC-SA 4.0