ESP32 程序的内存模型

本文翻译自:ESP32 Programmers’ Memory Model - Amey Inamdar

MCU 中的内存资源可能是其最宝贵的资源,因为它在芯片中占据最大的面积。更新的应用程序对内存的需求正在不断增长。为了充分利用硬件资源,理解内存架构并能针对应用程序的实际用例进行内存优化变得至关重要。特别是对于包含通信子系统( Wi-Fi 和 BT/BLE )的 ESP32 SoC 架构,通信子系统本身需要占用一定数量的内存才能运行,因此有必要明确应用程序的需求并对其进行内存优化。

我们经常会遇到有关应用程序可用内存余量的问题;除非我们深入了解用例,否则这个问题没有简单的答案。但是,当开发人员了解有关内存布局,系统要求和常见优化方法的详细信息时,就会发现 ESP32 可以适应各种有趣的应用程序用例。

本博客旨在为开发者提供 ESP32 SoC 的内存布局概述,介绍不同的内存区域及其特性,并讨论典型 ESP32 固件的内存分配。

请注意,此处提到的所有特定详细信息均与 ESP-IDF 发布版本 4.0 有关,该版本是撰写此博客时最新的稳定发布版本(译者注:原文发布于 2020 年 7 月 3 日)。

片内存储空间布局

图 1 ESP32 SRAM 布局

图 1 ESP32 SRAM 布局

上图显示了 ESP32 内部存储器(SRAM)的布局。SRAM 分为 3 个存储块 SRAM0、SRAM1 和SRAM2(以及 RTC 快速和慢速存储器 2 个小块,我们将在后面分别讨论)。

SRAM 以两种方式使用:一种用于指令存储,称为 IRAM(用于执行代码,text 段),另一种用于数据存储,称为 DRAM(用作 BSS 段,Data 段和堆)。SRAM0 和 SRAM1 可以用作连续的 IRAM,而 SRAM1 和 SRAM2 可以用作连续的 DRAM 地址空间。
在这里插入图片描述

图 2 程序内存图
虽然 SRAM1 可以同时用作 IRAM 和 DRAM ,但实际上 ESP-IDF 会默认使用 SRAM1 作为 DRAM ,这是因为通常在应用程序中数据空间会比较紧缺。上图的浅蓝色图表显示了程序员在开发应用程序时需要考虑的内存映射,可以利用的内存包括 192KB 的 IRAM 和 328KB 的 DRAM(译者注:不是所有的空间都能被用户程序使用)。因为没有重叠部分,所以对应用程序来说没有太大影响,但是需要注意的是 IRAM 和 DRAM 地址空间,地址范围的方向是相反的。

IRAM 组织结构

在这里插入图片描述

图 3 双核模式下的 IRAM 布局

现在让我们放大 IRAM 分段。

ESP32 中 192 KB 的可用 IRAM 用于代码执行,并且其中一部分作为高速缓存(Cache)用于访问 Flash(和 PSRAM )。

  • 前 32KB IRAM 用作 CPU0 的高速缓存,接下来的 32KB 用作 CPU1 高速缓存。这是在硬件中静态配置的,无法更改。
  • 在第一个 64KB 之后,链接脚本开始将 text 段放置在 IRAM 中。它首先放置所有中断向量,然后放置已编译应用程序中所有标记为放置在 IRAM 中的 text 段。在通常情况下,大多数应用程序代码从 flash (XiP)执行,但某些代码对执行时间有较高要求,或者本身需要操作 flash,需要将它们放置在 IRAM 中。这项操作通过对这些函数或代码文件添加特定属性标识实现,链接程序脚本将据属性标识将它们放置在 IRAM 中。链接脚本将 _iram_text_start_iram_text_end 符号放置在 text 段的两个边界处。
  • text 段之后的 IRAM 保持未使用状态,并添加到堆中。

链接脚本将 _iram_text_start_iram_text_end 符号放置在 text 段的两个边界处。 text 段之后的 IRAM 保持未使用状态,并被添加到堆中。

并且,当应用程序配置为单核模式时,CPU1 不工作并且不启用 CPU1 Cache。在这种情况下,CPU1 Cache 的空间(0x40078000–0x4007FFFF)将被添加到堆中。

放置在堆中的未使用的 IRAM 可以通过动态分配访问。

如果应用程序有此要求,它可用于在 IRAM 中放置任何代码。但是,这种情况很少见。

IRAM 也可以用于放置数据,但有两个重要限制条件:

  1. 用于访问 IRAM 中数据的地址必须是 32 位对齐的;
  2. 访问的数据大小也必须是 32 位对齐的。

如果应用程序具有可以遵循这两个访问规则的数据,则 IRAM 空间可用于存储该数据。

还有一种方法可以不受此限制的访问 IRAM 空间。但是作为访问速度会变慢。这将在后面的部分中讨论。

DRAM 组织结构

在这里插入图片描述

图 4 DRAM 布局

上图显示了应用程序的典型(简化)DRAM 布局。由于 DRAM 地址从 SRAM2 的末尾开始,并向后增加,因此链接阶段段空间的分配从 SRAM2 的末尾开始。

  • 前 8KB(0x3FFA_E000–0x3FFA_FFFF)用作某些 ROM 内置函数的数据空间;
  • 链接器紧接着将已初始化的数据段放在第一个 8KB 存储器之后;
  • 接下来是未初始化的 BSS 段;
  • 数据段和 BSS 段之后剩余的内存被配置为堆,典型的动态内存分配一般分配至该位置。

请注意,数据段和 BSS 段的大小取决于应用程序。因此,每个应用程序根据其使用的组件和所调用的 API 都有不同的可用堆大小。

