FreeRTOS uses a region of memory called Heap (into the RAM) to allocate memory for tasks, queues, timers , semaphores, mutexes and when dynamically creating variables. FreeRTOS heap is different than the system heap defined at the compiler level.
- When FreeRTOS requires RAM instead of calling the standard malloc it calls PvPortMalloc(). When it needs to free memory it calls PvPortFree() instead of the standard free().
- FreeRTOS offers several heap management schemes that range in complexity and features. It includes five sample memory allocation implementations, each of which are described in the following link: http://www.freertos.org/a00111.html
- The total amount of available heap space is set by configTOTAL_HEAP_SIZE which is defined in FreeRTOSConfig.h.
- The xPortGetFreeHeapSize() API function returns the total amount of heap space that remains unallocated (allowing the configTOTAL_HEAP_SIZE setting to be optimized). The total amount of heap space that remains unallocated is also available with xFreeBytesRemaining variable for heap management schemes 2 to 5.
Task Control Block
- Each created task (including the idle task) requires a Task Control Block (TCB) and a stack that are allocated in the heap.
- The TCB size in bytes depends of the options enabled in the FreeRTOSConfig.h.
- With minimum configuration the TCB size is 24 words i.e 96 bytes.
- if configUSE_TASK_NOTIFICATIONS enabled add 8 bytes (2 words)
- if configUSE_TRACE_FACILITY enabled add 8 bytes (2 words)
- if configUSE_MUTEXES enabled add 8 bytes (2 words).
- The task stack size is passed as argument when creating at task. The task stack size is defined in words of 32 bits not in bytes.
- osThreadDef(Task_A, Task_A_Function, osPriorityNormal, 0, stacksize );
- FreeRTOS requires to allocate in the heap for each task :
- number of bytes = TCB_size + (4 x task stack size)
- configMINIMAL_STACK_SIZE defines the minimum stack size that can be used in words. the idle task stack size takes automatically this value
The necessary task stack size can be fine-tuned using the APIuxTaskGetStackHighWaterMark() as follow:
- Use an initial large stack size allowing the task to run without issue (example 4KB)
- The API uxTaskGetStackHighWaterMark() returns the minimum number of free bytes (ever encountered) in the task stack. Monitor the return of this function within the task.
- Calculate the new stack size as the initial stack size minus the minimum stack free bytes.
- The method requires that the task has been running enough to enter the worst path (in term of stack consumption)
Queue , Timer & Semaphore Memory Details
- FreeRTOS requires to allocate in the heap for each message queue:
- number of bytes = 76 + queue_storage_area.
- queue_storage_area (in bytes) = (element_size * nb_elements) + 16
- When Timers are enabled (configUSE_TIMERS enabled) , the scheduler creates automatically the timers service task (daemon) when started. The timers service task is used to control and monitor (internally) all timers that the user will create. The timers task parameters are set through the fowling defines :
- To save heap size (i.e RAM footprint) it is recommended to disable the define “configUSE_TIMERS” when timers are not used by the application
- The scheduler also creates automatically a message queue used to send commands to the timers task (timer start, timer stop …)
- The number of elements of this queue (number of messages that can be hold) are configurable through the define:
- Each semaphore declared by the user application requires 88 bytes to be allocated in the heap.
- Each mutex declared by the user application requires 88 bytes to be allocated in the heap.
How to reduce RAM footprint
- Optimize stack allocation for each task :
- uxTaskGetStackHighWaterMark(). This API returns the minimum number of free bytes (ever encountered) in the task stack
- vApplicationStackOverflowHook(). This API is a stack overflow callback called when a stack overflow is detected (available when activating the define configCHECK_FOR_STACK_OVERFLOW)
- Adjust heap dimensioning :
- xPortGetFreeHeapSize(). API that returns the total amount of heap space that remains unallocated. Must be used after created all tasks, message queues, semaphores, mutexes in order to check the heap consumption and eventually re-adjust the application define ” configTOTAL_HEAP_SIZE”.
- The total amount of heap space that remains unallocated is also available with xFreeBytesRemaining variable for heap management schemes 2 to 5
- If heap_1.c, heap_2.c, heap_4.c or heap_5.c are being used, and nothing in your application is ever calling malloc() directly (as opposed to pvPortMalloc()), then ensure the linker is not allocated a heap to the C library, it will never get used.
- Recover and minimize the stack used by main and rationalize the number of tasks.
- If the application doesn’t use any software timers then disable the define configUSE_TIMERS
- If the application doesn’t use any mutexe then disable the define configUSE_MUTEXES
- configMAX_PRIORITIES defines the number of priorities available to the application tasks. Any number of tasks can share the same priority. Each available priority consumes RAM within the RTOS kernel so this value should not be set any higher than actually required by the application. It is recommended to declare tasks with contiguous priority levels: 1, 2, 3, 4, etc… rather than 10, 20, 30, 40, etc. The scheduler actually allocates statically the ready task list of size configMAX_PRIORITIES * list entry structure : so high value of configMAX_PRIORITIES shall be avoided to reduce RAM footprints.
FreeRTOS Memory allocation
FreeRTOS manages own heap for:
- Dynamic memory allocation
- It is possible to select type of memory allocation
- FreeRTOS Memory Management > Categories > Middleware > FreeRTOS > Config Parameters
In-depth memory usage user can find inside FreeRTOS Heap Usage Tab
Uses first fit algorithm to allocate memory. Simplest allocation method (deterministic), but does not allow freeing of allocated memory => could be interesting when no memory freeing is necessary
Heap_1.c implements a very basic version of pvPortMalloc(), and does not implement vPortFree(). Applications that never delete a task, or other kernel object, have the potential to use heap_1.
Some commercially critical and safety critical systems that would otherwise prohibit the use of dynamic memory allocation also have the potential to use heap_1. Critical systems often prohibit dynamic memory allocation because of the uncertainties associated with nondeterminism, memory fragmentation, and failed allocations—but Heap_1 is always deterministic, and cannot fragment memory.
- Not recommended to new projects. Kept due to backward compatibility.
- Implements the best fit algorithm for allocation
- Allows memory free() operation but doesn’t combine adjacent free blocks risk of fragmentation
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. Tasks are created using the xTaskCreate() API function, which is described in section 3.4. The calls to pvPortMalloc() occur internally within xTaskCreate(). Every TCB is exactly the same size, so the best fit algorithm ensures that the block of RAM previously allocated to the TCB of the deleted task is reused to allocate the TCB of the new task.
- Implements simple wrapper for standard C library malloc() and free(); wrapper makes these functions thread safe, but makes code increase and not deterministic
- It uses linker heap region.
- configTOTAL_HEAP_SIZE setting has no effect when this model is used
Uses first fit algorithm to allocate memory. It is able to combine adjacent free memory blocks into a single block
Heap_4 combines (coalescences) adjacent free blocks into a single larger block, minimizing the risk of fragmentation, and making it suitable for applications that repeatedly allocate and free different sized blocks of RAM.
1. A shows the array after three tasks have been created. A large free block remains at the top of the array.
2. B shows the array after one of the tasks has been deleted. The large free block at the top of the array remains. There is also a free block where the TCB and stack of the task that has been deleted were previously allocated. Note that, unlike when heap_2 was demonstrated, the memory freed when the TCB was deleted, and the memory freed when the stack was deleted, does not remain as two separate free blocks, but is instead combined to create a larger single free block.
3. C shows the situation after a FreeRTOS queue has been created. Queues are created using the xQueueCreate() API function, which is described in section 4.3. xQueueCreate() calls pvPortMalloc() to allocate the RAM used by the queue. As heap_4 uses a first fit algorithm, pvPortMalloc() will allocate RAM from the first free RAM block that is large enough to hold the queue, which in Figure 7, was the RAM freed when the task was deleted. The queue does not consume all the RAM in the free block however, so the block is split into two, and the unused portion remains available to future calls to pvPortMalloc()
4. D shows the situation after pvPortMalloc() has been called directly from application code, rather than indirectly by calling a FreeRTOS API function. The user allocated block was small enough to fit in the first free block, which was the block between the memory allocated to the queue, and the memory allocated to the following TCB. The memory freed when the task was deleted has now been split into three separate blocks; the first block holds the queue, the second block holds the user allocated memory, and the third block remains free.
5. E show the situation after the queue has been deleted, which automatically frees the memory that had been allocated to the deleted queue. There is now free memory on either side of the user allocated block.
6. F shows the situation after the user allocated memory has also been freed. The memory that had been used by the user allocated block has been combined with the free memory on either side to create a larger single free block. Heap_4 is not deterministic, but is faster than most standard library implementations of malloc() and free()
- STM32F429IDISCOVERY board
- Mini USB Cable
- Jumper wire
Successfully demonstrated FreeRTOS memory management using STM32CubeMx
If you enjoyed this article, share your feedback.