嵌入式链接描述文件 - 正确放置'堆栈'和'堆'区域?

时间:2018-04-17 03:51:51

标签: c linker embedded bare-metal linker-scripts

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

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

/* 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

  <...>
}

所以这是我对链接器行为的错误理解:

  • '_estack'值设置为RAM的结尾 - 此脚本用于'STM32F031K6'芯片,该芯片具有4KB的RAM,起始于0x20000000。它在ST的示例向量表中用于定义起始堆栈指针,所以看起来这应该标记'Stack'内存块的一端。

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

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

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

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

或者老实说,如果我的应用程序不使用malloc或其类似的话,我应该放弃'堆'段吗?无论如何,在可行的情况下避免嵌入式系统中的动态内存分配似乎是一种好习惯。

2 个答案:

答案 0 :(得分:4)

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

堆栈

许多ARM uC中的第一个有两个堆栈。一个称为Master Stack,第二个称为Process Stack。当然,您不需要启用此选项。

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

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

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

堆。

必须谨慎使用动态分配。第一个问题是可用内存的可能内存碎片,因为uC具有非常有限的资源。必须非常小心地使用动态分配的内存,否则它将成为严重问题的根源。前段时间,USB HAL库在中断例程中使用了动态分配 - 有时候只有一小部分时间足以分散堆,不足以进行任何进一步的分配。

另一个问题是大多数可用工具链中sbrk的错误实现。我唯一知道的正确的是由我们的同事@Freddie Chopin维护的BleedingEdge工具链。 问题是实现假设堆和堆栈彼此成长并最终可以满足 - 这当然是错误的。另一个问题是使用堆的开始和结束地址不正确地使用和初始化静态变量。

答案 1 :(得分:2)

  

'_estack'值设置为RAM的结尾 - 该脚本用于'STM32F031K6'芯片,该芯片具有4KB的RAM,起始于0x20000000。它在ST的示例向量表中用于定义起始堆栈指针,因此看起来这应该标记'Stack'内存块的一端。

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

  

是'_Min_x_Space'的特殊标签,还是那些名字可能有些混乱?

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

  

因为看起来链接器脚本只是将那些精确大小的内存段附加到RAM而不考虑程序的实际使用情况。

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

  

如果没有使用其他RAM,nm会显示'_user_heap_stack'部分以0x20000600结束,这会在'_estack'之前留下一堆空RAM

它正好留下0x400字节,即_Min_Stack_Size。 Remeber堆栈在这里向下增长(通常也在其他地方)。

  

似乎是一种很好的做法,无论如何都可以避免嵌入式系统中的动态内存分配。

并非一切都是安全关键的。如果您不希望/不需要/允许,您可以不使用堆。 (好吧,后者不是免费的)