【freertos-kernel】MemMang
文章目录
- heap1.c
- void * pvPortMalloc( size_t xWantedSize )
- void vPortFree( void * pv )
- heap2.c
- static void prvHeapInit( void ) PRIVILEGED_FUNCTION
- 一些宏
- prvInsertBlockIntoFreeList
- void * pvPortMalloc( size_t xWantedSize )
- void vPortFree( void * pv )
- heap3.c
- heap4.c
- HEAP_PROTECTOR
- prvInsertBlockIntoFreeList
- heap5.c
- void vPortDefineHeapRegions( const HeapRegion_t * const pxHeapRegions ) PRIVILEGED_FUNCTION
heap1.c
只能分配内存,但不能释放内存 ,也就是不能重复使用或回收已经分配过的内存。
优点 :
简单可靠。没有内存碎片问题。
缺点 :
内存无法重复使用。不适合需要频繁分配/释放内存的应用。
/* 因为堆起始地址可能要对齐,所以实际可用空间会略小于总大小。 */
#define configADJUSTED_HEAP_SIZE ( configTOTAL_HEAP_SIZE - portBYTE_ALIGNMENT )/* 如果用户自己定义了堆内存,
则使用外部定义的 ucHeap[]。
否则由内核自动创建一个静态数组作为堆内存。*/
#if ( configAPPLICATION_ALLOCATED_HEAP == 1 )extern uint8_t ucHeap[ configTOTAL_HEAP_SIZE ];
#elsestatic uint8_t ucHeap[ configTOTAL_HEAP_SIZE ];
#endif /* configAPPLICATION_ALLOCATED_HEAP *//* Index into the ucHeap array.
初始化为 0,表示从堆的开始位置分配内存。*/
static size_t xNextFreeByte = ( size_t ) 0U;
void * pvPortMalloc( size_t xWantedSize )
void * pvReturn = NULL;
static uint8_t * pucAlignedHeap = NULL;
对齐处理
确保分配的内存块是对齐的(通常是 4 或 8 字节对齐)。如果请求大小不是对齐单位的整数倍,则向上取整。
#if ( portBYTE_ALIGNMENT != 1 )
{if( xWantedSize & portBYTE_ALIGNMENT_MASK ){if( ( xWantedSize + ( portBYTE_ALIGNMENT - ( xWantedSize & portBYTE_ALIGNMENT_MASK ) ) ) > xWantedSize ){xWantedSize += ( portBYTE_ALIGNMENT - ( xWantedSize & portBYTE_ALIGNMENT_MASK ) );}else{xWantedSize = 0;}}
}
#endif /* if ( portBYTE_ALIGNMENT != 1 ) */
portBYTE_ALIGNMENT 对齐字节数。portBYTE_ALIGNMENT_MASK 对齐字节数的二进制掩码。
众所周知x % (2^n) = x & (2^n - 1)
, 因为portBYTE_ALIGNMENT 是 2 的幂次(如 2, 4, 8, 16, 32…),xWantedSize&portBYTE_ALIGNMENT_MASK
相当于取余xWantedSize%portBYTE_ALIGNMENT
。
可以对照下图瞅一瞅
挂起任务调度器以确保线程安全
在分配内存期间暂停任务调度,防止多任务并发访问导致的数据竞争。
vTaskSuspendAll();
{...
}
( void ) xTaskResumeAll();
设置堆起始地址并对齐
将堆指针调整到合适的对齐边界上。例如:如果 portBYTE_ALIGNMENT == 8,就跳过前几个字节,使起始地址是 8 的倍数。
if( pucAlignedHeap == NULL )
{pucAlignedHeap = ( uint8_t * ) ( ( ( portPOINTER_SIZE_TYPE ) & ucHeap[ portBYTE_ALIGNMENT - 1 ] ) & ( ~( ( portPOINTER_SIZE_TYPE ) portBYTE_ALIGNMENT_MASK ) ) );
}
实际分配逻辑
如果剩余空间足够,就分配一块内存。
返回当前空闲位置的指针,并更新下一次分配的偏移量。
if( ( xWantedSize > 0 ) && /* valid size */( ( xNextFreeByte + xWantedSize ) < configADJUSTED_HEAP_SIZE ) &&( ( xNextFreeByte + xWantedSize ) > xNextFreeByte ) ) /* Check for overflow. */
{pvReturn = pucAlignedHeap + xNextFreeByte;xNextFreeByte += xWantedSize;
}
分配失败钩子函数
#if ( configUSE_MALLOC_FAILED_HOOK == 1 )
{if( pvReturn == NULL ){vApplicationMallocFailedHook();}
}
#endifreturn pvReturn;
void vPortFree( void * pv )
( void ) pv;
configASSERT( pv == NULL );
heap2.c
支持动态内存分配和释放。
所有可用内存通过一个链表进行管理。
每次调用pvPortMalloc()
时,从链表中查找一个足够大的空闲块,使用 最佳适配 算法。
调用vPortFree()
时,将内存块标记为空闲,但 不会合并相邻空闲块 。
因此,频繁分配和释放小内存块可能会导致内存碎片。
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;
//将 sizeof(BlockLink_t) 向上对齐到最近的 portBYTE_ALIGNMENT 字节边界。
static const size_t xHeapStructSize = ( ( sizeof( BlockLink_t ) + ( size_t ) ( portBYTE_ALIGNMENT - 1 ) ) & ~( ( size_t ) portBYTE_ALIGNMENT_MASK ) );
//定义一个最小的内存块大小。
#define heapMINIMUM_BLOCK_SIZE ( ( size_t ) ( xHeapStructSize * 2 ) )
每个空闲内存块必须至少包含两个 BlockLink_t
大小的空间。因为当一个内存块被分割时,需要保留足够的空间来容纳一个新的 BlockLink_t
元数据头。
如果剩下的空间小于 heapMINIMUM_BLOCK_SIZE
,则不会继续分割该块,避免浪费或出错。
static void prvHeapInit( void ) PRIVILEGED_FUNCTION
BlockLink_t * pxFirstFreeBlock;
uint8_t * pucAlignedHeap;
/* 确保堆的起始地址位于正确的对齐边界上 */
pucAlignedHeap = ( uint8_t * ) ( ( ( portPOINTER_SIZE_TYPE ) & ucHeap[ portBYTE_ALIGNMENT - 1 ] ) & ( ~( ( portPOINTER_SIZE_TYPE ) portBYTE_ALIGNMENT_MASK ) ) );
/* xStart 用于保存指向空闲内存块链表中第一个节点的指针。 */
xStart.pxNextFreeBlock = ( void * ) pucAlignedHeap;
xStart.xBlockSize = ( size_t ) 0;
/* xEnd 用于标记空闲内存块链表的末尾。*/
xEnd.xBlockSize = configADJUSTED_HEAP_SIZE;
xEnd.pxNextFreeBlock = NULL;
/* 开始时,只有一个空闲内存块,它的大小占用了整个堆空间。*/
pxFirstFreeBlock = ( BlockLink_t * ) pucAlignedHeap;
pxFirstFreeBlock->xBlockSize = configADJUSTED_HEAP_SIZE;
pxFirstFreeBlock->pxNextFreeBlock = &xEnd;
一些宏
#define heapBITS_PER_BYTE ( ( size_t ) 8 )
//用于判断内存块是否被分配的一个位掩码
#define heapBLOCK_ALLOCATED_BITMASK \
( ( ( size_t ) 1 ) << ( ( sizeof( size_t ) * heapBITS_PER_BYTE ) - 1 ) )
//判断传入的 xBlockSize 是否是一个“有效的内存块大小”
//如果 xBlockSize 的最高位是 1,则它不是一个合法的空闲块大小。
//如果是32位系统,最大合法大小为 0x7fffffff
#define heapBLOCK_SIZE_IS_VALID( xBlockSize )\
( ( ( xBlockSize ) & heapBLOCK_ALLOCATED_BITMASK ) == 0 )
//xBlockSize 最高位是1表示已分配,0表示未分配。
#define heapBLOCK_IS_ALLOCATED( pxBlock )\
( ( ( pxBlock->xBlockSize ) & heapBLOCK_ALLOCATED_BITMASK ) != 0 )
//设置xBlockSize 最高位为1表示已分配
#define heapALLOCATE_BLOCK( pxBlock )\
( ( pxBlock->xBlockSize ) |= heapBLOCK_ALLOCATED_BITMASK )
//设置xBlockSize 最高位为0表示未分配
#define heapFREE_BLOCK( pxBlock )\
( ( pxBlock->xBlockSize ) &= ~heapBLOCK_ALLOCATED_BITMASK )
prvInsertBlockIntoFreeList
将一个新的空闲内存块按照大小顺序插入到空闲链表中,保持链表始终按内存块大小升序排列。
#define prvInsertBlockIntoFreeList( pxBlockToInsert ) \
{ \BlockLink_t * pxIterator; \size_t xBlockSize; \xBlockSize = pxBlockToInsert->xBlockSize; \for( pxIterator = &xStart; pxIterator->pxNextFreeBlock->xBlockSize < xBlockSize; pxIterator = pxIterator->pxNextFreeBlock ) \{ \} \pxBlockToInsert->pxNextFreeBlock = pxIterator->pxNextFreeBlock;\pxIterator->pxNextFreeBlock = pxBlockToInsert; \
}
void * pvPortMalloc( size_t xWantedSize )
BlockLink_t * pxBlock;
BlockLink_t * pxPreviousBlock;
BlockLink_t * pxNewBlockLink;
void * pvReturn = NULL;
size_t xAdditionalRequiredSize;
将用户请求的大小加上一个 BlockLink_t 结构体的大小,并进行字节对齐处理 。
if( xWantedSize > 0 )
{if( heapADD_WILL_OVERFLOW( xWantedSize, xHeapStructSize ) == 0 ){xWantedSize += xHeapStructSize;if( ( xWantedSize & portBYTE_ALIGNMENT_MASK ) != 0x00 ){xAdditionalRequiredSize = portBYTE_ALIGNMENT - ( xWantedSize & portBYTE_ALIGNMENT_MASK );if( heapADD_WILL_OVERFLOW( xWantedSize, xAdditionalRequiredSize ) == 0 ){xWantedSize += xAdditionalRequiredSize;}else{xWantedSize = 0;}}}else{xWantedSize = 0;}
}
挂起任务调度器(临界区保护),调用申请失败钩子函数。
vTaskSuspendAll();
{...👇
}
( void ) xTaskResumeAll();
#if ( configUSE_MALLOC_FAILED_HOOK == 1 )
{if( pvReturn == NULL ){vApplicationMallocFailedHook();}
}
#endif
return pvReturn;
👇
//堆初始化(仅第一次调用时执行)
if( xHeapHasBeenInitialised == pdFALSE )
{prvHeapInit();xHeapHasBeenInitialised = pdTRUE;
}
//从 xStart 开始遍历链表,找到第一个足够大 的空闲块(首次适配)。
if( heapBLOCK_SIZE_IS_VALID( xWantedSize ) != 0 )
{if( ( xWantedSize > 0 ) && ( xWantedSize <= xFreeBytesRemaining ) ){pxPreviousBlock = &xStart;pxBlock = xStart.pxNextFreeBlock;while( ( pxBlock->xBlockSize < xWantedSize ) && ( pxBlock->pxNextFreeBlock != NULL ) ){pxPreviousBlock = pxBlock;pxBlock = pxBlock->pxNextFreeBlock;}if( pxBlock != &xEnd ) //有合适的内存块{ //分配内存并可能进行分割//跳过 BlockLink_t 头部,返回用户可用内存区域的指针pvReturn = ( void * ) ( ( ( uint8_t * ) pxPreviousBlock->pxNextFreeBlock ) + xHeapStructSize );//将该块从空闲链表中移除pxPreviousBlock->pxNextFreeBlock = pxBlock->pxNextFreeBlock;if( ( pxBlock->xBlockSize - xWantedSize ) > heapMINIMUM_BLOCK_SIZE ){ //如果剩余空间足够大,就拆分成两个小块。//前面的小块返回给用户, 剩余小块重新插入空闲链表pxNewBlockLink = ( void * ) ( ( ( uint8_t * ) pxBlock ) + xWantedSize );pxNewBlockLink->xBlockSize = pxBlock->xBlockSize - xWantedSize;pxBlock->xBlockSize = xWantedSize;//更新大小prvInsertBlockIntoFreeList( ( pxNewBlockLink ) );}//更新剩余内存并标记为已分配xFreeBytesRemaining -= pxBlock->xBlockSize;heapALLOCATE_BLOCK( pxBlock );pxBlock->pxNextFreeBlock = NULL;}}
}
void vPortFree( void * pv )
释放这块内存,并更新空闲链表和剩余内存大小
uint8_t * puc = ( uint8_t * ) pv;
BlockLink_t * pxLink;
if( pv != NULL )
{ //向前移动一个 xHeapStructSize(即 BlockLink_t 大小),找到这个内存块对应的链表结构体。puc -= xHeapStructSize; pxLink = ( void * ) puc;configASSERT( heapBLOCK_IS_ALLOCATED( pxLink ) != 0 );//确保要释放的内存块确实是“已分配”的configASSERT( pxLink->pxNextFreeBlock == NULL );//确保这个块不是已经在空闲链表中if( heapBLOCK_IS_ALLOCATED( pxLink ) != 0 ){if( pxLink->pxNextFreeBlock == NULL ){heapFREE_BLOCK( pxLink );//清除该块的“已分配标志位”#if ( configHEAP_CLEAR_MEMORY_ON_FREE == 1 ){ //如果配置选项 configHEAP_CLEAR_MEMORY_ON_FREE == 1,则在释放内存时将其内容清零( void ) memset( puc + xHeapStructSize, 0, pxLink->xBlockSize - xHeapStructSize );}#endifvTaskSuspendAll();{ //将内存块插入空闲链表prvInsertBlockIntoFreeList( ( ( BlockLink_t * ) pxLink ) );xFreeBytesRemaining += pxLink->xBlockSize;}( void ) xTaskResumeAll();}}
}
heap3.c
直接调用标准库函数 malloc() 和 free()
适用于支持内存管理的标准操作系统环境(如 Linux、Windows、带完整 C 运行时的嵌入式系统)
heap4.c
heap2的优化版,多了相邻内存碎片合并功能和堆内存指针保护机制。
HEAP_PROTECTOR
堆内存指针保护机制(Heap Protector) ,用于防止因堆缓冲区溢出导致的链表指针损坏。
在嵌入式系统中,如果某个任务写越界(buffer overflow),可能会破坏:
空闲链表中的指针(如 pxNextFreeBlock)
导致整个堆管理系统崩溃或进入死循环
heap4使用 XOR 加密技术,将指针与一个随机值(canary)异或后存储在链表中,这样即使发生越界写操作,被破坏的也是加密后的指针,不会直接破坏链表结构。
每次访问链表指针时都使用 heapPROTECT_BLOCK_POINTER() 宏进行加密/解密。
#if ( configENABLE_HEAP_PROTECTOR == 1 )
/*** @brief Application provided function to get a random value to be used as canary.** @param pxHeapCanary [out] Output parameter to return the canary value.*/extern void vApplicationGetRandomHeapCanary( portPOINTER_SIZE_TYPE * pxHeapCanary );PRIVILEGED_DATA static portPOINTER_SIZE_TYPE xHeapCanary;/* Macro to load/store BlockLink_t pointers to memory. By XORing the* pointers with a random canary value, heap overflows will result* in randomly unpredictable pointer values which will be caught by* heapVALIDATE_BLOCK_POINTER assert. */#define heapPROTECT_BLOCK_POINTER( pxBlock ) ( ( BlockLink_t * ) ( ( ( portPOINTER_SIZE_TYPE ) ( pxBlock ) ) ^ xHeapCanary ) )
#else#define heapPROTECT_BLOCK_POINTER( pxBlock ) ( pxBlock )
#endif /* configENABLE_HEAP_PROTECTOR */
prvInsertBlockIntoFreeList
插入后仍然保持链表按地址升序排列。
static void prvInsertBlockIntoFreeList( BlockLink_t * pxBlockToInsert ) /* PRIVILEGED_FUNCTION */
{BlockLink_t * pxIterator;uint8_t * puc;//遍历空闲链表,找到一个位置,使得当前块 pxBlockToInsert 插入后仍然保持链表按地址升序排列for( pxIterator = &xStart; heapPROTECT_BLOCK_POINTER( pxIterator->pxNextFreeBlock ) < pxBlockToInsert; pxIterator = heapPROTECT_BLOCK_POINTER( pxIterator->pxNextFreeBlock ) ){}if( pxIterator != &xStart ){heapVALIDATE_BLOCK_POINTER( pxIterator );}puc = ( uint8_t * ) pxIterator;//尝试与前面的块合并(前向合并),判断当前块是否紧接在前一个块之后(地址连续)if( ( puc + pxIterator->xBlockSize ) == ( uint8_t * ) pxBlockToInsert ){ //如果是,则合并两个块:将前一个块的大小增加为两者之和,将当前块“吸收”进前一个块中pxIterator->xBlockSize += pxBlockToInsert->xBlockSize;pxBlockToInsert = pxIterator;}puc = ( uint8_t * ) pxBlockToInsert;//尝试与后面的块合并(后向合并)if( ( puc + pxBlockToInsert->xBlockSize ) == ( uint8_t * ) heapPROTECT_BLOCK_POINTER( pxIterator->pxNextFreeBlock ) ){if( heapPROTECT_BLOCK_POINTER( pxIterator->pxNextFreeBlock ) != pxEnd ){pxBlockToInsert->xBlockSize += heapPROTECT_BLOCK_POINTER( pxIterator->pxNextFreeBlock )->xBlockSize;pxBlockToInsert->pxNextFreeBlock = heapPROTECT_BLOCK_POINTER( pxIterator->pxNextFreeBlock )->pxNextFreeBlock;}else{pxBlockToInsert->pxNextFreeBlock = heapPROTECT_BLOCK_POINTER( pxEnd );}}else{pxBlockToInsert->pxNextFreeBlock = pxIterator->pxNextFreeBlock;}if( pxIterator != pxBlockToInsert ){pxIterator->pxNextFreeBlock = heapPROTECT_BLOCK_POINTER( pxBlockToInsert );}
}
heap5.c
heap4 的扩展版 。支持非连续的多块内存 组成整个堆空间(例如 SRAM、DMA 内存、外部 SDRAM 等不同区域),其他的和heap4基本一样。
void vPortDefineHeapRegions( const HeapRegion_t * const pxHeapRegions ) PRIVILEGED_FUNCTION
- 必须在调用
pvPortMalloc()
之前调用vPortDefineHeapRegions()
函数定义多个内存区域。 - 如果创建了任何任务对象(如任务、队列、事件组等),就会调用
pvPortMalloc()
,因此vPortDefineHeapRegions()
必须在创建其他对象之前调用 。 vPortDefineHeapRegions()
函数只接受一个参数:该参数是一个HeapRegion_t
类型的结构体数组。
堆区域结构体
//portable.h
typedef struct HeapRegion
{uint8_t * pucStartAddress; //指向一块内存的起始地址,这块内存将成为堆的一部分size_t xSizeInBytes; // 内存块的大小(以字节为单位)
} HeapRegion_t;
- 数组必须以一个 NULL 指针和大小为 0 的区域作为结束标志。
- 数组中定义的内存区域 必须按照地址从低到高的顺序排列 。
void vPortDefineHeapRegions( const HeapRegion_t * const pxHeapRegions ) /* PRIVILEGED_FUNCTION */
{BlockLink_t * pxFirstFreeBlockInRegion = NULL;BlockLink_t * pxPreviousFreeBlock;portPOINTER_SIZE_TYPE xAlignedHeap;size_t xTotalRegionSize, xTotalHeapSize = 0;BaseType_t xDefinedRegions = 0;portPOINTER_SIZE_TYPE xAddress;const HeapRegion_t * pxHeapRegion;//确保这个函数只被调用一次。pxEnd 是一个全局变量,用来标记空闲链表的结尾configASSERT( pxEnd == NULL );#if ( configENABLE_HEAP_PROTECTOR == 1 ){ //如果启用了堆保护功能,则设置随机“canary”值来检测堆溢出vApplicationGetRandomHeapCanary( &( xHeapCanary ) );}#endifpxHeapRegion = &( pxHeapRegions[ xDefinedRegions ] );// //遍历所有内存区域,当遇到 xSizeInBytes == 0 时停止循环(即遇到 {NULL, 0})。while( pxHeapRegion->xSizeInBytes > 0 ){ xTotalRegionSize = pxHeapRegion->xSizeInBytes;//对内存块的起始地址进行对齐,并调整可用大小xAddress = ( portPOINTER_SIZE_TYPE ) pxHeapRegion->pucStartAddress;if( ( xAddress & portBYTE_ALIGNMENT_MASK ) != 0 ){xAddress += ( portBYTE_ALIGNMENT - 1 );xAddress &= ~( portPOINTER_SIZE_TYPE ) portBYTE_ALIGNMENT_MASK;//调整可用大小xTotalRegionSize -= ( size_t ) ( xAddress - ( portPOINTER_SIZE_TYPE ) pxHeapRegion->pucStartAddress );}xAlignedHeap = xAddress;/* 设置空闲链表头xStart */if( xDefinedRegions == 0 ){xStart.pxNextFreeBlock = ( BlockLink_t * ) heapPROTECT_BLOCK_POINTER( xAlignedHeap );xStart.xBlockSize = ( size_t ) 0;}else{configASSERT( pxEnd != heapPROTECT_BLOCK_POINTER( NULL ) );////所有内存区域必须按地址递增顺序排列configASSERT( ( size_t ) xAddress > ( size_t ) pxEnd );}#if ( configENABLE_HEAP_PROTECTOR == 1 ){ ////更新低地址边界if( ( pucHeapLowAddress == NULL ) ||( ( uint8_t * ) xAlignedHeap < pucHeapLowAddress ) ){pucHeapLowAddress = ( uint8_t * ) xAlignedHeap;}}#endif /* configENABLE_HEAP_PROTECTOR */pxPreviousFreeBlock = pxEnd;xAddress = xAlignedHeap + ( portPOINTER_SIZE_TYPE ) xTotalRegionSize; //得到当前区域的末尾地址xAddress -= ( portPOINTER_SIZE_TYPE ) xHeapStructSize; //为结束标记预留空间xAddress &= ~( ( portPOINTER_SIZE_TYPE ) portBYTE_ALIGNMENT_MASK );//确保结束标记的位置也满足字节对齐要求//初始化“结束标记”pxEnd = ( BlockLink_t * ) xAddress;pxEnd->xBlockSize = 0;pxEnd->pxNextFreeBlock = heapPROTECT_BLOCK_POINTER( NULL );//设置当前区域的第一个空闲块pxFirstFreeBlockInRegion = ( BlockLink_t * ) xAlignedHeap;pxFirstFreeBlockInRegion->xBlockSize = ( size_t ) ( xAddress - ( portPOINTER_SIZE_TYPE ) pxFirstFreeBlockInRegion );pxFirstFreeBlockInRegion->pxNextFreeBlock = heapPROTECT_BLOCK_POINTER( pxEnd );if( pxPreviousFreeBlock != NULL )//链接上一个区域(如果有){pxPreviousFreeBlock->pxNextFreeBlock = heapPROTECT_BLOCK_POINTER( pxFirstFreeBlockInRegion );}xTotalHeapSize += pxFirstFreeBlockInRegion->xBlockSize;//更新总堆大小#if ( configENABLE_HEAP_PROTECTOR == 1 ){ //更新高地址边界if( ( pucHeapHighAddress == NULL ) ||( ( ( ( uint8_t * ) pxFirstFreeBlockInRegion ) + pxFirstFreeBlockInRegion->xBlockSize ) > pucHeapHighAddress ) ){pucHeapHighAddress = ( ( uint8_t * ) pxFirstFreeBlockInRegion ) + pxFirstFreeBlockInRegion->xBlockSize;}}#endif//移动指针到下一个区域xDefinedRegions++;pxHeapRegion = &( pxHeapRegions[ xDefinedRegions ] );}xMinimumEverFreeBytesRemaining = xTotalHeapSize;xFreeBytesRemaining = xTotalHeapSize;configASSERT( xTotalHeapSize );//确保至少有一个有效的内存区域被定义
}