FreeRTOS memory management method (super detailed)

Memory management

        We know that the RTOS kernel requires RAM every time a task, queue, mutex, software timer, semaphore or event group is created, RAM can be obtained from the RTOS API object The RTOS heap within the creation function is automatically allocated dynamically, or provided by the application writer.

        If the RTOS object is created dynamically, the standard C library malloc() and free() functions can sometimes be used for this purpose, but... theyare used on embedded systems Not always available, takes up valuable code space, is not thread-safe, and is not deterministic (the time it takes to execute the function will vary from call to call) , so more often than not What is needed is not an alternative memory allocation implementation.

        The RAM and timing requirements of one embedded/real-time system may be very different from another, so a single RAM allocation algorithm will only ever work for a subset of applications.

        To avoid this problem, FreeRTOS keeps the memory allocation API in its portable layer. The portability layer allows the provision of application-specific implementations suitable for the real-time system being developed, in addition to the source files that implement core RTOS functionality. When the RTOS kernel needs RAM, instead of calling malloc(), it calls pvPortMalloc(). When freeing RAM, the RTOS kernel calls vPortFree() instead of free().

        ​ ​ ​ FreeRTOS provides several heap management options, varying in complexity and functionality. You can also provide your own heap implementation, or even use two heap implementations at the same time. Using both heaps simultaneously allows task stacks and other RTOS objects to be placed in internal RAM, and application data to be placed in slower external RAM.

5 management methods

        The source code provides 5 files by default, corresponding to the 5 methods of memory management. Each provided implementation is contained in a separate source file (heap_1.c, heap_2.c, heap_3.c, heap_4.c, and heap_5.c respectively), located in the main RTOS source The code download content is in the Source/Portable/MemMang directory. Other implementations can be added as needed. Only one of these source files should be included in a project at a time [the heap defined by these portable layer functions will be used by the RTOS kernel, even if the application using the RTOS chooses to use its own heap implementation].

heap_1 - The simplest, does not allow memory to be released.

heap_2 - Allows memory to be released, but adjacent free blocks will not be merged.

heap_3 - A simple wrapper around standard malloc() and free() to ensure thread safety.

heap_4 - Merge adjacent free blocks to avoid fragmentation. Contains absolute address placement options.

heap_5 - Like heap_4, a heap capable of spanning multiple non-contiguous memory regions.

Notice:

        Heap_1 is less useful because FreeRTOS added static allocation support.

        heap_2 is now considered legacy, as the newer heap_4 implementation is preferred.

heap_1.c

        Heap_1 is less useful because FreeRTOS added static allocation support. heap_1 is the simplest implementation. Once memory is allocated, it does not allow the memory to be released again. Nonetheless, heap_1.c is suitable for a large number of embedded applications. This is because many small and deeply embedded applications create all the tasks, queues, semaphores, etc. needed at system startup and use all these objects for the lifetime of the program (until the application is closed or restarted again) . No content will be deleted.

        This implementation simply subdivides a single array into smaller chunks when RAM is required. The total size of the array (total size of the heap) is set via configTOTAL_HEAP_SIZE (defined in FreeRTOSConfig.h). The configAPPLICATION_ALLOCATED_HEAP FreeRTOSConfig.h configuration constant is provided to allow the heap to be placed at specific addresses in memory.

        The xPortGetFreeHeapSize() API function returns the total amount of unallocated heap space, allowing optimization of the configTOTAL_HEAP_SIZE setting.

heap_1 implementation

        ​​​This can be used if your application never deletes tasks, queues, semaphores, mutexes, etc. (This actually covers most applications using FreeRTOS).

        Always deterministic (always takes the same time to execute) and does not cause memory fragmentation. It is very simple and allocates memory from a statically allocated array, which means it is generally suitable for applications that do not allow true dynamic memory allocation.

When using heap_1, the memory allocation process is as shown in the figure below:

A: The entire array is free before creating the task

B: After the first task was created, the blue area was assigned

C: Array usage after creating 3 tasks

