FreeRTOS内存管理分析

目录

heap_1.c内存管理算法

heap_2.c内存管理算法

heap_3.c内存管理算法

 heap_4.c内存管理算法

heap_5.c内存管理算法


内存管理对应用程序和操作系统来说非常重要,而内存对于嵌入式系统来说是寸土寸金的资源,FreeRTOS操作系统将内核与内存管理分开实现,操作系统内核仅规定了必要的内存管理函数原型,而不关心这些函数的具体实现。这样做大有好处,可以增加系统的灵活性,不同的应用场合可以使用不同的内存分配实现,选择对自己更有利的内存管理算法。比如对于安全型的嵌入式系统,通常不允许动态分配内存,那么可以采用非常简单的内存管理策略,一经申请的内存,甚至不允许被释放。
FreeRTOS内核规定的几个内存管理的函数原型为:

1. void *pvPortMalloc( size_t xSize ) :内存申请函数
2. void vPortFree( void *pv ) :内存释放函数
3. void vPortInitialiseBlocks( void ) :初始化内存堆函数
4. size_t xPortGetFreeHeapSize( void ) :获取当前未分配的内存堆大小
5. size_t xPortGetMinimumEverFreeHeapSize( void ):获取未分配的内存堆历史最小值

FreeRTOS中内存管理的接口函数为pvPortMalloc、vPortFree,对应于C库的malloc和free函数。

源码中对应的提供了5个文件,对应内存管理的5种方法。

文件        优点缺点
heap_1.c分配简单,时间确定只分配,不回收
heap_2.c动态分配,最佳匹配碎片,时间不定
heap_3.c调用标准库函数速度慢,时间不定
heap_4.c相邻空闲内存可以合并可解决碎片问题,时间不定
heap_5.c在heap_4.c基础上支持分隔的内存块可解决碎片问题,时间不定

对于heap_1.c和heap_2.c和heap_4.c这三种内存管理算法,内存堆实际上是一个很大的静态数组,定义为:

static uint8_t ucHeap[ configTOTAL_HEAP_SIZE ];

其中宏configTOTAL_HEAP_SIZE用来定义内存堆的大小,这个宏在FreeRTOSConfig.h中设置。
对于heap_3.c,这种策略只是简单的包装了标准库中的malloc()和free()函数,包装后的malloc()和free()函数具备线程保护。因此,内存堆需要通过编译器或者启动文件设置堆空间。
heap_5.c比较有趣,它允许程序设置多个非连续内存堆,比如需要快速访问的内存堆设置在片内RAM,稍微慢速访问的内存堆设置在外部RAM。每个内存堆的起始地址和大小由应用程序设计者定义。(需要手动自己定义一个数组,手动调用函数初始化内存)
 

heap_1.c内存管理算法

 通过阅读heap_1.c的源码分析可以知道,它只实现了pvPortMalloc,没有实现vPortFree.

如果你的程序不需要删除内核对象,那么可以使用heap_1.c,它只分配不回收内存:

  • 实现最简单
  • 没有碎片问题
  • 一些要求非常严格的系统里,不允许使用动态内存,就可以使用heap_1

对于这种内存分配,我们可以把内存看成一个一根完整的长面包,每次申请内存,就从一端切下适当长度的面包返回给申请者,知道面包被分配完毕,就这么简单。

它的实现原理很简单,首先定义一个大数组:

/* Allocate the memory for the heap. */
#if ( configAPPLICATION_ALLOCATED_HEAP == 1 )/* The application writer has already defined the array used for the RTOS
* heap - probably so it can be placed in a special segment or address. */extern uint8_t ucHeap[ configTOTAL_HEAP_SIZE ];
#elsestatic uint8_t ucHeap[ configTOTAL_HEAP_SIZE ];
#endif /* configAPPLICATION_ALLOCATED_HEAP */

然后对于 pvPortMalloc 调用时,从这个数组中分配空间。

FreeRTOS在创建任务时,需要2个内核对象:task control block(TCB)、stack。

使用heap_1时,内存分配过程如下图所示:

  • A:创建任务之前整个数组都是空闲的
  • B:创建第1个任务之后,蓝色区域被分配出去了
  • C:创建3个任务之后的数组使用情况

 内存申请:pvPortMalloc


