Embedded Development Board Learning


Heap Memory in FreeRTOS #6 Static and Dynamic Allocation

Date: marzo 4, 2025

Author: Guillermo Garcia

Categories: RTOS Tags:

Understanding how FreeRTOS handles heap memory through static and dynamic allocation can improve performance and system stability. This article will explore these two allocation methods, their advantages and disadvantages.

Understanding Heap Memory in FreeRTOS

FreeRTOS provides multiple memory management schemes to allocate heap memory dynamically. These schemes are defined in heap_1.c to heap_5.c, each offering different features and trade-offs:

  • heap_1.c: Simplest, provides only pvPortMalloc() but lacks vPortFree() (no deallocation).
  • heap_2.c: Supports both allocation and deallocation but can fragment memory.
  • heap_3.c: Uses the standard C malloc() and free().
  • heap_4.c: Implements a coalescing allocator to reduce fragmentation.
  • heap_5.c: Supports multiple non-contiguous memory regions.

Choosing the right management scheme depends on system requirements and available RAM.

When the kernel requires RAM, instead of calling malloc() directly it calls pvPortMalloc().
When RAM is being freed, instead of calling free() directly, the kernel calls vPortFree().
pvPortMalloc() has the same prototype as malloc(), and vPortFree() has the same prototype
as free().

Memory Resources of a Task

When we create a task, the FreeRTOS kernel creates a series of resources in memory reserved exclusively for this task. The resources that a task needs are the following:

Task Control Block (TCB)

  • Contains the task information such as its status, priority, stack pointer, identifier, and other metadata.
  • In dynamic allocation, the TCB is allocated on the heap using pvPortMalloc().
  • In static allocation, the TCB is a user-defined static variable.

Task Stack

  • Each task needs its own stack to store local variables, CPU registers, and the return address on interrupts.
  • In dynamic allocation, the stack is allocated on the heap.
  • In static allocation, the stack is a user-defined variable.

Heap_2.c

The way these resources are managed for each task depends on the memory management algorithm chosen in the FreeRTOS kernel. In the case of heap_2.c, the management would be as shown in the following image.

heap memory managed for each task

A shows the array after three tasks have been created. A large free block remains at
the top of the array.

  • A shows the array after three tasks have been created. A large free block remains at
    the top of the array.
  • B shows the array after one of the tasks has been deleted. The large free block at the
    top of the array remains. There are now also two smaller free blocks that were
    previously allocated to the TCB and stack of the deleted task.
  • C shows the situation after another task has been created. Creating the task has
    resulted in two calls to pvPortMalloc(), one to allocate a new TCB and one to allocate
    the task stack. (The calls to pvPortMalloc() occur internally within the xTaskCreate()
    API function.)
/* This is in the FreeRTOSConfig.h file */ 
#define configTOTAL_HEAP_SIZE			( ( size_t ) ( 75 * 1024 ) )

Heap_2.c also uses a simple array dimensioned by configTOTAL_HEAP_SIZE. It uses a best
fit algorithm to allocate memory and, unlike heap_1, it does allow memory to be freed. Again,
the array is declared statically, so will make the application appear to consume a lot of RAM,
even before any of the array has been assigned.

Static vs. Dynamic Allocation

We now know that when we create tasks, exclusive memory resources will be created for each task. These resources are managed according to the algorithm chosen in the heap_2.c kernel for our case.

It is possible to manage memory resources in two ways in FreeRTOS: statically, that is, we assign the size of that memory, or dynamically we let the kernel manage and assign the resources.

Static Allocation

Static allocation is performed at compile time and does not require heap memory. Instead, memory is assigned to variables in the .bss or .data section of RAM.

Advantages:

  • Predictable memory usage.
  • No risk of fragmentation.
  • Improved stability and determinism.

Disadvantages:

  • Less flexible, as memory must be defined at compile time.
  • May lead to higher RAM consumption if not managed properly

Example of Static Allocation in FreeRTOS:

static StackType_t taskStack[configMINIMAL_STACK_SIZE];
static StaticTask_t taskControlBlock;

void vTaskFunction(void *pvParameters) {
    for(;;) {
        // Task Code
    }
}

void createTask() {
    xTaskCreateStatic(
        vTaskFunction,
        "TaskName",
        configMINIMAL_STACK_SIZE,
        NULL,
        tskIDLE_PRIORITY,
        taskStack,
        &taskControlBlock
    );
}

It is possible to enable Static Allocation support by placing the definition in the FreeRTOSConfig.h file.

/* This is in the FreeRTOSConfig.h file */  
#define configSUPPORT_STATIC_ALLOCATION   1

