嵌入式链接器脚本 - 'stack' 和 'heap' 区域的正确放置?

Embedded linker scripts - proper placement of 'stack' and 'heap' regions?

最近一直在研究自动生成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

  <...>
}

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

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

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

我能想到的唯一解释是 'Heap' 和 'Stack' 段可能没有实际意义,只是定义为编译时保护,以便链接器抛出错误当可用的动态内存明显少于预期时。如果是这样,我是否应该将其视为最小 'Combined Heap/Stack' 大小?

或者老实说,如果我的应用程序不使用 malloc 或类似的东西,我是否应该删除 'Heap' 段?无论如何,尽可能避免在嵌入式系统中进行动态内存分配似乎是一个好习惯。

The '_estack' value is set to the end of RAM - this script is for an 'STM32F031K6' chip which has 4KB of RAM starting at 0x20000000. It is used in ST's example vector tables to define the starting stack pointer, so it seems like this is supposed to mark one end of the 'Stack' memory block.

由于这里的堆栈会向下增长(从高地址到低地址),所以它实际上是堆栈内存区域的开始。

Are '_Min_x_Space' special labels, or are those names maybe slightly confusing?

它们的特别之处在于,以下划线开头后跟大写字母的符号是为实现保留的。例如min_stack_space 可能与用户定义的符号冲突。

Because it looks like the linker script just appends memory segments of those exact sizes to the RAM without consideration for the program's actual usage.

这是最小尺寸。堆栈和堆中断都可能增长。

If there is no other RAM used, nm shows that the '_user_heap_stack' section ends at 0x20000600, which leaves a bunch of empty RAM before '_estack'

正好留下0x400字节,也就是_Min_Stack_Size。记住堆栈在这里向下增长(通常在其他地方也是如此)。

seems like good practice to avoid dynamic memory allocation in embedded systems when you can, anyways.

并非所有事情都对安全至关重要。如果您不 want/need/are 允许,您可以不使用堆。 (好吧,后者不是那么自由)

你问栈和堆放在哪里的问题。在 uC 上,答案并不像@a2f 所说的那么明显,原因有很多。

堆栈

首先,许多 ARM uC 有两个堆栈。一个称为 Master Stack,第二个称为 Process Stack。当然你不需要启用这个选项。

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

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

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

堆。

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

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