/*-----------------------------------------------------------*/void *pvPortMalloc( size_t xWantedSize )
{
void *pvReturn = NULL;
static uint8_t *pucAlignedHeap = NULL;/* Ensure that blocks are always aligned to the required number of bytes. */#if( portBYTE_ALIGNMENT != 1 ){if( xWantedSize & portBYTE_ALIGNMENT_MASK ){/* Byte alignment required. */xWantedSize += ( portBYTE_ALIGNMENT - ( xWantedSize & portBYTE_ALIGNMENT_MASK ) );}}#endifvTaskSuspendAll();{if( pucAlignedHeap == NULL ){/* Ensure the heap starts on a correctly aligned boundary. */pucAlignedHeap = ( uint8_t * ) ( ( ( portPOINTER_SIZE_TYPE ) &ucHeap[ portBYTE_ALIGNMENT ] ) & ( ~( ( portPOINTER_SIZE_TYPE ) portBYTE_ALIGNMENT_MASK ) ) );}/* Check there is enough room left for the allocation. */if( ( ( xNextFreeByte + xWantedSize ) < configADJUSTED_HEAP_SIZE ) &&( ( xNextFreeByte + xWantedSize ) > xNextFreeByte )	)/* Check for overflow. */{/* Return the next free byte then increment the index past thisblock. */pvReturn = pucAlignedHeap + xNextFreeByte;xNextFreeByte += xWantedSize;}traceMALLOC( pvReturn, xWantedSize );}( void ) xTaskResumeAll();#if( configUSE_MALLOC_FAILED_HOOK == 1 ){if( pvReturn == NULL ){extern void vApplicationMallocFailedHook( void );vApplicationMallocFailedHook();}}#endifreturn pvReturn;
}

如果是第一次执行这个函数,需要将变量pucAlignedHeap指向内存堆区域第一个地址对齐处。我们上面说内存堆其实是一个大数组,编译器为这个数组分配的起始地址是随机的,可能不符合我们的对齐需要,这时候要进行调整。比如数组地址是0x20000001,这时候一些芯片是对齐才能工作,所以代码一开始设置的内存初始状态如下图所示:

在申请内存过程中同样会对要申请的内存大小做一个字节对齐的操作(向上对齐) ,比如对于8字节对齐的系统来说, 你申请11个字节大小的内存,这时候向上对齐就是16字节,接下来就是挂起调度器,防止多个任务同时申请内存,因为内存是不可重入的(使用了静态变量)。

分配内存的过程如下图:

 下面是一个更加清晰的整体的一个分配内存的图:

 内存分配完成后,不管有没有分配成功都恢复之前挂起的调度器。
如果内存分配不成功,这里最可能是内存堆空间不够用了,会调用一个钩子函数vApplicationMallocFailedHook()。这个钩子函数由应用程序提供,通常我们可以打印内存分配设备信息或者点亮也故障指示灯。

heap_2.c内存管理算法

Heap_2之所以还保留,只是为了兼容以前的代码。新设计中不再推荐使用Heap_2。建议使用Heap_4来替代Heap_2,更加高效。

Heap_2也是在数组上分配内存,跟Heap_1不一样的地方在于:

  • Heap_2使用最佳匹配算法(best fit)来分配内存
  • 它支持vPortFree

最佳匹配算法:

  • 假设heap有3块空闲内存:5字节、25字节、100字节
  • pvPortMalloc想申请20字节
  • 找出最小的、能满足pvPortMalloc的内存:25字节
  • 把它划分为20字节、5字节
    • 返回这20字节的地址
    • 剩下的5字节仍然是空闲状态,留给后续的pvPortMalloc使用

与Heap_4相比,Heap_2不会合并相邻的空闲内存,所以Heap_2会导致严重的"碎片化"问题。

但是,如果申请、分配内存时大小总是相同的,这类场景下Heap_2没有碎片化的问题。所以它适合这种场景:频繁地创建、删除任务,但是任务的栈大小都是相同的(创建任务时,需要分配TCB和栈,TCB总是一样的)。

虽然不再推荐使用heap_2,但是它的效率还是远高于malloc、free。

Tips: 其实heap_2.c算法就是在heap_1.c的基础上进行的改进,其中可以思考为什么heap_1.c没办法实现free函数吗,因为free函数释放内存只传入你要释放内存的地址,对于要释放多少呢,heap_1.c算法是没有记录的,我记得我在学习C语言的时候也想过这个问题,free函数只传入要释放内存的地址,它怎么知道要释放多大呢,在操作系统的学习中了解过这部分知识,其实申请的时候,会有一个头部在申请内存的前面,这个添加头部的方法其实很常见。

局部静态变量pucAlignedHeap指向对齐后的内存堆起始位置。地址对齐的原因在第一种内存管理策略中已经说明。假如内存堆数组ucHeap从RAM地址0x10002003处开始,系统按照8字节对齐,则对齐后的内存堆与第一个内存管理策略一样,如图所示:

 与第一种内存管理策略不同,第二种内存管理策略使用一个链表结构来跟踪记录空闲内存块,将空闲块组成一个链表。头部结构体定义为:

typedef struct A_BLOCK_LINK
{struct A_BLOCK_LINK *pxNextFreeBlock;	/*<< The next free block in the list. */size_t xBlockSize;						/*<< The size of the free block. */
} BlockLink_t;static BlockLink_t xStart, xEnd;

两个BlockLink_t类型的全局静态变量xStart和xEnd用来标识空闲内存块的起始和结束。刚开始时,整个内存堆有效空间就是一个空闲块:

 当申请N字节内存时,实际上不仅需要分配N字节内存,还要分配一个BlockLink_t类型结构体空间,用于描述这个内存块,结构体空间位于空闲内存块的最开始处。当然,和第一种内存管理策略一样,申请的内存大小加上BlockLink_t类型结构体大小之后也要向上扩大到对齐字节数的整数倍 。

