Table of Contents

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: