ESP-IDF+vscode开发ESP32第十六讲——存储管理
目录前言一、知识梳理1.1 IRAM 和 DRAM1.2 IROM 和 DROM1.3 整体区分1.4 DMA缓冲区1.5 内存管理前言想要深入理解嵌入式那么存储管理是绕不开的。这里面有很多名词包括堆、栈、RAM、ROM等等。本文就基于ESP32P4来分析一下ESP-IDF是如何进行存储管理的一、知识梳理单片机的ROM、RAM、堆、栈的理解_堆栈都属于rom还是ram-CSDN博客在这篇文章中我已经全面的介绍了很多存储的概念不过是基于stm32和rethread那么本文以此为基础对esp32的不同处进行梳理。一般情况下把单片机的存储只为分ROM和RAM但ESP-IDF做了更细致的区分ESP-IDF 区分了指令总线IRAM、IROM和数据总线 (DRAM、DROM)。指令存储器是可执行的只能通过 4 字节对齐字读取或写入。数据存储器不可执行可以通过单独的字节操作访问。下面先给出普遍情况下的两者区别特性ROM (Flash)RAM (SRAM)掉电特性不丢失 (Non-volatile)丢失 (Volatile)读写速度较慢 (读快写极慢且需擦除)极快 (纳秒级)主要用途存储“死”数据代码、常量、初始值存储“活”数据运行时的变量状态C语言段映射.text(代码),.rodata(常量),.data的LMA(初始值).data(变量),.bss(零初始化), Stack(栈), Heap(堆)容量大小较大 (几十 KB 到 几 MB)较小 (几 KB 到 几百 KB)极其宝贵溢出后果编译时报错 (Linker Error: region overflow)运行时崩溃 (HardFault, 栈溢出, 内存泄漏)1.1 IRAM 和 DRAMIRAM指令 RAM是连接到 CPU 指令总线上的内存通常仅用于存储可执行数据即指令。访问通道只能通过指令总线访问。物理特性CPU 只能把它当作“代码”来执行。它不支持按单字节随意读写只能按 4 字节Word对齐读取。核心用途存放对时间极其敏感、必须零延迟执行的代码。中断服务程序ISR。Wi-Fi / 蓝牙底层协议栈的核心调度代码。Flash Cache 缺失Cache Miss时的异常处理代码。DRAM数据 RAM是连接到 CPU 数据总线上的内存用于存储数据。访问通道只能通过数据总线访问。物理特性CPU 只能把它当作“数据”来读写。支持按字节、半字、字随意读写支持 DMA 访问。但它不可执行如果 CPU 试图跳转到 DRAM 地址去执行代码会触发硬件异常Illegal Instruction。核心用途存放运行时的所有“活”数据。全局变量、静态变量.data,.bss段。任务的栈Stack和堆Heap。DMA 缓冲区如 I2S、SPI 传输的数据缓冲还有一种特殊的称为D/IRAM。它是指物理上的同一块 SRAM同时被映射到了指令总线和数据总线上因此可用作指令 RAM 或数据 RAM。例如一款520KB的SRAM从整体上可以被划分为以下区域物理 Bank物理容量DRAM 逻辑地址IRAM 逻辑地址属性SRAM0192 KB无映射0x4007_0000-0x4009_FFFF存放最核心的ISR和底层代码SRAM1128 KB0x3FFA_E000-0x3FFB_FFFF0x4008_0000-0x4009_FFFF(与SRAM0高位重叠映射)既能跑代码又能存数据SRAM2200 KB0x3FFC_0000-0x3FFF_FFFF无映射存放全局变量、堆、栈、DMA1.2 IROM 和 DROMIROM (Instruction ROM - 指令映射区)是外部 SPI Flash 中的代码分区。如果一个函数没有被显式地声明放在 IRAM 或者 RTC 存储器中则它会放在 flash 中。由于 IRAM 空间有限应用程序的大部分二进制代码都需要放入 IROM 中。访问机制通过 CPU 的指令总线 MMU Flash Cache 访问。CPU 不能直接从 Flash 执行代码必须由硬件 Cache 控制器先将 Flash 数据按块通常 32KB 或 64KB缓存到内部 IRAM 的 Cache 中CPU 再从 Cache 取指。核心特性容量极大取决于外部 Flash 大小通常 4MB~16MB。速度不稳定Cache Hit 时零等待极快Cache Miss 时极慢CPU 必须暂停等待 SPI 总线从 Flash 读数据耗时数十到数百个时钟周期。Flash 操作时瘫痪当调用esp_flash_write/erase写 Flash 时SPI 总线被独占Cache 被禁用。此时所有放在 IROM 中的代码都无法执行。存放内容绝大多数普通的 C/C 函数未加IRAM_ATTR宏的函数、标准库代码。DROM (Data ROM - 数据映射区)是是外部 SPI Flash划分给只读数据的分区。访问机制通过 CPU 的数据总线 MMU Flash Cache 访问。核心特性与 IROM 共享物理 Flash 和 Cache 机制但映射在数据总线上。存放内容const修饰的全局/局部变量、字符串字面量如Error code: %d、数组查找表、switch-case跳转表。而ESP32的片内ROMMask POM是出厂固化的物理 ROM。地址0x4000_0000-0x4006_FFFF(约 448 KB)映射在指令总线。内容乐鑫出厂时烧录在硅片里的死代码。包含最底层的 Bootloader一级启动、基本的 SPI/UART 驱动、基础的数学运算函数如memcpy,sin,cos的 ROM 优化版。特点绝对只读不可擦写。ESP-IDF 的rom/目录下的头文件就是用来调用这些 ROM 函数的。在 Flash 还没初始化好的极早期启动阶段CPU 只能执行这里的代码。1.3 整体区分维度IRAM (指令 RAM)DRAM (数据 RAM)IROM (指令 ROM 映射)DROM (数据 ROM 映射)物理本质内部 SRAM (硅片上)内部 SRAM (硅片上)外部 SPI Flash (通过 MMU 映射)外部 SPI Flash (通过 MMU 映射)访问总线指令总线 (Instruction Bus)数据总线 (Data Bus)指令总线 (Instruction Bus)数据总线 (Data Bus)可执行性✅ 可执行 (跑代码)❌ 不可执行✅ 可执行 (跑代码)❌读写属性仅支持 4 字节对齐读取 (常规开发视为只读)✅ 随意读写 (支持按字节/半字/字)❌ 只读 (运行时不可写)❌ 只读 (运行时不可写)访问速度⚡ 极速 (0 等待纳秒级)⚡ 极速 (0 等待纳秒级) 不稳定 (Cache Hit 极快Miss 极慢) 不稳定 (Cache Hit 极快Miss 极慢)掉电保持❌ 易失 (掉电丢失)❌ 易失 (掉电丢失)✅ 非易失 (掉电保持)✅ 非易失 (掉电保持)典型容量~128 KB - 192 KB (非常紧张)~200 KB - 280 KB (相对宽裕)几 MB (取决于外部 Flash 大小)几 MB (取决于外部 Flash 大小)主要存放内容中断服务程序 (ISR)、Wi-Fi/BT 底层核心调度、Flash Cache Miss 处理代码全局/静态变量、任务栈 (Stack)、堆 (Heap)、DMA 缓冲区绝大多数普通 C/C 函数、标准库代码const常量、字符串字面量、查找表、switch跳转表从表格中能看出大部分程序都存储在IROM中极少部分程序存储在IRAM中但IROM速度远低于IRAM于是官方提供了方式让我们能将一些需要极快响应的程序存入IRAM中。以下情况时应将部分应用程序放入 IRAM如果在注册中断处理程序时使用了ESP_INTR_FLAG_IRAM则中断处理程序必须要放入 IRAM。可将一些时序关键代码放入 IRAM以减少从 flash 中加载代码造成的相关损失。如何将代码放入 IRAM官方提供了两种方式第一种是借助链接器脚本还有一种是通过使用IRAM_ATTR宏在源代码中指定需要放入 IRAM 的代码。使用第二种方式可能会导致 IRAM 安全中断处理程序出现问题这是因为使用IRAM_ATTR宏指定了函数而函数中的字符串或常量可能没有自动放入 RAM 中导致错误。这时可以使用DRAM_ATTR宏对这些字符串或常量进行标注。例如void IRAM_ATTR gpio_isr_handler(void* arg) { const static DRAM_ATTR uint8_t INDEX_DATA[] { 45, 33, 12, 0 }; const static char *MSG DRAM_STR(I am a string stored in RAM); }注意具体哪些数据需要被标记为DRAM_ATTR可能很难确定。如果没有被标记为DRAM_ATTR某些变量或表达式有时会被编译器别为常量即使它们没有被标记为const并将其放入 flash 中。而使用第一种方法更加全面则一般不会出现这种问题。如果一个函数没有被显式地声明放在 IRAM 中则它会放在 flash 中。由于 IRAM 空间有限应用程序的大部分二进制代码都需要放入 IROM 中。1.4 DMA缓冲区大多数的 DMA 控制器比如 SPI、sdmmc 等都要求发送/接收缓冲区放在 DRAM 中并且按字对齐。同时官方建议将 DMA 缓冲区放在DRAM 的静态变量而不是堆栈中。而使用DMA_ATTR宏可以声明该全局/本地的静态变量具备 DMA 功能。例如DMA_ATTR uint8_t buffer[]I want to send something;初次之外还可以使用使用 MALLOC_CAP_DMA 标志来动态分配具备 DMA 的内存缓冲区。1.5 内存管理一块什么都没有烧录的芯片DRAM上只有堆随着使用从堆中分出了全局/静态变量、任务栈 (Stack)、DMA 缓冲区。于是乐鑫专门设计的一套基于“内存能力Capabilities”的高级内存管理 API用来实现堆中内存分配。这些API类似与标准 C 库malloc/free但有所区别比如标准malloc只关心内存的“大小”而heap_caps_malloc不仅关心大小还精确控制内存的“物理硬件特性”。维度标准malloc(size)/free(ptr)ESP-IDFheap_caps_malloc(size, caps)/heap_caps_free(ptr)设计哲学“盲盒分配”只要总容量够随便给一块。“精准点餐”必须满足指定的物理硬件特性参数只有 1 个size(字节数)有 2 个size(字节数) caps(内存能力掩码)分配的物理位置由系统底层决定在 ESP32 中默认是内部 DRAM。由开发者通过caps参数强制指定 SRAM、PSRAM、IRAM 等。适用场景普通变量、常规逻辑的临时缓冲。硬件外设交互DMA、中断、大容量数据音频/图像/PSRAM、极速代码。底层实现在 ESP-IDF 中标准malloc实际上是被重写Wrap 了的底层最终也是调用heap_caps_malloc。ESP-IDF 内存管理的真正底层核心 API。所以我们应该使用heap_caps_xxx来分配内存关于这部分API可查看堆内存分配 - ESP32-P4 - — ESP-IDF 编程指南 v6.0.1 文档或者我总结的ESP32 实用API指南1_esp32 api-CSDN博客