代码之家  ›  专栏  ›  技术社区  ›  Will

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

  •  2
  • Will  · 技术社区  · 6 年前

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

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

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

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

    我的问题是,这应该怎么做?“\u Min\u x\u Space”是特殊标签,还是这些名称可能有点混淆?因为链接器脚本看起来只是将这些精确大小的内存段附加到RAM中,而没有考虑程序的实际使用情况。

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

    我能想到的唯一解释是,“Heap”和“Stack”段可能没有实际意义,只定义为编译时保护,以便链接器在可用动态内存明显少于预期时抛出错误。如果是这样的话,我应该把它看作是最小的“组合堆/堆栈”大小吗?

    或者说实话,如果我的应用程序不使用malloc或它的同类软件,我应该放弃“Heap”部分吗?不管怎样,尽可能避免嵌入式系统中的动态内存分配似乎是一种很好的做法。

    2 回复  |  直到 6 年前
        1
  •  5
  •   Community Artem    4 年前

    您会问到将堆栈和堆放在哪里的问题。在uC上,由于许多原因,答案不如@a2f所述的那么明显。

    堆栈

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

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

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

    当您将堆栈放置在RAM的开头时(作为开头,我指的是RAM开始地址+堆栈大小),当堆栈将溢出时,会生成硬件异常。您完全可以控制uC,您可以看到问题的原因(例如损坏的传感器向uC发送大量数据),并启动紧急例行程序(例如停止机器,将车辆置于维修模式等)。堆栈溢出不会在未检测到的情况下发生。

    堆。

    动态分配必须在uCs上小心使用。第一个问题是可用内存可能存在内存碎片,因为uC的资源非常有限。必须非常小心地考虑动态分配内存的使用,否则它会成为严重问题的根源。不久前,USB HAL库在中断例程中使用了动态分配——有时,秒数的一小部分足以将堆分割成碎片,从而不允许进一步分配。

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

        2
  •  3
  •   a3f    6 年前

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

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

    “\u Min\u x\u Space”是特殊标签,还是这些名称可能有点混淆?

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

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

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

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

    它只剩下0x400字节 _Min_Stack_Size 。请记住,堆栈在此处向下生长(通常在其他地方也会向下生长)。

    不管怎样,尽可能避免嵌入式系统中的动态内存分配似乎是一种很好的做法。

    并非所有事情都是安全关键的。如果您不想/不需要/不被允许,您可以自由地不使用堆。(好的,后者不是免费的)