我们看一下内存申请过程:首先计算实际要分配的内存大小,判断申请的内存是否合法。如果合法则从链表头xStart开始查找,如果某个空闲块的xBlockSize字段大小能容得下要申请的内存,则从这块内存取出合适的部分返回给申请者,剩下的内存块组成一个新的空闲块,按照空闲块的大小顺序插入到空闲块链表中,小块在前大块在后。

/*-----------------------------------------------------------*/void *pvPortMalloc( size_t xWantedSize )
{
BlockLink_t *pxBlock, *pxPreviousBlock, *pxNewBlockLink;
static BaseType_t xHeapHasBeenInitialised = pdFALSE;
void *pvReturn = NULL;vTaskSuspendAll();{/* If this is the first call to malloc then the heap will requireinitialisation to setup the list of free blocks. */if( xHeapHasBeenInitialised == pdFALSE ){prvHeapInit();xHeapHasBeenInitialised = pdTRUE;}/* The wanted size is increased so it can contain a BlockLink_tstructure in addition to the requested amount of bytes. */if( xWantedSize > 0 ){xWantedSize += heapSTRUCT_SIZE;/* Ensure that blocks are always aligned to the required number of bytes. */if( ( xWantedSize & portBYTE_ALIGNMENT_MASK ) != 0 ){/* Byte alignment required. */xWantedSize += ( portBYTE_ALIGNMENT - ( xWantedSize & portBYTE_ALIGNMENT_MASK ) );}}if( ( xWantedSize > 0 ) && ( xWantedSize < configADJUSTED_HEAP_SIZE ) ){/* Blocks are stored in byte order - traverse the list from the start(smallest) block until one of adequate size is found. */pxPreviousBlock = &xStart;pxBlock = xStart.pxNextFreeBlock;while( ( pxBlock->xBlockSize < xWantedSize ) && ( pxBlock->pxNextFreeBlock != NULL ) ){pxPreviousBlock = pxBlock;pxBlock = pxBlock->pxNextFreeBlock;}/* If we found the end marker then a block of adequate size was not found. */if( pxBlock != &xEnd ){/* Return the memory space - jumping over the BlockLink_t structureat its start. */pvReturn = ( void * ) ( ( ( uint8_t * ) pxPreviousBlock->pxNextFreeBlock ) + heapSTRUCT_SIZE );/* This block is being returned for use so must be taken out of thelist of free blocks. */pxPreviousBlock->pxNextFreeBlock = pxBlock->pxNextFreeBlock;/* If the block is larger than required it can be split into two. */if( ( pxBlock->xBlockSize - xWantedSize ) > heapMINIMUM_BLOCK_SIZE ){/* This block is to be split into two.  Create a new blockfollowing the number of bytes requested. The void cast isused to prevent byte alignment warnings from the compiler. */pxNewBlockLink = ( void * ) ( ( ( uint8_t * ) pxBlock ) + xWantedSize );/* Calculate the sizes of two blocks split from the singleblock. */pxNewBlockLink->xBlockSize = pxBlock->xBlockSize - xWantedSize;pxBlock->xBlockSize = xWantedSize;/* Insert the new block into the list of free blocks. */prvInsertBlockIntoFreeList( ( pxNewBlockLink ) );}xFreeBytesRemaining -= pxBlock->xBlockSize;}}traceMALLOC( pvReturn, xWantedSize );}( void ) xTaskResumeAll();#if( configUSE_MALLOC_FAILED_HOOK == 1 ){if( pvReturn == NULL ){extern void vApplicationMallocFailedHook( void );vApplicationMallocFailedHook();}}#endifreturn pvReturn;
}

阅读源码的个人理解:

第一次调用malloc申请内存时需要进行heap的初始化操作,而它的初始化操作就是将heap加上一个头部,以及初始化xStart和xEnd,heap_2.c的内存管理是通过块Block进行管理的。

  • 在申请内存块的时候,使用的是最佳匹配算法,在空闲块的链表中找到一个刚好合适又最小的块,如果申请之后还剩下比较大的内存则会进行拆分,把剩余的内存块添加回空闲内存块管理的链表中,按照内存大小进行插入。
  • 正是因为上面空闲内存的插入是按照大小进行插入这一个特点,注定了空闲内存块之间是不能进行合并的,所以会出现内存碎片的情况.

所以我们可以知道heap_2.c适合对于那些频繁申请释放固定大小内存的场合,这样不会造成内存碎片。 

