嵌入式链接器脚本 - 正确放置“堆栈”和“堆”区域?

Wil*_*ill 2 c embedded linker bare-metal linker-scripts

最近我一直在研究自动生成的 STM32 项目中使用的链接器脚本,我对堆栈和堆内存段的定义有点困惑。

例如,我一直在查看 ST 的“CubeMX”固件包中为其 F0 系列芯片提供的文件,这些芯片具有 ARM Cortex-M0 内核。如果文件的许可证允许,我会粘贴整个脚本,但如果您好奇,可以从 ST 免费下载整个程序包1。无论如何,以下是与我的问题相关的部分:

/* Highest address of the user mode stack */
_estack = 0x20001000;    /* end of RAM */
/* Generate a link error if heap and stack don't fit into RAM */
_Min_Heap_Size = 0x200;      /* required amount of heap  */
_Min_Stack_Size = 0x400; /* required amount of stack */

<...>

SECTIONS {
  <...>

  .bss :
  {
    <...>
  } >RAM

  /* User_heap_stack section, used to check that there is enough RAM left */
  ._user_heap_stack :
  {
    . = ALIGN(8);
    PROVIDE ( end = . );
    PROVIDE ( _end = . );
    . = . + _Min_Heap_Size;
    . = . + _Min_Stack_Size;
    . = ALIGN(8);
  } >RAM

  <...>
}
Run Code Online (Sandbox Code Playgroud)

所以这是我对链接器行为的可能不正确的理解:

  • '_estack' 值设置为 RAM 的末尾 - 此脚本适用于具有 4KB RAM 的 'STM32F031K6' 芯片,从 0x20000000 开始。它在 ST 的示例向量表中用于定义起始堆栈指针,因此似乎应该标记“堆栈”内存块的一端。

  • '_Min_Heap_Size' 和 '_Min_Stack_Size' 值似乎应该定义专用于堆栈和堆供程序使用的最小空间量。分配大量动态内存的程序可能需要更多“堆”空间,而调用深度嵌套函数的程序可能需要更多“堆栈”空间。

我的问题是,这应该如何工作?'_Min_x_Space' 是特殊标签,还是这些名称可能有点令人困惑?因为看起来链接器脚本只是将这些确切大小的内存段附加到 RAM 中,而没有考虑程序的实际使用情况。

此外,为堆栈定义的空间似乎不一定在其开始和上面定义的“_estack”值之间定义一个连续的段。如果没有使用其他 RAM,则nm显示 '_user_heap_stack' 部分在 0x20000600 处结束,这在 '_estack' 之前留下了一堆空 RAM。

我能想到的唯一解释是“堆”和“堆栈”段可能没有实际意义,仅被定义为编译时保护措施,以便链接器在可用动态内存明显少于预期的。如果是这种情况,我是否应该将其视为最小的“组合堆/堆栈”大小?

或者老实说,如果我的应用程序不使用 malloc 或其同类,我是否应该删除“堆”段?无论如何,尽可能避免在嵌入式系统中进行动态内存分配似乎是一种很好的做法。

P__*_*J__ 5

您会问放置堆栈和堆的位置。在 uC 上,由于多种原因,答案并不像@a2f 所说的那么明显。

堆栈

首先,许多 ARM uC 有两个堆栈。一个称为主堆栈,第二个称为进程堆栈。当然,您不需要启用此选项。

另一个问题是 Cortex uC 可能有(例如 STM32F3,许多 F4、F7、H7)许多 SRAM 块。由开发人员决定放置堆栈和堆的位置。

堆栈放在哪里?我建议将 MSP 放在所选 RAM 的开头。为什么?如果堆栈放在最后,您无法控制堆栈的使用。当堆栈溢出时,它可能会默默地覆盖您的变量,并且程序的行为变得不可预测。如果是 LED 闪烁,这不是问题。但是想象一下大型机器控制器或汽车会损坏计算机。

当您将堆栈放置在 RAM 的开头时(作为开头,我的意思是 RAM 起始地址 + 堆栈大小),当堆栈即将溢出时,会生成硬件异常。您可以完全控制 uC,您可以查看导致问题的原因(例如损坏的传感器使 uC 充满数据)并启动紧急程序(例如停止机器、将汽车置于服务模式等) . 堆栈溢出不会在未检测到的情况下发生。

堆。

在 uC 上必须谨慎使用动态分配。第一个问题是可用内存可能会出现内存碎片,因为 uC 的资源非常有限。必须非常仔细地考虑使用动态分配的内存,否则它可能成为严重问题的根源。前段时间 USB HAL 库在中断例程中使用动态分配 - 有时,一秒钟的一小部分足以将堆分割成足够的碎片,不允许进一步分配。

另一个问题是在大多数可用工具链中错误地实现了 sbrk。我所知道的唯一一个正确的是 BleedingEdge 工具链,该工具链由我们来自该论坛的同事 @Freddie Chopin 维护。问题是实现假设堆和堆栈相互增长并最终可以相遇 - 这当然是错误的。另一个问题是使用和初始化具有堆开始和结束地址的静态变量的不当使用和初始化。

  • 这是将堆栈定位在 RAM 开头的另一次投票,[https://embeddedgurus.com/state-space/2014/02/are-we-shooting-ourselves-in-the-foot-with-stack-overflow /](https://embeddedgurus.com/state-space/2014/02/are-we-shooting-ourselves-in-the-foot-with-stack-overflow/) (2认同)