Stacks and stack sizes

In an RTOS like RT or NIL there are several dedicated stacks, each stack has a dedicated RAM space that must have a correctly sized assigned RAM area.

Stack types

There are several stacks in the systems, some are always present, some others are present only in some architectures:

  1. C runtime stack, this stack is used by the main() function and the thread that executes it. It is not a normal thread stack because it is initialized in the startup code and its size is defined in a port dependent way. Details are in the various ports documentation.
  2. Interrupt Stack, some architectures have a dedicated interrupt stack. This is an important feature in a multi-threaded environment, without a dedicated interrupt stack each thread has to reserve enough space, for interrupts servicing, within its own stack. This space, multiplied by the total threads number, can amount to a significant RAM overhead.
  3. Thread Stack, each thread has a dedicated stack for its own execution and context switch.
  4. Other Stacks, some architectures (ARM) can have other stacks but the OS does not directly use any of them.

Risks

The most critical thing when writing an embedded multi-threaded application is to determine the correct stack size for main, threads and, when present, interrupts. Assigning too much space to a stack is a waste of RAM, assigning too little space leads to crashes or, worst scenario, hard to track instability.

Assigning the correct size

You may try to examine the asm listings in order to calculate the exact stack requirements but this requires much time, experience and patience. An alternative way is to use an interactive method. Follow this procedure for each thread in the system:

  1. Enable the following debug options in the kernel:
    • CH_DBG_ENABLE_STACK_CHECK, this option enables a stack check before context switches. This option halts the system in chSysHalt() just before a stack overflow happens.
    • CH_DBG_FILL_THREADS, this option fills the threads working area with an easily recognizable pattern (0x55).
  2. Assign a large and safe size to the thread stack, for example 256 bytes on 32 MCUs, 128 bytes on 8/16 bit MCUs. This is almost always too much for simple threads. Note that calling the standard C library functions, sprintf() or sscanf() for example, can take a lot of space.
  3. Run the application, if the application crashes or halts then increase the stack sizes and repeat (you know how to use the debugger right?).
  4. Let the application run and make sure to trigger the thread in a way to make it follow most or all its code paths. If the application crashes or halts then increase the stack size and repeat.
  5. Stop the application using the debugger and examine the thread working area (you know what a map file is, right?). You can see that the thread stack overwrote the fill pattern (0x55) from the top of the working area downward. You can estimate the excess stack by counting the untouched locations.
  6. Trim down the stack size and repeat until the application still runs correctly and you have a decent margin in the stack.
  7. Repeat for all the thread classes in the system.
  8. Turn off the debug options.
  9. Done.

Note that ChibiStudio includes a debug plugin capable of calculating the remaining space in each stack automatically.

Final Notes

Some useful info:

  • Stack overflows are the most common problems source during development, when in trouble with crashes or anomalous behaviors always first verify stack sizes.
  • The required stack size can, and very often does change when changing compiler vendor, compiler version, compiler options, code type (ARM or THUMB for example).
  • Code compiled in THUMB mode uses more stack space compared to the same code compiled in ARM mode. In GCC this is related to lack of tail calls optimizations in THUMB mode, this is probably true also in other compilers.
  • Speed optimized code often requires less stack space compared to space optimized code. Be careful when changing optimizations.
  • The interrupts space overhead on the thread stacks (INT_REQUIRED_STACK defined in chcore.h) is included in the total working area size by the system macros THD_WORKING_AREA_SIZE() and THD_WORKING_AREA().
  • The correct way to reserve space into the thread stacks for interrupts processing is to override the PORT_INT_REQUIRED_STACK default value. Architectures with a dedicated interrupt stack do not require changes to this value. Resizing of the global interrupt stack may be required instead.
  • Often is a good idea to have some extra space in stacks unless you are really starved on RAM. Anyway, it is best to optimize stack space at the very end of your development cycle.