/*-----------------------------------------------------------*/void vPortFree( void *pv )
{
uint8_t *puc = ( uint8_t * ) pv;
BlockLink_t *pxLink;if( pv != NULL ){/* The memory being freed will have an BlockLink_t structure immediatelybefore it. */puc -= heapSTRUCT_SIZE;/* This unexpected casting is to keep some compilers from issuingbyte alignment warnings. */pxLink = ( void * ) puc;vTaskSuspendAll();{/* Add this block to the list of free blocks. */prvInsertBlockIntoFreeList( ( ( BlockLink_t * ) pxLink ) );xFreeBytesRemaining += pxLink->xBlockSize;traceFREE( pv, pxLink->xBlockSize );}( void ) xTaskResumeAll();}
}

 阅读代码释放内存的原理也很简单,无非就是通过要释放内存的地址往前推得到内存块结构体,就可以知道要释放的内存大小了。

heap_3.c内存管理算法

第三种内存管理策略简单的封装了标准库中的malloc()和free()函数,采用的封装方式是操作内存前挂起调度器、完成后再恢复调度器。封装后的malloc()和free()函数具备线程保护。
第一种和第二种内存管理策略都是通过定义一个大数组作为内存堆,数组的大小由宏configTOTAL_HEAP_SIZE指定。第三种内存管理策略与前两种不同,它不再需要通过数组定义内存堆,而是需要使用编译器设置内存堆空间,一般在启动代码中设置。因此宏configTOTAL_HEAP_SIZE对这种内存管理策略是无效的。

 heap_4.c内存管理算法

跟Heap_1、Heap_2一样,Heap_4也是使用大数组来分配内存。

Heap_4使用首次适应算法(first fit)来分配内存。它还会把相邻的空闲内存合并为一个更大的空闲内存,这有助于较少内存的碎片问题。

首次适应算法:

  • 假设堆中有3块空闲内存:5字节、200字节、100字节
  • pvPortMalloc想申请20字节
  • 找出第1个能满足pvPortMalloc的内存:200字节
  • 把它划分为20字节、180字节
    • 返回这20字节的地址
    • 剩下的180字节仍然是空闲状态,留给后续的pvPortMalloc使用

Heap_4会把相邻空闲内存合并为一个大的空闲内存,可以较少内存的碎片化问题。适用于这种场景:频繁地分配、释放不同大小的内存。

Tips:其实个人感觉第四种内存管理算法就是在第二种内存管理算法heap_2.c的基础上再次进行改进而已, 由于heap_2.c算法存在内存碎片的问题,所以heap_4.c可以将相邻的内存块进行合并,这就意味着空闲内存块之间是按照地址进行存放的,不是像heap_2.c一样按照大小进行排序管理的。

和第二种内存管理策略一样,它也使用一个链表结构来跟踪记录空闲内存块。头部结构体定义是一样的。

与第二种内存管理策略一样,空闲内存块也是以单链表的形式组织起来的,BlockLink_t类型的全局静态变量xStart表示链表头,但第四种内存管理策略的链表尾保存在内存堆空间最后位置,并使用BlockLink_t指针类型局部静态变量pxEnd指向这个区域(第二种内存管理策略使用静态变量xEnd表示链表尾)(这里我想吐槽一下,感觉完全没必要啊,完全可以使用和heap_2.c一样的管理,可能这里是为了节省内存,能省就省就和迷你列表项一样🤣)

 重点:第四种内存管理算法和第二种内存管理算法之间最大的不同就是,第四种内存管理算法的空闲内存块链表不是以内存块大小为存储顺序,而是以内存块起始地址大小为存储顺序的,地址小的在前,地址大的在后,其实这好像是必须的,因为你要合并相邻的内存块肯定地址要按顺序来才能合并嘛🤣,这是为了适应合并算法而作的改变。


