FreeRTOS 总共提供了5种内存分配方法:
heap_1.c
heap_2.c
heap_3.c
heap_4.c
heap_5.c
这五种分配方式各有各的优势,用户可根据应用情况按需使用,在分析源码之前先了解一下内存管理的一些相关知识,其中内存碎片一直是内存管理致力于解决的一项问题,内存碎片是指频繁地请求和释放不同大小的内存,结果就是当再次要求分配连续的内存时导致申请失败,原因是由于之前内存块被释放后,存在空闲内存块大小不一,空闲的小内存块穿插于各个大内存块中间,当需要申请的内存总比这些空闲小内存块大的时候,这些小内存就永远无法得到使用,但它又一直占着空间,这些无法合并的小内存块就叫内存碎片,内存碎片最终会导致内存明明是够的,却无法分配成功的现象。入下图所示:
FreeRTOS 对内存碎片也有做出相应的处理,后面代码会详细分析源码是如何尽量避免碎片产生的。这里先了解一下5中内存分配的优缺点:
heap_1:
适合不需要动态管理的应用,一旦创建好任务、信号量、队列就不再删除的应用,不会产生内存碎片,对于一般的应用这已经足够使用了
heap_2:
使用动态管理内存,用在可能会重复的删除任务、队列、信号量的应用中,如果每次分配的大小不一 样会导致内存碎片产生,如果针对一项操作每次分配的内存都一样大,那heap2就很合适了
heap_3:
对标准C中的函数 malloc() 和 free() 的简单封装(做线程保护),STM32的话在启动文件中修改Heap_Size来修改内存的大小,这种方式具有不确定性,可能会增加代码量
heap_4:
使用动态管理内存,用在可能会重复的删除任务、队列、信号量的应用中,不会像heap_2那样产生严重的内存碎片,但同样有可能产生内存碎片的问题,只有前后地址连续时才能合并内存块,一定程度上可以降低内存碎片的产生,一般有动态分配需求建议使用这种方式
heap_5:
和heap4的实现方式大致相同,在heap4的基础上允许内存跨越多个不连续的内存段,比如说有外部SRAM或SDRAM的时候,heap4只能选择外部或内部中的一个作为内存管理对象,但heap5则允许两个一起作为内存堆来使用。
在操作内存之前,用户需要自定义内存管理的对象,即内存堆,在 FreeRTOSConfig.c 中可以定义内存堆的大小:
#define configTOTAL_HEAP_SIZE ( ( size_t ) ( 17 * 1024 ) )
系统实际上就是去定义一个大的数组作为内存堆:
static uint8_t ucHeap[ configTOTAL_HEAP_SIZE ];
在了解各个分配方式的优缺点后,下面针对每个内存分配方式进行源码分析:
heap_1.c
内存分配源码分析:
void *pvPortMalloc( size_t xWantedSize )
{
void *pvReturn = NULL;
static uint8_t *pucAlignedHeap = NULL;
#if( portBYTE_ALIGNMENT != 1 )
{
/* 字节数8字节对齐 */
if( xWantedSize & portBYTE_ALIGNMENT_MASK )
{
/* 补充字节数对齐 */
xWantedSize += ( portBYTE_ALIGNMENT - ( xWantedSize & portBYTE_ALIGNMENT_MASK ) );
}
}
#endif
/* 任务调度器休眠 */
vTaskSuspendAll();
{
if( pucAlignedHeap == NULL )
{
/* 获取内存堆首地址,并确保地址8字节对齐 */
pucAlignedHeap = ( uint8_t * ) ( ( ( portPOINTER_SIZE_TYPE ) &ucHeap[ portBYTE_ALIGNMENT ] ) & ( ~( ( portPOINTER_SIZE_TYPE ) portBYTE_ALIGNMENT_MASK ) ) );
}
/* 检查是否有足够的内存 */
if( ( ( xNextFreeByte + xWantedSize ) < configADJUSTED_HEAP_SIZE ) &&
( ( xNextFreeByte + xWantedSize ) > xNextFreeByte ) )/* Check for overflow. */
{
/* 内存足够,返回内存地址 */
pvReturn = pucAlignedHeap + xNextFreeByte;
/* 内存地址索引变量递增 */
xNextFreeByte += xWantedSize;
}
traceMALLOC( pvReturn, xWantedSize );
}
/* 任务调度器恢复 */
( void ) xTaskResumeAll();
/* 内存分配失败的回调 */
#if( configUSE_MALLOC_FAILED_HOOK == 1 )
{
if( pvReturn == NULL )
{
extern void vApplicationMallocFailedHook( void );
vApplicationMallocFailedHook();
}
}
#endif
/* 返回分配到的内存地址 */
return pvReturn;
}
内存释放源码分析:
void vPortFree( void *pv )
{
/* 使用 heap1 一旦申请内存成功是不允许释放的,所以这里什么也不做 */
( void ) pv;
configASSERT( pv == NULL );
}
heap_2.c
内存分配源码分析:
void *pvPortMalloc( size_t xWantedSize )
{
BlockLink_t *pxBlock, *pxPreviousBlock, *pxNewBlockLink;
static BaseType_t xHeapHasBeenInitialised = pdFALSE;
void *pvReturn = NULL;
/* 任务调度器休眠 */
vTaskSuspendAll();
{
if( xHeapHasBeenInitialised == pdFALSE )
{
/* 第一次使用分配需要先初始化内存堆 */
prvHeapInit();
xHeapHasBeenInitialised = pdTRUE;
}
if( xWantedSize > 0 )
{
/* 需要分配的总大小=内存块大小+内存块描述结构体的大小 */
xWantedSize += heapSTRUCT_SIZE;
/* 字节数8字节对齐 */
if( ( xWantedSize & portBYTE_ALIGNMENT_MASK ) != 0 )
{
/* 未对齐,这里补充字节数对齐 */
xWantedSize += ( portBYTE_ALIGNMENT - ( xWantedSize & portBYTE_ALIGNMENT_MASK ) );
}
}
/* 检查申请的内存大小是否合理,合理则进行内存分配 */
if( ( xWantedSiz