Feb 15 / Wadix Technologies

FreeRTOS Memory Management Explained: Static vs Dynamic Allocation and Heap Schemes

FreeRTOS Memory Management Explained: Static vs Dynamic Allocation and Heap Schemes

1. Introduction

In a FreeRTOS based system, almost everything eventually consumes memory. Tasks need stacks and control blocks. Queues, semaphores, timers, and event groups all require storage. Application buffers, protocol layers, and drivers add their own demands. On a desktop system, memory allocation is often treated as a convenience feature provided by the operating system. In an embedded system, it is part of the system architecture.

FreeRTOS supports both static and dynamic allocation of kernel objects, and it provides several heap implementations, commonly referred to as the heap_x schemes. At first glance, these may look like simple configuration options. In practice, they encode important design decisions about reliability, timing behavior, and long term stability.

To use them well, it helps to step back from the API level and look at how memory is actually obtained by the kernel, what risks this introduces, and how FreeRTOS exposes different allocation models to the application.


2. Why memory allocation is a design decision in embedded systems

In many embedded systems, memory is limited, fixed in size, and shared by all software components. There is no virtual memory and no safety net if allocation fails. If the system cannot obtain the memory it needs at the moment it needs it, the result is often a reset, a lockup, or silent corruption.

Dynamic memory allocation also has timing implications. Some allocation strategies take a constant amount of time, while others depend on the current state of the heap. In a real time system, this difference matters. Allocation time becomes part of the worst case execution time of the code path that uses it.

Finally, memory behavior over time matters. Many embedded systems are expected to run for weeks, months, or years without rebooting. In such systems, fragmentation and long term heap health can be more important than peak memory usage on day one.

For these reasons, memory allocation in FreeRTOS is not just a coding convenience. It is a system level design choice.


3. Static vs dynamic allocation in FreeRTOS at the API level

The difference between static and dynamic allocation in FreeRTOS is visible directly in the APIs.

When using dynamic allocation, the application typically creates objects like this:

xTaskCreate(TaskFunc, "T1", 512, NULL, 2, NULL);

xQueueCreate(10, sizeof(uint8_t));

xSemaphoreCreateBinary();

In all of these cases, FreeRTOS allocates memory internally for control structures and buffers. That memory comes from the FreeRTOS heap, using pvPortMalloc().

The same objects can be created using static allocation:

static StackType_t taskStack[512];

static StaticTask_t taskTcb;

xTaskCreateStatic(TaskFunc, "T1", 512, NULL, 2, taskStack, &taskTcb);

For queues and semaphores, the pattern is similar:

static uint8_t queueStorage[10];

static StaticQueue_t queueStruct;

xQueueCreateStatic(10, sizeof(uint8_t), queueStorage, &queueStruct);

In these static variants, no heap memory is used at all. The application provides all required storage. FreeRTOS only initializes and uses that memory.

From a system point of view, this is a fundamental difference. The dynamic APIs depend on heap behavior at runtime. The static APIs fix memory usage at build time and remove allocation failures and allocation timing from runtime behavior.


4. What the FreeRTOS heap actually is

A common source of confusion is the term heap itself. In FreeRTOS, the kernel does not rely on the standard C library heap unless explicitly configured to do so. Instead, it provides its own heap implementations. All dynamically created kernel objects, such as tasks, queues, semaphores, and timers, draw their memory from this FreeRTOS heap.

At the lowest level, all dynamic allocation in FreeRTOS goes through two functions:

void *pvPortMalloc(size_t size);

void vPortFree(void *ptr);

The selected heap_x implementation defines how these two functions behave.

Fig 1. Relationship between application code, FreeRTOS kernel objects, and the FreeRTOS heap memory region.

From the application point of view, the heap is simply a region of memory reserved for kernel allocations. From the system point of view, the heap implementation defines allocation policy, timing behavior, and fragmentation characteristics.


5. The heap_x family and what the schemes really mean

FreeRTOS provides several heap implementations, commonly known as heap_1, heap_2, heap_3, heap_4, and heap_5. They are not just different code files. Each one represents a different policy choice.

At a high level:

  • heap_1 allows allocation but not freeing. vPortFree() is not supported. This avoids fragmentation entirely but only works for systems that never delete objects.

  • heap_2 allows freeing memory but does not coalesce free blocks, so fragmentation can grow over time.

  • heap_3 is a wrapper around the C library malloc and free, which means it inherits their behavior and limitations.

  • heap_4 supports allocation and freeing and also coalesces adjacent free blocks to reduce fragmentation.

  • heap_5 extends this idea to multiple non contiguous memory regions.

From the API point of view, xTaskCreate() or xQueueCreate() looks the same in all cases. From the system point of view, the behavior of pvPortMalloc() underneath is completely different.

Fig 2. Different FreeRTOS heap schemes represent different policies for allocation, freeing, fragmentation handling, and memory regions.

Choosing a heap scheme is therefore choosing a memory management policy, not just an implementation detail.


6. Fragmentation and long running systems

Fragmentation is often discussed in abstract terms, but in embedded systems it has very concrete consequences. Over time, a heap that supports freeing can become split into many small gaps. Even if the total free memory is large enough, it may no longer be possible to satisfy a request for a larger contiguous block.

This is especially relevant in systems that run for long periods without restart and that create and delete objects dynamically as part of normal operation. In such systems, memory usage patterns over time can matter more than the initial configuration.

This is why some systems choose heap schemes that do not support freeing at all, and instead allocate everything once at startup. Others accept fragmentation but choose schemes like heap_4 or heap_5 that attempt to limit it.


7. Determinism and worst case execution time

In a real time system, it is not enough for memory allocation to usually be fast. It must be fast in the worst case, or at least bounded in a way that fits the system timing requirements.

Some heap schemes have predictable, tightly bounded allocation time. Others may need to search through free lists or merge blocks, which makes their execution time depend on the current state of the heap.

When APIs like xTaskCreate() or xQueueCreate() are used in time critical paths, this difference matters. The choice of heap scheme directly affects whether allocation time can be treated as a constant or must be considered a variable cost.


8. System design impact and choosing a strategy

Because of these trade offs, memory allocation strategy is not just a low level configuration choice. It affects system startup, error handling, long term stability, and real time behavior.

Common design patterns include allocating all dynamic objects once at startup and never freeing them, mixing static and dynamic allocation so that only non critical features use the heap, or avoiding dynamic allocation entirely after initialization.

Fig 3. Typical memory allocation strategies in a FreeRTOS based system.

Each of these patterns reflects a different balance between flexibility, predictability, and long term robustness.


9. Conclusion

FreeRTOS gives you several ways to allocate memory, but it does not make the design decision for you. Choosing between static and dynamic allocation, and choosing a particular heap scheme, is really about choosing a policy for how your system will behave under memory pressure, over long uptimes, and under real time constraints.

The heap_x implementations are not just different code files. They represent different assumptions about fragmentation, determinism, and system lifetime. Treating these choices as part of the system architecture, rather than as defaults to accept, leads to designs that are easier to reason about and more reliable in the field.

Created with