heap_2.c

        Heap_2 is now considered legacy, heap_4 is preferred. heap_2 uses a best-fit algorithm, and unlike Scenario 1, which allows freeing previously allocated blocks, it does not combine adjacent free blocks into one large block. See heap_4.c for an implementation that does not merge free blocks.

        The total amount of available heap space is set via configTOTA L_HEAP_SIZE (defined in FreeRTOSConfig.h). The configAPPLICATION_ALLOCATED_HEAP FreeRTOSConfig.h configuration constant is provided to allow the heap to be placed at specific addresses in memory. The xPortGetFreeHeapSize() API function returns the total amount of unallocated heap space, (allowing optimization of the configTOTAL_HEAP_SIZE setting), but does not provide information about how unallocated memory is fragmented into smaller chunks.

        The signature of the pvPortCalloc() function is the same as the standard library calloc function. It allocates memory for an array of objects and initializes all bytes in the allocated storage to zero. If the allocation is successful, it returns a pointer to the lowest byte in the allocated memory block. If the allocation fails, it returns a null pointer.

heap_2 implementation

        Even if the application deduplicates tasks, queues, semaphores, mutexes, etc., they will still be available, but be aware of the following information about memory fragmentation. Cannot be used if the memory being allocated and freed is of random size.

For example:

        If the application creates and deletes tasks dynamically, and the stack size allocated to the task being created is always the same, then heap2.c can be used in most cases. However, if the size of the stack allocated to the task being created is not always the same, the available free memory may become fragmented into many small pieces, eventually causing the allocation to fail. In this case, heap_4.c is a better choice.

        If the application creates and deletes tasks dynamically, and the queue storage area is the same in each case (the queue storage area is the queue item size times the queue length), then heap_2.c can be used in most cases. However, if the queue storage area is not the same in each case, then the available free memory may become fragmented into many small pieces, eventually causing the allocation to fail. In this case, heap_4.c is a better choice.

        Applications call pvPortMalloc() and vPortFree() directly, rather than just indirectly through other FreeRTOS API functions.

        If your application’s queues, tasks, semaphores, mutexes, etc. are in an unpredictable order, this can lead to memory fragmentation. This is not possible for almost all applications, but it should be kept in mind. Non-deterministic, but more efficient than most standard C library malloc implementations.

        heap_2.c is suitable for many small real-time systems where objects must be created dynamically. See heap_4 for a similar implementation that combines free memory blocks into a single large memory block.

When using heap_2, the memory allocation process is as shown in the figure below:

A: 3 tasks were created

B: A task is deleted, and the free memory has three parts: the top level, the TCB space of the deleted task, and the Stack space of the deleted task.

C: A new task is created. Because the TCB and stack size are the same as those of the previously deleted task, the original memory is just allocated.

heap_3.c

        This is a simple wrapper implemented for the standard C library malloc() and free() functions, and in most cases will be provided with the compiler of your choice. This wrapper simply makes the malloc() and free() functions thread-safe.

heap_3 implementation

        The linker needs to set up the heap, and the compiler library needs to provide malloc() and free() implementations. Not deterministic and can significantly increase RTOS kernel code size.

        Note that the configTOTAL_HEAP_SIZE setting in FreeRTOSConfig.h has no effect when using heap_3.

heap_4.c

         This scheme uses the first adaptation algorithm, and unlike scheme 2, it does form adjacent free memory blocks into a single large memory block (it does contain a merge algorithm).

        The total amount of available heap space is set via configTOTAL_HEAP_SIZE (defined in FreeRTOSConfig.h). The configAPPLICATION_ALLOCATED_HEAP FreeRTOSConfig.h configuration constant is provided to allow the heap to be placed at specific addresses in memory.

        The xPortGetFreeHeapSize() API function returns the total amount of unallocated heap space when called, and the xPortGetMinimumEverFreeHeapSize() API function returns the minimum amount of free heap space that already exists on the system when the FreeRTOS application starts. Neither function provides information about how unallocated memory is fragmented into small chunks.

        The vPortGetHeapStats() API function provides additional information. It fills in the members of a heap_t structure as shown below.