FreeRTOS API functions are available which all are using static memory:

When configSUPPORT_STATIC_ALLOCATION 1 is enabled it is necessary to define callback functions that must be provided by the application to supply the RAM used by the idle and timer service tasks. We define the functions with the following prototype.

/* configSUPPORT_STATIC_ALLOCATION is set to 1, so the application must provide an
   implementation of vApplicationGetIdleTaskMemory() to provide the memory that is
   used by the Idle task. */
void vApplicationGetIdleTaskMemory( StaticTask_t **ppxIdleTaskTCBBuffer,
                                    StackType_t **ppxIdleTaskStackBuffer,
                                    uint32_t *pulIdleTaskStackSize )
{
    /* If the buffers to be provided to the Idle task are declared inside this
       function then they must be declared static - otherwise they will be allocated on
       the stack and so not exists after this function exits. */
    static StaticTask_t xIdleTaskTCB;
    static StackType_t uxIdleTaskStack[ configMINIMAL_STACK_SIZE ];

    /* Pass out a pointer to the StaticTask_t structure in which the Idle task's
       state will be stored. */
    *ppxIdleTaskTCBBuffer = &xIdleTaskTCB;

    /* Pass out the array that will be used as the Idle task's stack. */
    *ppxIdleTaskStackBuffer = uxIdleTaskStack;

    /* Pass out the size of the array pointed to by *ppxIdleTaskStackBuffer.
       Note that, as the array is necessarily of type StackType_t,
       configMINIMAL_STACK_SIZE is specified in words, not bytes. */
    *pulIdleTaskStackSize = configMINIMAL_STACK_SIZE;
}

/*-----------------------------------------------------------*/

/* configSUPPORT_STATIC_ALLOCATION and configUSE_TIMERS are both set to 1, so the
   application must provide an implementation of vApplicationGetTimerTaskMemory()
   to provide the memory that is used by the Timer service task. */
void vApplicationGetTimerTaskMemory( StaticTask_t **ppxTimerTaskTCBBuffer,
                                     StackType_t **ppxTimerTaskStackBuffer,
                                     uint32_t *pulTimerTaskStackSize )
{
    /* If the buffers to be provided to the Timer task are declared inside this
       function then they must be declared static - otherwise they will be allocated on
       the stack and so not exists after this function exits. */
    static StaticTask_t xTimerTaskTCB;
    static StackType_t uxTimerTaskStack[ configTIMER_TASK_STACK_DEPTH ];

    /* Pass out a pointer to the StaticTask_t structure in which the Timer
       task's state will be stored. */
    *ppxTimerTaskTCBBuffer = &xTimerTaskTCB;

    /* Pass out the array that will be used as the Timer task's stack. */
    *ppxTimerTaskStackBuffer = uxTimerTaskStack;

    /* Pass out the size of the array pointed to by *ppxTimerTaskStackBuffer.
       Note that, as the array is necessarily of type StackType_t,
      configTIMER_TASK_STACK_DEPTH is specified in words, not bytes. */
    *pulTimerTaskStackSize = configTIMER_TASK_STACK_DEPTH;
}

Dynamic Allocation

Dynamic memory allocation occurs at runtime using pvPortMalloc() and vPortFree(). This provides greater flexibility but comes with risks such as fragmentation and potential memory leaks.

Advantages:

  • More efficient RAM usage when tasks require variable memory sizes.
  • Easier management of dynamic workloads.

Disadvantages:

  • Can lead to fragmentation over time.
  • Unpredictable memory availability.
  • Additional CPU overhead due to allocation and deallocation.

Example of Dynamic Allocation in FreeRTOS:

void vTaskFunction(void *pvParameters) {
    for(;;) {
        // Task Code
    }
}

void createTask() {
    xTaskCreate(
        vTaskFunction,
        "TaskName",
        configMINIMAL_STACK_SIZE,
        NULL,
        tskIDLE_PRIORITY,
        NULL
    );
}

It is possible to enable Dynamic Allocation support by placing the definition in the FreeRTOSConfig.h file.

/* This is in the FreeRTOSConfig.h file */   
#define configSUPPORT_DYNAMIC_ALLOCATION  1

Closing

Choosing between static and dynamic allocation in FreeRTOS depends on the system’s memory constraints and performance requirements. Static allocation offers stability and predictability, while dynamic allocation provides flexibility at the cost of potential fragmentation.



Card image cap
Guillermo Garcia Thanks for reading! I am one of the editors of this site and I am committed to providing you with the best information possible by sharing my experience in embedded systems.


Comments... There are no comments.

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

Publicidad