一、概述       

        对于刚入门实时操作系统的小白来说,我们对堆和栈的概念理解的并不充分,本文介绍FREERTOS实时操作系统在嵌入式的硬件下是如何实现内存管理和分配的。

二、STM32H7VGT6的RAM空间概述

        以下是STM32H7VGT6的内存分配概述,如果像详细了解请看我的有关的FREERTOS博客,里面有各个内存的特性。

        STM32H7内存结构复杂,通常有:

  • DTCM RAM: 最快的内存(128KB),零等待周期,常用于堆栈和关键数据。

  • SRAM1/SRAM2/SRAM3/SRAM4: 主系统RAM,速度较快,容量大(如512KB)。

  • AXI SRAM: 位于AXI总线上的RAM(如256KB或更大)。

  • 备份SRAM: 低功耗保持的RAM。

        FreeRTOS对象存放的位置,根本上由你使用的堆(Heap)所在的内存区域决定,经过测试,FreeRTOS的堆默认存放在AXI SRAM区域,由此可见,我们在设置系统堆总大小的时候,默认情况下不能超过256KB(实际更小,因为裸机的一些全局变量也会存放在这里)。

        下图是我通过Keil5工程搜索ucheap数组所在位置,得到其0x24000000起始的位置,这是AXI SRAM区域。

三、FreeRTOS的堆(Heap)—— 所有动态对象的来源

        这是最关键的概念。FreeRTOS在创建任务、队列、信号量等内核对象时,需要动态分配内存。这些内存就来自它的“堆”。

        位置: 由 FreeRTOSConfig.h 中的配置和你的链接脚本决定。

        使用FreeRTOS自带的内存管理方案

  • 自定义(configAPPLICATION_ALLOCATED_HEAP = 1: 你需要自己在一个特定的内存区域(如DTCM)定义一个数组,并将 ucHeap 指向它。 这是将FreeRTOS堆放在特定内存的最常用方法

  • 默认(configAPPLICATION_ALLOCATED_HEAP = 0: 堆是一个由FreeRTOS定义的全局数组(通常叫 ucHeap[ configTOTAL_HEAP_SIZE ])。这个数组在哪个内存段,取决于链接脚本。通常它会被放在 .bss 或 .data 段,最终位于默认的RAM(可能是SRAM1)。
  • 在 FreeRTOSConfig.h 中,你通过 configAPPLICATION_ALLOCATED_HEAP 宏来决定堆的位置。
// 例如,将堆放在DTCM(假设0x20000000是DTCM起始地址)
#define FREERTOS_HEAP_BASE   (0x20000000)
uint8_t ucHeap[ configTOTAL_HEAP_SIZE ] __attribute__((at(FREERTOS_HEAP_BASE)));
// 或者使用链接脚本更优雅地指定节区

        使用自定义内存管理: 你可以实现自己的 pvPortMalloc/vPortFree,从任何内存池(如SDRAM、外部RAM)分配,灵活性最高。

四、任务栈(Task Stack)

  • 来源: 在调用 xTaskCreate() 时,FreeRTOS会从它的中分配两块内存:

    1. 任务控制块(TCB)

    2. 任务栈空间

  • 位置: 因此,任务栈就在FreeRTOS堆所在的内存里。如果你把堆放在DTCM,任务栈就在DTCM;如果堆在AXI SRAM,栈就在AXI SRAM。

  • 重要优化: 对于高性能的H7,尤其是任务中有大量浮点运算或高频率切换时,强烈建议将任务栈放在最快的DTCM中,以减少访问延迟。

五、队列(Queues)、互斥量(Mutexes)、信号量(Semaphores)

  • 来源: 它们都是通过类似 xQueueCreate(), xSemaphoreCreateMutex() 等API创建的。这些函数内部会调用 pvPortMalloc() 从FreeRTOS的中分配所需内存。

  • 位置: 所以,它们和任务栈一样,都位于FreeRTOS堆所在的内存区域。

  • 结构: 分配的内存包括这些内核对象的结构体本身以及队列中用到的数据存储区。

六、如何验证和查看各种变量和函数的存放位置?

  1. 查看链接脚本(.ld文件): 搜索 ucHeap 或 _heap 等符号,看它被分配到了哪个内存区域(如 RAM_D1, DTCMRAM 等)。

  2. 使用编译后的Map文件: 编译后生成的 .map 文件会列出所有符号的地址。搜索 ucHeap, pxReadyTasksLists(一个内核全局变量)等,它们的地址会告诉你实际所在的RAM区域。

  3. 运行时查看: 在调试器中,查看 ucHeap 的地址(例如 0x20000000 是DTCM,0x24000000 是AXI SRAM)。

七、总结

        在使用FreeRTOS时,我们一定要切记,FreeRTOS的动态对象都在它的“堆”里,而这个堆的位置是由你,开发者,最终决定的。

  • 追求极致性能: 将FreeRTOS堆(ucHeap)和所有内核对象放在DTCM。注意DTCM容量有限(128KB),要合理设置 configTOTAL_HEAP_SIZE

  • 需要大容量任务和队列: 如果任务很多或队列很大,DTCM不够用,可以将堆放在AXI SRAM(例如512KB)。这是性能和容量的良好折衷。

  • 超大型应用: 如果内部RAM都不够,可以创建多个内存池。例如,将关键任务的栈放在DTCM(通过自定义内存管理),将不常用的队列数据放在外部SDRAM。

  • 使用CubeMX配置: 在CubeMX中配置FreeRTOS时,它默认会生成代码,将堆放在 .bss 段。你需要手动修改链接脚本或代码来将其重定位到特定内存。

Logo

openvela 操作系统专为 AIoT 领域量身定制,以轻量化、标准兼容、安全性和高度可扩展性为核心特点。openvela 以其卓越的技术优势,已成为众多物联网设备和 AI 硬件的技术首选,涵盖了智能手表、运动手环、智能音箱、耳机、智能家居设备以及机器人等多个领域。

更多推荐