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

Will Source

Lately I've been studying the linker scripts used in auto-generated STM32 projects, and I'm a little bit confused about how the stack and heap memory segments are defined.

As an example, I've been looking at the files provided in ST's "CubeMX" firmware package for their F0 lines of chips, which have ARM Cortex-M0 cores. I'd paste a whole script if the files' licenses allowed it, but you can download the whole package from ST for free if you're curious1. Anyways, here are the parts relevant to my question:

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

  <...>
}

So here's my probably-incorrect understanding of the linker's behavior:

  • 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.

  • The '_Min_Heap_Size' and '_Min_Stack_Size' values seem like they are supposed to define the minimum amount of space that should be dedicated to the stack and heap for the program to use. Programs that allocate a lot of dynamic memory may need more 'Heap' space, and programs that call deeply-nested functions may need more 'Stack' space.

My question is, how is this supposed to work? Are '_Min_x_Space' special labels, or are those names maybe slightly confusing? 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.

Also, the space defined for the Stack does not appear to necessarily define a contiguous segment between its start and the '_estack' value defined above. 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'.

The only explanation I can think of is that the 'Heap' and 'Stack' segments might have no actual meaning, and are only defined as a compile-time safeguard so that the linker throws an error when there is significantly less dynamic memory available than expected. If that's the case, should I think of it as more of a minimum 'Combined Heap/Stack' size?

Or honestly, should I just drop the 'Heap' segment if my application won't use malloc or its ilk? It seems like good practice to avoid dynamic memory allocation in embedded systems when you can, anyways.

clinkerembeddedbare-metallinker-scripts

Answers

answered 3 months ago a3f #1

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.

As the stack here would grow downwards (from high to low addresses), it's actually the start of the stack memory region.

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

The thing special about them is that symbols starting with an underscore followed by an uppercase letter are reserved for the implementation. e.g. min_stack_space could clash with user-defined symbols.

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.

That's the minimum size. Both the stack and the heap break may grow.

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'

It leaves exactly 0x400 bytes, which is _Min_Stack_Size. Remeber stack grows downwards here (and often elsewhere as well).

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

Not everything is safety-critical. You're free to not use the heap if you don't want/need/are allowed to. (Ok, not that free in the latter)

answered 3 months ago P__J__ #2

You ask the question where to place the stack and the heap. On uC the answer is not as obvious as @a2f stated for many reasons.

the stack

First of many ARM uC have two stacks. One is called Master Stack and the second one Process Stack. Of course you do not need to enable this option.

Another problem is that the Cortex uC may have (for example STM32F3, many F4, F7, H7) many SRAM blocks. It is up to developer to decide where to place the stack and the heap.

Where to place the stack? I would suggest to place MSP at the beginning of the chosen RAM. Why? If the stack is placed at the end you do not have any control of the stack usage. When stack overflows it may silently overwrite your variables and the behavior of the program becomes unpredictable. It is not the issue if it is the LED blink thing. But imagine a large machine controller or car breaks computer.

When you place the stack at the beginning of the RAM (as beginning I mean RAM start address + stack size) when the stack is going to overflow the hardware exception is generated. You are in the full control of the uC, you can see what caused the problem (for example damaged sensor flooding the uC with data) and start the emergency routine (for example stop the machine, put the car into the service mode etc etc). The stack overflow will not happen undetected.

the Heap.

Dynamic allocation has to be used with the caution on the uCs. First problem is the possible memory fragmentation of the available memory as uC have very limited resources. Use of the dynamically allocated memory has to be considered very carefully otherwise it cab be a source of the serious problems. Some time ago USB HAL library was using dynamic allocation in the interrupt routine - a fraction of the second was sometimes enough to fragment the heap enough disallowing any further allocation.

Another problem is wrong implementation of the sbrk in the most of the available toolchains. The only one I know with the correct one is the BleedingEdge toolchain maintained by our colleague from this forum @Freddie Chopin. The problem is that the implementations assume that the heap and the stack grow towards each other and eventually can meet - which is of course wrong. Another problem is improper use and initialization of the static variables with the addresses of the heap start and end.

comments powered by Disqus