/* Prototype of the vPortGetHeapStats() function. */
void vPortGetHeapStats( HeapStats_t *xHeapStats );

/* Definition of the Heap_stats_t structure. */

typedef struct xHeapStats
{
       size_t xAvailableHeapSpaceInBytes;      /* The total heap size currently available - this is the sum of all the free blocks, not the largest block that can be allocated. */
       size_t xSizeOfLargestFreeBlockInBytes;     /* The maximum size, in bytes, of all the free blocks within the heap at the time vPortGetHeapStats() is called. */
       size_t xSizeOfSmallestFreeBlockInBytes; /* The minimum size, in bytes, of all the free blocks within the heap at the time vPortGetHeapStats() is called. */
       size_t xNumberOfFreeBlocks;            /* The number of free memory blocks within the heap at the time vPortGetHeapStats() is called. */
       size_t xMinimumEverFreeBytesRemaining; /* The minimum amount of total free memory (sum of all free blocks) there has been in the heap since the system booted. */
       size_t xNumberOfSuccessfulAllocations;   /* The number of calls to pvPortMalloc() that have returned a valid memory block. */
       size_t xNumberOfSuccessfulFrees;     /* The number of calls to vPortFree() that has successfully freed a block of memory. */
} HeapStats_t;

        The signature of the pvPortCalloc() function is the same as the standard library calloc function. It allocates memory for an array of objects and initializes all bytes in the allocated storage to zero. If the allocation is successful, it returns a pointer to the lowest byte in the allocated memory block. If the allocation fails, it returns a null pointer.

heap_4 uses

        Even if the application deletes tasks, queues, semaphores, mutexes, etc. repeatedly, they are still available.

        less likely to result in severe fragmentation of the heap space into multiple small chunks than the heap_2 implementation (even if the memory being allocated and freed is of random size). Not deterministic, but more efficient than most standard C library malloc implementations.

        heap_4.c is particularly useful for applications that want to use the portable layer memory allocation scheme directly in the application code (rather than indirectly by calling the API functions pvPortMalloc() and vPortFree()).

        The execution time of Heap_4 is uncertain, but its efficiency is higher than malloc and free of the standard library.

heap_5.c

         This scheme uses the same first fit and memory merging algorithm as heap_4, allowing the heap to span multiple non-adjacent (non-contiguous) memory regions. Heap_5 is initialized by calling vPortDefineHeapRegions() and must not be used until vPortDefineHeapRegions() is executed. Creating RTOS objects (tasks, queues, semaphores, etc.) will implicitly call pvPortMalloc(), so when using heap_5 it is critical to call vPortDefineHeapRegions() before creating any such objects.

        vPortDefineHeapRegions() takes a single parameter, which is an array of HeapRegion_t structures. HeapRegion_t is defined in portable.h as

typedef struct HeapRegion
{
    /* Start address of a block of memory that will be part of the heap.*/
    uint8_t *pucStartAddress;

    /* Size of the block of memory. */
    size_t xSizeInBytes;
} HeapRegion_t;

The HeapRegion_t type definition

        Arrays are terminated with empty zero-sized area definitions. The memory areas defined in the array must be displayed in address order, from low address to high address. The following source code snippet provides an example. Additionally, the MSVC Win32 emulator demo also uses heap_5 so it can be used as a reference.

/* Allocate two blocks of RAM for use by the heap.  The first is a block of
0x10000 bytes starting from address 0x80000000, and the second a block of
0xa0000 bytes starting from address 0x90000000.  The block starting at
0x80000000 has the lower start address so appears in the array fist. */

const HeapRegion_t xHeapRegions[] =
{
    { ( uint8_t * ) 0x80000000UL, 0x10000 },
    { ( uint8_t * ) 0x90000000UL, 0xa0000 },
    { NULL, 0 } /* Terminates the array. */
};
/* Pass the array into vPortDefineHeapRegions(). */
vPortDefineHeapRegions( xHeapRegions );

