目录
- 内存管理
- heap4无法解决的内存碎片:
- HEAP4简析
- 分配内存在哪,大小多少
- 如何管理
- 重要源码解析
内存管理
- heap1:最简单的内存管理,管理的其实是一个静态全局变量,只允许分配,不允许释放,设计之初就是用于创建信号量、任务、队列一般不需要释放的数据,不过在FreeRTOS V9.0.0及其以后版本添加 support for static allocation基本就被替代了。
- heap2.:比1多了内存释放,分配也因为有内存释放原因多了一个最佳适配算法,不过在内存释放后没有空闲块合并的功能,只适合信号量队列任务等大小固定的内存分配,随机大小的内存分配释放会因为内存碎片问题而无法进行。后面heap4是heap2的优化版。
- heap3:简单封装标准C库mallco和free,提供线程安全。
- heap4:一般来说FREERTOS五种堆管理里这个是最常见使用的了,首次适配算法,heap2的优化版,多了相邻内存碎片合并功能,内存碎片的风险少很多,不过没有MMU的芯片没有逻辑地址和物理地址的映射概念,内存分配就是一个萝卜一个坑,想做内存紧凑比较费劲。
- heap5:比heap4多了不连续地址多段内存管理,分配释放代码完全一样。
heap4无法解决的内存碎片:
本文主要解析heap4。
HEAP4简析
heap4的使用只需要两个接口pvPortMalloc()和vPortFree()。简要的说下重点
分配内存在哪,大小多少
静态全局变量,位于静态区
static uint8_t ucHeap[ configTOTAL_HEAP_SIZE ];
大小由FreeRTOSConfig.h 文件里的宏定义控制
#define configTOTAL_HEAP_SIZE ((size_t)(40*1024))
如何管理
freertos基本只对空闲块做管理,heap4也一样,空闲块做链表管理,并且链表排序内存地址从小到大管理,上文也说到,是首次适应算法,这么做主要便于内存空闲块合并。
分配:从空闲块找第一个大小合适的空闲块,空闲块大小减去需要的内存扔满足最小空闲块要求,该空闲块把剩余内存拆分出来做新的一个空闲块插入继续按地址排列继续插入内存空闲链表中。
释放:根据释放首地址,释放对应内存块,将内存块按地址插入到空闲块链表里,如果空闲块与相邻内存块地址连续,则合并为一个大空闲内存块
图网上找的的,作者未知
- xStart:静态场两,空闲块链表首节点,指向第一个空闲块节点
- pxEnd:结构体指针,地址是ucHeap末尾,空闲链表尾节点,nextpoint指向null,标志链表结束。
heap4 分配模型图,A->F
结合图文,算是比较大概的介绍了heap4的管理模型,其中还有些字节对齐及一些细节内容由结合源码分析
重要源码解析
typedef struct A_BLOCK_LINK //空闲块节点,具体的分配内存返回的的指针实际上是该块的下一个地址,
//也就是说此结构体只是一个节点信息
{
struct A_BLOCK_LINK *pxNextFreeBlock; /*<< 下一个空闲块. */
size_t xBlockSize; /*<< 该块的大小. 最高位代表该块是否被分配*/
} BlockLink_t;
static void prvHeapInit( void ) //堆初始化,不需要用户调用,第一次pvPortMalloc()会执行该函数
{
BlockLink_t *pxFirstFreeBlock;
uint8_t *pucAlignedHeap;
size_t uxAddress;
size_t xTotalHeapSize = configTOTAL_HEAP_SIZE;
/* 8字节对齐,因为AAPCS规则要求堆栈8字节对齐 */
uxAddress = ( size_t ) ucHeap;
if( ( uxAddress & portBYTE_ALIGNMENT_MASK ) != 0 ) //
{
uxAddress += ( portBYTE_ALIGNMENT - 1 );
uxAddress &= ~( ( size_t ) portBYTE_ALIGNMENT_MASK );
xTotalHeapSize -= uxAddress - ( size_t ) ucHeap;
}
pucAlignedHeap = ( uint8_t * ) uxAddress;
/* xStart 存储空闲节点连表首节点*/
xStart.pxNextFreeBlock = ( void * ) pucAlignedHeap;
xStart.xBlockSize = ( size_t ) 0;
/* pxEnd 和xStart不同,pxEND是一个指针指向堆空闲末尾,标记空闲块链表的结束*/
uxAddress = ( ( size_t ) pucAlignedHeap ) + xTotalHeapSize;
uxAddress -= xHeapStructSize;
uxAddress &= ~( ( size_t ) portBYTE_ALIGNMENT_MASK );
pxEnd = ( void * ) uxAddress;
pxEnd->xBlockSize = 0;
pxEnd->pxNextFreeBlock = NULL;
/* 第一个空闲块指针,指向堆空间字节对齐后的第一个节点,
并且此空闲块的下一个节点是pxend,至此空闲块成链*/
pxFirstFreeBlock = ( void * ) pucAlignedHeap;
pxFirstFreeBlock->xBlockSize = uxAddress - ( size_t ) pxFirstFreeBlock;
pxFirstFreeBlock->pxNextFreeBlock = pxEnd;
/* 一个统计堆空闲历史最小值,一个显示当前堆空闲字节*/
xMinimumEverFreeBytesRemaining = pxFirstFreeBlock->xBlockSize;
xFreeBytesRemaining = pxFirstFreeBlock->xBlockSize;
/* 代表内存快是否分配的bit */
xBlockAllocatedBit = ( ( size_t ) 1 ) << ( ( sizeof( size_t ) * heapBITS_PER_BYTE ) - 1 );
}
初始化函数,主要做了三件事,对初始堆空间做字节对齐,空闲节点串联,xFreeBytesRemaining等信息初始化。
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 require
initialisation to setup the list of free blocks. */
if( pxEnd == NULL ) //末尾节点为null代表未初始化,第一次分配内存需要先对堆空间初始化
{
prvHeapInit();
}
else
{
mtCOVERAGE_TEST_MARKER();
}
/* 高一位代表分配标记位,所以管理的的内存有最大值,申请的不能大于最大值*/
if( ( xWantedSize & xBlockAllocatedBit ) == 0 )
{
if( xWantedSize > 0 )
{
xWantedSize += xHeapStructSize;//分配内存是以块的形式分配,还要加上块节点结构的大小
/*控制字节对齐,保证分配内存字节对齐 */
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 ) )
{
/* 遍历链表找到第一个满足要求的节点 */
pxPreviousBlock = &xStart;
pxBlock = xStart.pxNextFreeBlock;
while( ( pxBlock->xBlockSize < xWantedSize ) && ( pxBlock->pxNextFreeBlock != NULL ) )
{
pxPreviousBlock = pxBlock;
pxBlock = pxBlock->pxNextFreeBlock;
}
/* 找到节点 */
if( pxBlock != pxEnd )
{
/* pxPreviousBlock->pxNextFreeBlock为空闲块地址,真正返回地址的是块节
点信息之后的数组 */
pvReturn = ( void * ) ( ( ( uint8_t * ) pxPreviousBlock->pxNextFreeBlock ) + xHeapStructSize );
/* 节点从空闲表去除 */
pxPreviousBlock->pxNextFreeBlock = pxBlock->pxNextFreeBlock;
/* 拆分空闲节点 */
if( ( pxBlock->xBlockSize - xWantedSize ) > heapMINIMUM_BLOCK_SIZE )
{
/* 分裂算法 */
pxNewBlockLink = ( void * ) ( ( ( uint8_t * ) pxBlock ) + xWantedSize );
configASSERT( ( ( ( size_t ) pxNewBlockLink ) & portBYTE_ALIGNMENT_MASK ) == 0 );
/* 计算新节点信息 */
pxNewBlockLink->xBlockSize = pxBlock->xBlockSize - xWantedSize;
pxBlock->xBlockSize = xWantedSize;
/* 插入空闲快链表 */
prvInsertBlockIntoFreeList( pxNewBlockLink );
}
else
{
mtCOVERAGE_TEST_MARKER();
}
xFreeBytesRemaining -= pxBlock->xBlockSize;
if( xFreeBytesRemaining < xMinimumEverFreeBytesRemaining )
{
xMinimumEverFreeBytesRemaining = xFreeBytesRemaining;
}
else
{
mtCOVERAGE_TEST_MARKER();
}
/*分配块高一位置为标记以分配*/
pxBlock->xBlockSize |= xBlockAllocatedBit;
pxBlock->pxNextFreeBlock = NULL;
}
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();
}
}
#endif
configASSERT( ( ( ( size_t ) pvReturn ) & ( size_t ) portBYTE_ALIGNMENT_MASK ) == 0 );
return pvReturn;
}
分配:调堆初始化、首次适配算法、分裂节点。
void vPortFree( void *pv )
{
uint8_t *puc = ( uint8_t * ) pv;
BlockLink_t *pxLink;
if( pv != NULL )
{
/* 找到释放内存的内存块节点信息*/
puc -= xHeapStructSize;
pxLink = ( void * ) puc;
configASSERT( ( pxLink->xBlockSize & xBlockAllocatedBit ) != 0 );
configASSERT( pxLink->pxNextFreeBlock == NULL );
if( ( pxLink->xBlockSize & xBlockAllocatedBit ) != 0 ) //确保该块已经分配,才能进行释放操作
{
if( pxLink->pxNextFreeBlock == NULL )
{
/* 标志位处理*/
pxLink->xBlockSize &= ~xBlockAllocatedBit;
vTaskSuspendAll();
{
/* 插入空闲块链表 */
xFreeBytesRemaining += pxLink->xBlockSize;
traceFREE( pv, pxLink->xBlockSize );
prvInsertBlockIntoFreeList( ( ( BlockLink_t * ) pxLink ) );
}
( void ) xTaskResumeAll();
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
}
释放:没什么可讲的,内存空闲块合并在prvInsertBlockIntoFreeList函数处理
static void prvInsertBlockIntoFreeList( BlockLink_t *pxBlockToInsert )
{
BlockLink_t *pxIterator;
uint8_t *puc;
/* 根据地址遍历,寻找插入点. */
for( pxIterator = &xStart; pxIterator->pxNextFreeBlock < pxBlockToInsert; pxIterator = pxIterator->pxNextFreeBlock )
{
/* Nothing to do here, just iterate to the right position. */
}
/* 判找到插入点后判断是否和前一个空闲块连续,连续则合并 */
puc = ( uint8_t * ) pxIterator;
if( ( puc + pxIterator->xBlockSize ) == ( uint8_t * ) pxBlockToInsert )
{
pxIterator->xBlockSize += pxBlockToInsert->xBlockSize;
pxBlockToInsert = pxIterator;
}
else
{
mtCOVERAGE_TEST_MARKER();
}
/* 判找到插入点后判断是否和后一个空闲块连续,连续则合并 ,pxEnd标记堆空间结束是例外,不允许合并*/
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( pxIterator != pxBlockToInsert )
{
pxIterator->pxNextFreeBlock = pxBlockToInsert;
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}