FreeRTOS(二)之堆和栈:内存基础入门

前言

学 FreeRTOS 第一道坎不是任务,是搞懂内存。堆和栈分不清,后面任务栈大小、内存管理全都会懵。

目录

前言

一、堆(Heap)

[1.1 堆的概念](#1.1 堆的概念)

[1.2 堆的模拟实现](#1.2 堆的模拟实现)

二、栈(Stack)

[2.1 栈的概念](#2.1 栈的概念)

[2.2 栈的大小实验](#2.2 栈的大小实验)

三、堆和栈的核心区别

[四、FreeRTOS 的五种内存管理方案](#四、FreeRTOS 的五种内存管理方案)

总结


一、堆(Heap)

1.1 堆的概念

堆是由程序员通过动态内存分配函数(malloc()、new 等)分配的内存区域,当你不再需要这块内存时,必须显式释放它(free() / delete)。

简单理解:堆是一块空闲内存,可以从中取出一部分,用完再还回去

在 FreeRTOS 里,堆的作用是给"动态创建的任务、队列、信号量"提供内存。比如 xTaskCreate 动态创建任务时,任务控制块(TCB)和任务栈就是从堆里分配的。

1.2 堆的模拟实现

用代码模拟一下堆的工作方式,能帮你彻底理解它:

cpp 复制代码
/* 模拟堆:一个固定大小数组 + 一个分配函数 */
static char heap_buf[1024];   // 模拟堆空间
static int pos = 0;           // 当前分配位置
​
void *my_malloc(int size)
{
    if (pos + size > 1024)
        return NULL;          // 空间不够,返回空
​
    void *p = &heap_buf[pos]; // 取走一块内存
    pos += size;              // 指针后移
    return p;
}

这段代码的运行逻辑:

  1. heap_buf[1024] 是一个大小为 1024 字节的静态数组,用来模拟堆内存空间;

  2. my_malloc() 函数负责从 heap_buf 中按 size 参数分配指定大小的内存;

  3. 分配后 pos 的值增加,以便下次分配时不会覆盖之前的内存空间

  4. 由于没有实现 my_free() 函数,当前设计是单向分配,无法回收已经分配的空间。

⚠️ 这就是新手写内存管理最容易犯的错:只分配不释放,最后内存耗尽。真实开发中 malloc 和 free 必须成对出现。

二、栈(Stack)

2.1 栈的概念

栈是一种后进先出(LIFO)的数据结构 。可以把栈想象成一摞盘子:后放上去的盘子先拿走

在 FreeRTOS 里,每个任务都有自己独立的栈空间,用于保存:

  • 任务运行时的上下文信息(寄存器值)

  • 局部变量

  • 函数调用信息(返回地址)

cpp 复制代码
void func_b(void)
{
    int b = 2;   // b 压入栈
}                // func_b 返回,b 出栈(自动释放)
​
void func_a(void)
{
    int a = 1;   // a 压入栈
    func_b();    // 调用 func_b,b 压入 a 的上面
}                // 全部返回,栈恢复原状

关键点:栈由操作系统自动分配释放,无需手动控制。函数被调用时,局部变量压入栈;函数结束时,变量自动释放。

2.2 栈的大小实验

FreeRTOS 创建任务时,xTaskCreateusStackDepth 参数就是任务栈大小(单位:,不是字节!)。

如果任务栈定小了会发生什么?任务里的局部变量一多,就会栈溢出------程序跑飞、乱跳、HardFault,这是 RTOS 开发最经典的坑。

排查手段:

  1. 看运行现象:任务执行到某个函数后开始乱跑、复位,多半是栈不够;

  2. 开栈溢出检测 :在 FreeRTOSConfig.h 中开启:

cpp 复制代码
#define configCHECK_FOR_STACK_OVERFLOW   2

开启后,系统会在任务切换时检查栈是否越界,一旦越界就调用钩子函数:

cpp 复制代码
void vApplicationStackOverflowHook(TaskHandle_t xTask, char *pcTaskName)
{
    /* 栈溢出!在这里打印出错任务名,或直接停机 */
    printf("Stack overflow: %s\n", pcTaskName);
    while (1);
}

三、堆和栈的核心区别

维度
谁管理 程序员手动(malloc/free) 操作系统自动
分配方向 从低地址向高地址 从高地址向低地址
生命周期 直到 free 才释放 函数返回即释放
速度 慢(要查找空闲块) 快(压栈弹栈)
容量 大(通常几百 KB~几 MB) 小(每任务几百字节~几 KB)
典型问题 内存泄漏、碎片 栈溢出

四、FreeRTOS 的五种内存管理方案

FreeRTOS 在 portable/MemMang/ 下提供了 5 种堆实现(heap_1 ~ heap_5),它们各有适用场景:

方案 特点 适用场景
heap_1 只能分配,不能释放 程序启动时创建好所有对象,之后不再删除
heap_2 可分配可释放,但不合并碎片 对象大小固定、反复创建删除的场景
heap_3 包装标准库 malloc/free,线程安全 与标准库混用的场景
heap_4 可分配可释放,合并相邻空闲块(最常用) 通用推荐,碎片少
heap_5 同 heap_4 + 支持跨内存区 多块不连续 RAM 的芯片(如带外部 SRAM)

实际开发中 90% 的工程用 heap_4,它兼顾了灵活性和防碎片能力。

总结

  • 堆 = 手动管理的大仓库,给任务、队列动态分配内存;

  • 栈 = 系统自动管理的临时区,函数调用、局部变量放这里;

  • 任务栈大小是 FreeRTOS 开发第一坑 ------宁大勿小,配合 configCHECK_FOR_STACK_OVERFLOW 使用;

  • 内存管理 5 种方案,默认优先 heap_4。

相关推荐
桃蹊、2 小时前
FreeRTOS(四)之任务管理(上):创建、删除与任务参数
freertos·任务·task·创建任务
mounter6252 天前
重构 Kexec Handover:Linux 内核如何将 KHO 打造为无状态、可重入的跨重启交接基石
linux·内存管理·linux kernel·kernel·liveupdate
论迹复利2 天前
PendSV与RISC-V trap——上下文切换的两种设计哲学
内核·freertos·risc-v
捷瑞电子工坊5 天前
FreeRTOS中断管理:从原理到实践(标准库版)
嵌入式·freertos·实时操作系统·任务通知·临界区·中断管理·二值信号量
wabs6665 天前
关于栈【力扣150.逆波兰表达式求值的思考】
数据结构·c++·算法·leetcode··代码随想录
捷瑞电子工坊6 天前
FreeRTOS 内存管理详解:从 heap_1 到 heap_5 的选择与实践
freertos·内存管理·嵌入式开发·内存碎片·嵌入式实时操作系统·动态内存分配
qq_401700416 天前
FreeRTOS队列集--允许任务同时在多个队列或信号量上阻塞等待读取操作
freertos
wabs6666 天前
关于栈【力扣1047. 删除字符串中的所有相邻重复项的思考】
数据结构·c++·算法·leetcode··代码随想录
hsjiasb7 天前
FreeRTOS学习(三十九)——最终总结
学习·学习笔记·freertos