为什么微控制器的用户手册中没有定义堆栈和堆大小?

问题描述

我对嵌入式编程很陌生。所以这对你来说可能是一个很简单的问题。

我见过不同 SDK(例如 IAR EWARM、Tasking 等)的不同链接器脚本文件/链接配置文件,其中定义了堆栈/堆的大小。

链接文件中也定义了每个微控制器的RAM和闪存的大小/范围。通常取自用户手册的内存映射。(地址范围在用户手册中提供)

我的问题是堆栈和堆的大小是如何计算的? 我可以选择堆栈/堆大小的任何值吗?或者他们的任何标准都是如此?

解决方法

基本上,堆栈大小是根据预期的程序大小选择的。对于更大和更复杂的程序,您将需要更大的堆栈大小。它还取决于体系结构,32 个苦味通常比 8 和 16 个苦味消耗更多的内存。确切的值是根据经验选择的,不过一旦您确切地知道您的程序实际使用了多少 RAM,您就可以增加堆栈大小以使用大部分未使用的内存。

映射堆栈也是一种习惯,以便它在溢出时增长到无害区域,例如非映射内存或闪存。理想情况下,当堆栈溢出发生时,您会收到硬件异常、“软件中断”或类似的异常。您不应该永远映射它,使其增长为 .data/.bss 并覆盖那里的其他变量。

至于堆,大小几乎总是选择为 0,并且该段从链接描述文件中完全删除。几乎每个微控制器应用程序都禁止堆分配。

,

这些在微控制器用户手册中没有定义,因为它们不是硬件定义的约束。相反,它们是应用程序定义的。它是依赖于软件的内存分区,而不是依赖于硬件。

局部、非静态变量、函数参数和调用返回地址一般都存放在栈中;因此所需的堆栈大小取决于调用深度以及调用树中每个函数的局部变量和参数的数量和大小。堆栈使用是动态的,但会有一些最坏情况路径,其中变量和调用深度的组合会导致使用高峰。

除此之外,在许多架构上,您还必须考虑中断处理程序堆栈的使用,这通常不太确定,但仍然存在中断嵌套和调用深度的“最坏情况”。出于这个原因,ISR 通常应该是简短的、确定性的并且使用很少的变量。

此外,您有一个多线程环境,例如 RTOS 调度程序,每个线程将有一个单独的堆栈。通常,这些线程堆栈是静态分配的数组或动态(堆)分配的,而不是由链接描述文件定义的。链接描述文件通常只为 main() 线程和中断/异常处理程序定义系统堆栈

估计所需的堆栈使用量并不总是那么容易,但有一些方法可以使用静态或动态分析。一些示例(部分特定于工具链)位于:

许多默认链接器脚本会自动扩展堆以填充静态数据和堆栈分配后的所有剩余可用空间。一个值得注意的例外是 Keil ARM-MDK 工具链,它要求您显式设置堆大小。

链接描述文件可能会为其他目的保留内存区域;特别是如果内存不是同质的——例如,片上 MCU 内存通常比外部 RAM 的访问速度更快,并且本身可能在不同的总线上细分,因此例如可能有一小段对单独的总线上的 DMA 有用,因此避免总线争用并产生更具确定性的执行。

需要仔细考虑在嵌入式系统中使用动态内存(堆)分配(甚至像@Lundin 建议的那样禁止,但并非所有嵌入式系统都受到相同的约束)。有许多问题需要考虑,包括:

  • 内存限制 - 许多嵌入式系统的内存非常小,您必须考虑在无法满足分配请求时系统的响应、安全性和功能性。
  • 内存泄漏 - 您自己的、您团队中的同事和第三方代码的质量可能没有您希望的那么高;您需要确保整个代码库没有内存泄漏(未能正确释放/释放内存)。
  • 确定性 - 大多数堆分配器需要可变且不确定的时间长度来分配内存,如果涉及块合并,甚至释放也可能是不确定的。
  • 堆损坏 - 已分配块的所有者很容易使分配不足/溢出并损坏相邻内存。通常,此类内存包含块或其他群的堆管理元数据,以及其他分配的实际数据。破坏此数据对其他代码具有不确定性影响,通常与导致错误的代码无关,因此在与导致错误的事件无关的代码中发生故障是很常见的。这样的错误很难发现和解决。如果堆元数据损坏,通常会在进一步的堆操作(分配/释放)失败时检测到错误。
  • 效率 - malloc() et-al 的堆分配通常是 8 字节对齐的,并且有一个预先挂起的元数据块。某些实现可能会添加一些“缓冲区”区域以帮助检测溢出(尤其是在调试版本中)。因此,对非常小的块进行大量分配可能会显着降低稀缺资源的使用效率。

嵌入式系统中处理这些问题的常用策略包括:

  • 禁止任何动态内存分配。例如,这在安全关键和 MISRA 合规应用程序中很常见。
  • 仅在初始化期间允许动态内存分配,不允许 free()。这可能看起来违反直觉,但在应用程序本身是“动态的”并且可能在某些配置中并非所有任务或设备驱动程序等都启动的情况下可能很有用,静态分配可能会导致大量未使用/无法使用的内存。立>
  • 用确定性的内存分配方法(例如固定块分配器)替换默认堆。通常这些有一个单独的 API 而不是覆盖 malloc/free,所以不是严格的替代品;只是一个不同的解决方案。
  • 禁止在硬实时关键代码中进行动态内存分配。这仅解决了确定性问题,但在具有大内存、精心设计的代码以及分配的 MMU 保护的系统中,可能会有缓解措施。
,

堆栈和堆是程序本身的一部分。它们基于您的程序的结构和编写方式,它占用了多少内存。剩余空闲内存将作为堆栈或堆工作,具体取决于您的设置方式。

链接脚本中,您可以定义这些值。