Initialising heap_5 after defining the memory blocks to be used by the heap

        The xPortGetFreeHeapSize() API function returns the total amount of unallocated heap space when called, and the xPortGetMinimumEverFreeHeapSize() API function returns the minimum amount of free heap space that already exists on the system when the FreeRTOS application starts. Neither function provides information about how unallocated memory is fragmented into small chunks.

        The signature of the pvPortCalloc() function is the same as the standard library calloc function. It allocates memory for an array of objects and initializes all bytes in the allocated storage to zero. If the allocation is successful, it returns a pointer to the lowest byte in the allocated memory block. If the allocation fails, it returns a null pointer.

        The vPortGetHeapStats() API function provides additional information about the heap status.

Heap related functions

pvPortMalloc/vPortFree

function prototype

void * pvPortMalloc( size_t xWantedSize ); // 分配内存,如果分配内存不成功,则返回值为NULL。

void vPortFree( void * pv );  // 释放内存

        Function: Allocate memory and release memory. If memory allocation is unsuccessful, the return value is NULL.

xPortGetFreeHeapSize

function prototype

size_t xPortGetFreeHeapSize( void );

        How much free memory is currently available? This function can be used to optimize memory usage. For example, after all kernel objects are allocated, executing this function returns 2000, then configTOTAL_HEAP_SIZE can be reduced by 2000.

        NOTE: Not available in heap_3.

xPortGetMinimumEverFreeHeapSize

function prototype

size_t xPortGetMinimumEverFreeHeapSize( void );

        Return: The minimum value of the free memory capacity during the program is running.

        ​​​​​Note: Only heap_4 and heap_5 support this function.

malloc failed hook function

void * pvPortMalloc( size_t xWantedSize )
{
    ......
    #if ( configUSE_MALLOC_FAILED_HOOK == 1 )
        {
            if( pvReturn == NULL )
            {
                extern void vApplicationMallocFailedHook( void );
                vApplicationMallocFailedHook();
            }
        }
    #endif
    return pvReturn;       
}

        Inside the pvPortMalloc function, so if you want to use this hook function: In FreeRTOSConfig.h, define configUSE_MALLOC_FAILED_HOOK as 1. Provide vApplicationMallocFailedHook function

        This function will only be called when pvPortMalloc fails.

Commonly used management methods

Dynamic Memory Allocation

        ​​​​ FreeRTOS provides built-in functions for dynamic memory allocation, such as pvPortMalloc and vPortFree. These functions allow tasks to request and release memory at runtime. This approach is useful for applications that require flexible memory management, but care needs to be taken to avoid memory leaks and fragmentation.

Static Memory Allocation

        FreeRTOS allows users to allocate static memory for tasks and kernel objects (such as queues, semaphores, etc.) at compile time. By defining appropriate macros in the FreeRTOS configuration file, the memory of the task's stack and kernel objects can be statically allocated.

Memory Pools

        The memory pool is a memory area created during system initialization and is used to store fixed-size memory blocks. Tasks can request memory blocks from the memory pool and return them to the memory pool after use. This helps reduce memory fragmentation.

Stack Auto-Grow

        ​ ​ ​ FreeRTOS allows a task’s stack to grow automatically at runtime to adapt to dynamic needs while the task is running. This approach eliminates the need to know the maximum depth of the stack in advance when the task is executed, but also requires care to prevent stack overflow.

Mixed use of static API and dynamic API

        In FreeRTOS, you can selectively use static API or dynamic API, and choose the appropriate method according to application requirements. This allows you to use static allocation for some tasks and dynamic allocation for others, taking full advantage of each approach.

        FreeRTOS provides a flexible memory management mechanism, and you can choose the appropriate method according to specific application scenarios. Special attention needs to be paid to the problems of memory leaks and fragmentation when using dynamic memory allocation, while static memory allocation and memory pools can reduce these problems to a certain extent.

Guess you like

Origin blog.csdn.net/m0_56694518/article/details/134901019