/*-----------------------------------------------------------*/void *pvPortMalloc( size_t xWantedSize )
{
BlockLink_t *pxBlock, *pxPreviousBlock, *pxNewBlockLink;
void *pvReturn = NULL;vTaskSuspendAll();{/* If this is the first call to malloc then the heap will requireinitialisation to setup the list of free blocks. */if( pxEnd == NULL ){prvHeapInit();}else{mtCOVERAGE_TEST_MARKER();}/* Check the requested block size is not so large that the top bit isset.  The top bit of the block size member of the BlockLink_t structureis used to determine who owns the block - the application or thekernel, so it must be free. */if( ( xWantedSize & xBlockAllocatedBit ) == 0 ){/* The wanted size is increased so it can contain a BlockLink_tstructure in addition to the requested amount of bytes. */if( xWantedSize > 0 ){xWantedSize += xHeapStructSize;/* Ensure that blocks are always aligned to the required numberof bytes. */if( ( xWantedSize & portBYTE_ALIGNMENT_MASK ) != 0x00 ){/* Byte alignment required. */xWantedSize += ( portBYTE_ALIGNMENT - ( xWantedSize & portBYTE_ALIGNMENT_MASK ) );configASSERT( ( xWantedSize & portBYTE_ALIGNMENT_MASK ) == 0 );}else{mtCOVERAGE_TEST_MARKER();}}else{mtCOVERAGE_TEST_MARKER();}if( ( xWantedSize > 0 ) && ( xWantedSize <= xFreeBytesRemaining ) ){/* Traverse the list from the start	(lowest address) block untilone	of adequate size is found. */pxPreviousBlock = &xStart;pxBlock = xStart.pxNextFreeBlock;while( ( pxBlock->xBlockSize < xWantedSize ) && ( pxBlock->pxNextFreeBlock != NULL ) ){pxPreviousBlock = pxBlock;pxBlock = pxBlock->pxNextFreeBlock;}/* If the end marker was reached then a block of adequate sizewas	not found. */if( pxBlock != pxEnd ){/* Return the memory space pointed to - jumping over theBlockLink_t structure at its start. */pvReturn = ( void * ) ( ( ( uint8_t * ) pxPreviousBlock->pxNextFreeBlock ) + xHeapStructSize );/* This block is being returned for use so must be taken outof the list of free blocks. */pxPreviousBlock->pxNextFreeBlock = pxBlock->pxNextFreeBlock;/* If the block is larger than required it can be split intotwo. */if( ( pxBlock->xBlockSize - xWantedSize ) > heapMINIMUM_BLOCK_SIZE ){/* This block is to be split into two.  Create a newblock following the number of bytes requested. The voidcast is used to prevent byte alignment warnings from thecompiler. */pxNewBlockLink = ( void * ) ( ( ( uint8_t * ) pxBlock ) + xWantedSize );configASSERT( ( ( ( size_t ) pxNewBlockLink ) & portBYTE_ALIGNMENT_MASK ) == 0 );/* Calculate the sizes of two blocks split from thesingle block. */pxNewBlockLink->xBlockSize = pxBlock->xBlockSize - xWantedSize;pxBlock->xBlockSize = xWantedSize;/* Insert the new block into the list of free blocks. */prvInsertBlockIntoFreeList( pxNewBlockLink );}else{mtCOVERAGE_TEST_MARKER();}xFreeBytesRemaining -= pxBlock->xBlockSize;if( xFreeBytesRemaining < xMinimumEverFreeBytesRemaining ){xMinimumEverFreeBytesRemaining = xFreeBytesRemaining;}else{mtCOVERAGE_TEST_MARKER();}/* The block is being returned - it is allocated and ownedby the application and has no "next" block. */pxBlock->xBlockSize |= xBlockAllocatedBit;pxBlock->pxNextFreeBlock = NULL;xNumberOfSuccessfulAllocations++;}else{mtCOVERAGE_TEST_MARKER();}}else{mtCOVERAGE_TEST_MARKER();}}else{mtCOVERAGE_TEST_MARKER();}traceMALLOC( pvReturn, xWantedSize );}( void ) xTaskResumeAll();#if( configUSE_MALLOC_FAILED_HOOK == 1 ){if( pvReturn == NULL ){extern void vApplicationMallocFailedHook( void );vApplicationMallocFailedHook();}else{mtCOVERAGE_TEST_MARKER();}}#endifconfigASSERT( ( ( ( size_t ) pvReturn ) & ( size_t ) portBYTE_ALIGNMENT_MASK ) == 0 );return pvReturn;
}

申请内存的代码和heap_2.c相比无非就是heap的初始化对于xEnd有点不一样,一个是指针一个是变量,然后就是空闲内存块的插入算法是不一样,一个按照地址来一个按照大小插入。

/*-----------------------------------------------------------*/static void prvInsertBlockIntoFreeList( BlockLink_t *pxBlockToInsert )
{
BlockLink_t *pxIterator;
uint8_t *puc;/* Iterate through the list until a block is found that has a higher addressthan the block being inserted. */for( pxIterator = &xStart; pxIterator->pxNextFreeBlock < pxBlockToInsert; pxIterator = pxIterator->pxNextFreeBlock ){/* Nothing to do here, just iterate to the right position. */}/* Do the block being inserted, and the block it is being inserted aftermake a contiguous block of memory? */puc = ( uint8_t * ) pxIterator;if( ( puc + pxIterator->xBlockSize ) == ( uint8_t * ) pxBlockToInsert ){pxIterator->xBlockSize += pxBlockToInsert->xBlockSize;pxBlockToInsert = pxIterator;}else{mtCOVERAGE_TEST_MARKER();}/* Do the block being inserted, and the block it is being inserted beforemake a contiguous block of memory? */puc = ( uint8_t * ) pxBlockToInsert;if( ( puc + pxBlockToInsert->xBlockSize ) == ( uint8_t * ) pxIterator->pxNextFreeBlock ){if( pxIterator->pxNextFreeBlock != pxEnd ){/* Form one big block from the two blocks. */pxBlockToInsert->xBlockSize += pxIterator->pxNextFreeBlock->xBlockSize;pxBlockToInsert->pxNextFreeBlock = pxIterator->pxNextFreeBlock->pxNextFreeBlock;}else{pxBlockToInsert->pxNextFreeBlock = pxEnd;}}else{pxBlockToInsert->pxNextFreeBlock = pxIterator->pxNextFreeBlock;}/* If the block being inserted plugged a gab, so was merged with the blockbefore and the block after, then it's pxNextFreeBlock pointer will havealready been set, and should not be set here as that would make it pointto itself. */if( pxIterator != pxBlockToInsert ){pxIterator->pxNextFreeBlock = pxBlockToInsert;}else{mtCOVERAGE_TEST_MARKER();}
}