堆代码中有两个区域(0x3FFE_0000–0x3FFE_0440 - 共 1088 字节)和(0x3FFE_3F20–0x3FFE_4350 - 共 1072 字节)供 ROM 代码存放数据。这些区域被标记为保留,并且堆分配器不会从这些区域分配内存。

启用蓝牙功能之后的 DRAM 组织结构

在这里插入图片描述

图 5 启用蓝牙功能之后的 DRAM 布局

启用蓝牙(BT)功能后, BT 控制器(软件和硬件)需要使用专用的数据空间。该空间作为控制器的 Data/BSS 段,同时作为传输空间用于 BT 数据包在软件和硬件之间传输。因此,链接脚本在默认的 DRAM 空间中保留了 0x3FFB_0000–0x3FFB_DB5C 之间的 54KB 空间,在该区域之后才进行应用程序的数据段和 BSS 段分配。

当应用程序仅使用低功耗蓝牙(BLE)功能时,可以将 BT 控制器内存的一部分交还给堆。释放并添加到堆中的内存大小约为 19KB。

启用跟踪调试空间之后 DRAM 组织结构

在这里插入图片描述

图 6 启用跟踪的 DRAM 布局

​应用程序级的跟踪调试(Trace)启用以后,它将在 DRAM 的末尾保留一个固定为 32KB 的内存空间。请注意,上图显示了未启用 BT 时的内存布局。但是应用程序也可以在启用BT的情况下使用跟踪,在这种情况下,链接脚本中也会保留 BT 控制器的内存空间。

片外 SPIRAM

ESP32 提供了在 QSPI 总线上外接伪静态 RAM (PSRAM 又名 SPIRAM)的能力,该总线同时用于访问 flash,二者同时工作时利用片选信号进行切换。该存储器同 flash 一样可直接寻址,访问过程通过 IRAM 中的 Cache 进行。ESP32 在其地址空间 0x3F80_0000 至 0x3FBF_FFFF 最多可映射 4MB SPIRAM (译者注:新版本 IDF 可使用 Himem API 访问最大为 8 MB 的 SPIRAM)。应用程序通过三种方式使用 SPIRAM :

  1. 使用 SPIRAM 保存特定软件模块的 BSS 段;
  2. 使用堆分配器从 SPIRAM 动态分配内存;
  3. 通过直接内存映射,在应用程序中使用静态地址访问 SPIRAM。

虽然这允许应用程序使用额外的内存,但对 SPIRAM 的使用有以下限制:

  1. SPIRAM 不支持 DMA,在需要使用 DMA 向/从外设传输数据的情况下,不能使用它;
  2. 由于 flash 和 SPIRAM 使用同一 QSPI 总线与 ESP32 通信,因此在执行禁用 XiP 模式的代码中不能使用 SPIRAM;
  3. 由于 SPIRAM 访问比内部 SRAM 慢,因此建议对性能有要求的代码使用内部 SRAM 保存数据。

此处详细介绍了使用 SPIRAM 的这些方式以及使用限制。

堆分配器

从以上 IRAM 和 DRAM 内存布局可以看到,DRAM 区域 _bss_end0x3FFF_FFFF(或 _heap_end 在跟踪调试启用时)和 IRAM 区 _iram_text_end0x4009_FFFF 是未使用的内存空间。如果系统中有 SPIRAM ,则该内存也属于未使用的内存空间。应用程序和 SDK 组件始终需要按需分配和释放内存。因此,通用内存分配器(也称为堆分配器)用于操作可用内存空间,并为其提供内存分配和释放 API 。

如您所见,堆分配器控制下的内存区域具有不同的功能和访问属性。因此,ESP-IDF 实现了一个基于功能的堆分配器,调用者可以在指定分配大小时同时指定用途。例如,应用程序可能指定分配具有 DMA 功能的内存空间,以便与某些外设一起使用,或者它可以指定从外部 SPIRAM 为音频缓冲区分配内存,因为从内部 DRAM 分配不是更好的选择。

ESP-IDF 还在基于功能的堆分配器的 API 之封装了通用的 malloc 和 free API ,以使应用程序易于从 POSIX 类型的系统移植。应用程序配置项可以包含一组管理规则,使 malloc API 能够根据分配的大小自动选择实际的内存段位置。

使用 IRAM 保存数据

从 ESP-IDF 4.2 版本开始,我们增加了使用 IRAM 进行数据存储的功能。如上所述,IRAM 具有地址和大小对齐的访问限制。如果进行未对齐访问,则会导致异常。在 4.2 版之后,ESP-IDF 透明地处理这些异常,以提供调用者所需的 load/store 功能。由于这些未对齐的访问会导致异常,因此访问速度将比 DRAM 慢。通常,每个异常处理大约需要 167 个 CPU 周期(即 240 MHz 时每次访问 0.7 usc 或 160 MHz 时每次访问 1 usec)。应用程序或 SDK 组件可以在链接时将 IRAM 用于 BSS 数据,或者在运行时通过堆分配器使用 IRAM 。使用 IRAM 进行数据有两个限制:

  1. IRAM 访问不是多核安全的。因此,需要在单核运行模式,或在系统和应用程序已知对 IRAM 的访问来自同一个内核时使用它(例如,将任务固定到一个内核上);
  2. 用于 DMA 的内存分配不应从 IRAM 分配。

ESP-IDF 4.2 提供了一些现成的配置,可以有效地利用未使用的 IRAM 进行数据操作,例如以单核模式下发送和接收 TLS 片段。

请查看博客,以获取有关 AWS-IoT 客户端应用程序的内存分析和一些常见优化技术的案例研究。

猜你喜欢

转载自blog.csdn.net/espressif/article/details/112956403