tags: [source-summary] type: source source: "尚硅谷嵌入式技术之FreeRTOS实时操作系统 V1.0.3 — 第19章 + 配套代码21" author: "尚硅谷研究院" date: 2026-07-12 url: ""
用生活理解:内存管理就像图书馆的座位管理——
- heap_1:图书馆只进不出,每个人来了就固定坐一个位置,永远不走(最简单)
- heap_2:可以走了,但走之后留下的空位不合并——时间长了满是小缝隙,大桌子放不下
- heap_4:有人走了,把旁边空位合并成大空间(推荐!)
- heap_5:图书馆有多个独立区域,可以跨区域管理座位
在嵌入式系统中,标准 C 库的 malloc()/free() 有这些问题:
| 算法 | 策略 | 合并空闲块 | 允许释放 | 适用场景 | 推荐? |
|---|---|---|---|---|---|
| heap_1 | 简单数组分配 | 不适用 | ❌ 不能释放 | 只创建不删除的项目 | |
| heap_2 | 最佳适应(Best-fit) | ❌ | ✅ | 任务栈大小固定 | ❌ 已过时 |
| heap_3 | C 库 malloc/free | 由 C 库决定 | ✅ | 标准库可用时 | |
| heap_4 | 首次适应(First-fit) | ✅ | ✅ | 通用场景 | ⭐ 推荐 |
| heap_5 | 跨区域 First-fit | ✅(跨区域) | ✅ | 内存地址不连续 |
"蹲坑不冲水":只申请不释放,就像上厕所不冲水——简单粗暴,但只适用于项目启动时一次性创建所有对象的场景。
初始化: [ 整个堆空间 ]
分配后: [ Task1 ][ Task2 ][ Task3 ][ 剩余 ]
一次分配、永不释放。适用于项目启动时创建所有任务/队列,运行中不删除任何东西。
"伤过的心已经变成玻璃碎片":最佳适应算法(Best-fit)从满足要求的块中选择最接近请求大小的。问题是不合并相邻空闲块——时间长满是小碎片,大块分配失败。
释放后: [ Task1 ][ 空闲 ][ Task3 ][ 空闲 ]
↑不合并 ↑不合并
变成小碎片 变成小碎片
分配16字节:选"最适合"的块(不一定是最大的)
问题:碎片越来越多,最终大块分配失败
标准 C 库
malloc()/free()的简单线程安全包装。如果开发环境自带 C 库就可以用,但和裸机 malloc 一样有非确定性问题。
首次适应算法(First-fit):按地址顺序找到第一个足够大的块就用,不追求最优匹配(更快)。自动合并相邻空闲块——有人走了,把旁边空位合并成大空间。
释放后: [ Task1 ][ 空闲 ][ Task3 ][ 空闲 ]
↓自动合并相邻空闲块
[ Task1 ][ 大空闲空间 ]
分配时:选"第一个足够大的"块
最多碎片控制:相邻空闲块自动合并,减少碎片。
"杀鸡焉用牛刀":能合并不连续的内存区域,但开销大。除非你的内存物理上就不连续(比如外扩 SARM 和片内 SRAM 要统一管理),否则用 heap_4 就够了。
内存布局: [ 区域A 128KB ] [ 区域B 64KB ] [ 区域C 32KB ]
↑ 不连续但heap_5能统一管理
void task1(void *pvParameters)
{
uint8_t key = 0;
void *buff = NULL;
while (1)
{
key = Key_Detect();
if (key == KEY1_PRESS)
{
// 申请20字节内存
buff = pvPortMalloc(20);
if (buff != NULL) {
printf("申请20字节成功!剩余堆=%d\r\n",
xPortGetFreeHeapSize());
}
}
else if (key == KEY2_PRESS)
{
// 释放内存
vPortFree(buff);
printf("释放成功!剩余堆=%d\r\n",
xPortGetFreeHeapSize());
}
vTaskDelay(500);
}
}
| 函数 | 作用 | 类比 |
|---|---|---|
pvPortMalloc(size) |
申请 size 字节 | 跟图书馆说"我要坐这儿" |
vPortFree(ptr) |
释放之前申请的内存 | 离开座位 |
xPortGetFreeHeapSize() |
查还剩多少堆空间 | 看看图书馆还有多少空位 |
code/21_内存管理/相关:[[03-FreeRTOS移植指南]](移植时需选择 heap_4.c)