这个链表插入函数,在内存释放函数中也会进行调用,它也是内存释放的关键代码, 它里面最关键的就是要进行前后相邻内存块是否能进行合并的判断和合并操作,先和前面的内存块判断是否进行合并,然后再和后面的内存块进行同样的操作。总的来说其实不是特别难,而且heap_4.c是一种比较均衡的内存管理算法,克服了前面heap_1.c和heap_2.c的问题,所以大多数还是采用这个内存管理算法的(以前不懂得时候,就感觉它是万能的,选这个准没错😂)。

heap_5.c内存管理算法

Heap_5分配内存、释放内存的算法跟Heap_4是一样的。

相比于Heap_4,Heap_5并不局限于管理一个大数组:它可以管理多块、分隔开的内存。

在嵌入式系统中,内存的地址可能并不连续,我们heap_4.c是一块连续的内存(声明的一个大数组嘛),而有些时候有的系统中内存地址可能不连续,这种场景下可以使用Heap_5。

既然内存是分隔开的,那么就需要进行初始化:确定这些内存块在哪、多大:

在使用pvPortMalloc之前,必须先指定内存块的信息(需要用户自己指定)
使用vPortDefineHeapRegions来指定这些信息

怎么指定一块内存?使用如下结构体:

typedef struct HeapRegion
{uint8_t * pucStartAddress; // 起始地址size_t xSizeInBytes;       // 大小
} HeapRegion_t;

怎么指定多块内存?使用一个HeapRegion_t数组,在这个数组中,低地址在前、高地址在后(必须这样做)。

比如:

HeapRegion_t xHeapRegions[] =
{{ ( uint8_t * ) 0x80000000UL, 0x10000 }, // 起始地址0x80000000,大小0x10000{ ( uint8_t * ) 0x90000000UL, 0xa0000 }, // 起始地址0x90000000,大小0xa0000{ NULL, 0 } // 表示数组结束};

因为heap_5.c的malloc函数没有像前面几种内存管理算法一样,第一次调用的时候进行了内存heap的初始化,所以在使用任何内存分配和释放操作前调用vPortDefineHeapRegions()函数初始化这些内存堆(用户自己手动调用)。


第一、第二和第四种内存管理策略都是利用一个大数组作为内存堆使用,并且只需要应用程序指定数组的大小(通过宏configTOTAL_HEAP_SIZE定义),数组定义由内存管理策略实现。第五种内存管理策略有些不同,首先它允许跨内存区定义多个内存堆,比如在片内RAM中定义一个内存堆,还可以在片外RAM再定义内存堆;其次,用户需要指定每个内存堆区域的起始地址和内存堆大小、将它们放在一个HeapRegion_t结构体类型数组中,并需要在使用任何内存分配和释放操作前调用vPortDefineHeapRegions()函数初始化这些内存堆。

/*-----------------------------------------------------------*/void vPortDefineHeapRegions( const HeapRegion_t * const pxHeapRegions )
{
BlockLink_t *pxFirstFreeBlockInRegion = NULL, *pxPreviousFreeBlock;
size_t xAlignedHeap;
size_t xTotalRegionSize, xTotalHeapSize = 0;
BaseType_t xDefinedRegions = 0;
size_t xAddress;
const HeapRegion_t *pxHeapRegion;/* Can only call once! */configASSERT( pxEnd == NULL );pxHeapRegion = &( pxHeapRegions[ xDefinedRegions ] );while( pxHeapRegion->xSizeInBytes > 0 ){xTotalRegionSize = pxHeapRegion->xSizeInBytes;/* Ensure the heap region starts on a correctly aligned boundary. */xAddress = ( size_t ) pxHeapRegion->pucStartAddress;if( ( xAddress & portBYTE_ALIGNMENT_MASK ) != 0 ){xAddress += ( portBYTE_ALIGNMENT - 1 );xAddress &= ~portBYTE_ALIGNMENT_MASK;/* Adjust the size for the bytes lost to alignment. */xTotalRegionSize -= xAddress - ( size_t ) pxHeapRegion->pucStartAddress;}xAlignedHeap = xAddress;/* Set xStart if it has not already been set. */if( xDefinedRegions == 0 ){/* xStart is used to hold a pointer to the first item in the list offree blocks.  The void cast is used to prevent compiler warnings. */xStart.pxNextFreeBlock = ( BlockLink_t * ) xAlignedHeap;xStart.xBlockSize = ( size_t ) 0;}else{/* Should only get here if one region has already been added to theheap. */configASSERT( pxEnd != NULL );/* Check blocks are passed in with increasing start addresses. */configASSERT( xAddress > ( size_t ) pxEnd );}/* Remember the location of the end marker in the previous region, ifany. */pxPreviousFreeBlock = pxEnd;/* pxEnd is used to mark the end of the list of free blocks and isinserted at the end of the region space. */xAddress = xAlignedHeap + xTotalRegionSize;xAddress -= xHeapStructSize;xAddress &= ~portBYTE_ALIGNMENT_MASK;pxEnd = ( BlockLink_t * ) xAddress;pxEnd->xBlockSize = 0;pxEnd->pxNextFreeBlock = NULL;/* To start with there is a single free block in this region that issized to take up the entire heap region minus the space taken by thefree block structure. */pxFirstFreeBlockInRegion = ( BlockLink_t * ) xAlignedHeap;pxFirstFreeBlockInRegion->xBlockSize = xAddress - ( size_t ) pxFirstFreeBlockInRegion;pxFirstFreeBlockInRegion->pxNextFreeBlock = pxEnd;/* If this is not the first region that makes up the entire heap spacethen link the previous region to this region. */if( pxPreviousFreeBlock != NULL ){pxPreviousFreeBlock->pxNextFreeBlock = pxFirstFreeBlockInRegion;}xTotalHeapSize += pxFirstFreeBlockInRegion->xBlockSize;/* Move onto the next HeapRegion_t structure. */xDefinedRegions++;pxHeapRegion = &( pxHeapRegions[ xDefinedRegions ] );}xMinimumEverFreeBytesRemaining = xTotalHeapSize;xFreeBytesRemaining = xTotalHeapSize;/* Check something was actually defined before it is accessed. */configASSERT( xTotalHeapSize );/* Work out the position of the top bit in a size_t variable. */xBlockAllocatedBit = ( ( size_t ) 1 ) << ( ( sizeof( size_t ) * heapBITS_PER_BYTE ) - 1 );
}

这个函数可以看到,代码量还是很大的🤣,但其实原理就是以链表的形式管理这些分隔的内存块。

一旦内存堆初始化之后,内存申请和释放都和第四种内存管理策略相同,不再单独分析。 

 第五种内存管理策略允许内存堆跨越多个非连续的内存区,并且需要显示的初始化内存堆,除此之外其它操作都和第四种内存管理策略十分相似。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/156189.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Redis:新的3种数据类型Bitmaps、HyperLoglog、Geographic

目录 Bitmaps简介常用命令bitmaps与set比较 HyperLoglog简介命令 Geographic简介命令 Bitmaps 简介 位操作字符串。 现代计算机使用二进制&#xff08;位&#xff09;作为信息的基本单位&#xff0c;1个字节等于8位&#xff0c;例如“abc”字符串是有3个字节组成&#xff0c…

Parallel Diffusion Models of Operator and Image for Blind Inverse Problems

盲逆问题算子和图像的并行扩散模型 论文链接&#xff1a;https://arxiv.org/abs/2211.10656 项目链接&#xff1a;https://github.com/BlindDPS/blind-dps Abstract 在正向算子已知的情况下(即非盲)&#xff0c;基于扩散模型的逆问题求解器已经展示了最先进的性能。然而&…

css鼠标横向滚动并且不展示滚动条几种方法

需求&#xff1a;实现内容超出之后使用属性滚轮进行左右查看超出内容&#xff0c;并且隐藏滚动条 1.不使用框架实现 每次滚动就滚动40px的距离 <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8" /><meta name&quo…

STM32——STM32Cubemx的学习使用总结

文章目录 一、简介二、STM32Cube IDE与MX区别&#xff1f;三、界面介绍和使用四、使用整体框架 一、简介 STM32CubeMX是一个图形化工具&#xff0c;可以非常容易地配置STM32微控制器和微处理器&#xff0c;以及为ArmCortex-M 内核或部分 Linux 设备树生成相应的初始化C代码&…

编译器优化代码研究

《Effective C》条款21&#xff1a; /** * 结论&#xff1a;对自定义类型对象表达式objA*objB objC; * 定义friend MyInt operator*(const MyInt& lhs,const MyInt& rhs) * 编译器优化后&#xff1a;operator*()函数内直接在调用接收处构造(此处的匿名临时对象)&am…

CANopen权威指南【CAN总线协议】

1这个总线定义是老外发明的。 想要使用&#xff0c;就必须按照协议去配置数据帧。 CIA301和cia402协议&#xff0c;实际就是寄存器地址上某一段的定义。 下载地址&#xff1a; CAN in Automation (CiA): Technical documents 注册下载也是非常快的。【没什么难度】 就是资…

HTTP响应详解

HTTP响应格式 HTTP响应报文通常由四个部分组成: 响应行(Response Line):包含协议版本、状态码和状态消息,例如:HTTP/1.1 200 OK。 响应头(Response Headers):包含了一系列的键值对,用来描述关于响应的信息,比如内容类型、日期、服务器信息等等。 空行:即CRLF(回车…

关于使用Java-JWT的笔记

Token的组成规则 一个token分三部分&#xff0c;按顺序为&#xff1a;头部&#xff08;header)&#xff0c;载荷&#xff08;payload)&#xff0c;签证&#xff08;signature) 由三部分生成token &#xff0c;三部分之间用“.”号做分隔。 例如&#xff1a;“eyJhbGciOiJIUzI1…

完全二叉树你需要了解一下

完全二叉树介绍完全二叉树应用场景完全二叉树和满二叉树的区别完全二叉树代码示例拓展 完全二叉树介绍 完全二叉树&#xff08;Complete Binary Tree&#xff09;是一种特殊的二叉树&#xff0c;它的定义是&#xff1a;如果设二叉树的深度为h&#xff0c;除第h层外&#xff0c…

博主都在用的网站,一键制作电子杂志

​随着互联网的发展&#xff0c;越来越多的人开始使用电子杂志来展示自己的作品或宣传自己的品牌。而制作电子杂志的工具也越来越多&#xff0c;其中一些工具非常受欢迎&#xff0c;被许多博主使用。今天&#xff0c;我们就来介绍一款博主都在用的网站&#xff0c;它可以帮助你…

MySQL InnoDB 引擎底层解析(三)

6.3.3. InnoDB 的内存结构总结 InnoDB 的内存结构和磁盘存储结构图总结如下&#xff1a; 其中的 Insert/Change Buffer 主要是用于对二级索引的写入优化&#xff0c;Undo 空间则是 undo 日志一般放在系统表空间&#xff0c;但是通过参数配置后&#xff0c;也可以用独立表空 间…

c语言上机作业:迭代法求平方根

1.题目 设计一个函数func用迭代法编程求一个数的平方根。平方根的迭代公式为&#xff1a;牛顿迭代法&#xff0c;(要求前后两次迭代值求差的绝对值小于10的-9次方) 2.思路 a.这里要求我们设置一个函数能够完成牛顿迭代法&#xff0c;这里需要使用到函数迭代的知识&#xff0…

泉盛UV-K5/K6全功能中文固件

https://github.com/wu58430/uv-k5-firmware-chinese/releases 主要功能&#xff1a; 中文菜单 许多来自 OneOfEleven 的模块&#xff1a; AM 修复&#xff0c;显著提高接收质量长按按钮执行 F 操作的功能复制快速扫描菜单中的频道名称编辑频道名称 频率显示选项扫描列表分配…

Window下如何对Redis进行开启与关闭

目录 前言1. 图文界面2. 命令行 前言 由于长期使用Linux界面&#xff0c;对于Window下的Redis&#xff0c;不知如何下手。特此记录该博文 特别注意&#xff0c;刚下载好的Redis&#xff0c;如果需要配置密码&#xff0c;可以再该文件进行配置&#xff1a;redis.windows-servi…

【Python】给出n个数,找出这n个数的最大值,最小值,和。

问题描述 给出n个数&#xff0c;找出这n个数的最大值&#xff0c;最小值&#xff0c;和。 样例输入 5 1 3 -2 4 5 Data 样例输出 5 -2 11 n int(input()) # 从用户输入中读取一个整数&#xff0c;将其赋给变量n# 从用户输入中读取一行字符串&#xff0c;使用空格分割字符串&a…

火狐挂代理访问问题Software is preventing Firefox from safely connecting to this site

1、报错 Software is preventing Firefox from safely connecting to this site2、解决步骤 火狐浏览器访问http://burp&#xff0c;右上角有下载按钮下载下来证书文件 在 Firefox 中设置证书颁发机构 (CA) 验证

共享内存的创建和映射过程

消息队列、共享内存、信号量的机制&#xff1a;它们在使用之前都要生成 key&#xff0c;然后通过 key 得到唯一的 id&#xff0c;并且都是通过 xxxget 函数。在内核里面&#xff0c;这三种进程间通信机制是使用统一的机制管理起来的&#xff0c;都叫 ipcxxx。为了维护这三种进程…

代码随想录Day51 完结篇 LeetCode T84 柱状图的最大矩形

前言 今天代码随想录一刷也告一段落了,没想到我居然坚持下来了,一节都没有落下,学习到了很多种不同的解题思路,也和大家一块交流了很多,哈哈也许不久以后我还得再次二刷代码随想录,希望这一系列的题解能给大家带来帮助,如想要系统学习,请参照代码随想录网站的题解以及b站的配套…

【Java 进阶篇】揭秘 Jackson:Java 对象转 JSON 注解的魔法

嗨&#xff0c;亲爱的同学们&#xff01;欢迎来到这篇关于 Jackson JSON 解析器中 Java 对象转 JSON 注解的详细解析指南。JSON&#xff08;JavaScript Object Notation&#xff09;是一种常用于数据交换的轻量级数据格式&#xff0c;而 Jackson 作为一款优秀的 JSON 解析库&am…

【python】--python基础学习

目录 一、基础语法二、基础数据类型1、变量赋值2、数值型3、字符串型4、列表List5、元组Tuple6、字典dictionary7、数据类型转换 三、python运算符四、条件控制与循环五、常用函数1、字符串函数2、format函数 一、基础语法 标识符是允许作为变量&#xff08;函数、类等&#x…