- static size_t xNextFreeByte = ( size_t ) 0;
- static uint8_t *pucAlignedHeap = NULL;
其中,变量xNextFreeByte记录已经分配的内存大小,用来定位下一个空闲的内存堆位置。因为内存堆实际上是一个大数组,我们只需要知道已分配内存的大小,就可以用它作为偏移量找到未分配内存的起始地址。变量xNextFreeByte被初始化为0,然后每次申请内存成功后,都会增加申请内存的字节数目。- void *pvPortMalloc( size_t xWantedSize )
- {
- void *pvReturn = NULL;
- static uint8_t *pucAlignedHeap = NULL;
- /* 确保申请的字节数是对齐字节数的倍数 */
- #if( portBYTE_ALIGNMENT != 1 )
- {
- if( xWantedSize & portBYTE_ALIGNMENT_MASK )
- {
- xWantedSize += ( portBYTE_ALIGNMENT - ( xWantedSize & portBYTE_ALIGNMENT_MASK ) );
- }
- }
- #endif
- vTaskSuspendAll();
- {
- if( pucAlignedHeap == NULL )
- {
- /* 第一次使用,确保内存堆起始位置正确对齐 */
- pucAlignedHeap = ( uint8_t * ) ( ( ( portPOINTER_SIZE_TYPE ) &ucHeap[ portBYTE_ALIGNMENT ] ) & ( ~( ( portPOINTER_SIZE_TYPE ) portBYTE_ALIGNMENT_MASK ) ) );
- }
- /* 边界检查,变量xNextFreeByte是局部静态变量,初始值为0 */
- if( ( ( xNextFreeByte + xWantedSize ) < configADJUSTED_HEAP_SIZE ) &&
- ( ( xNextFreeByte + xWantedSize ) > xNextFreeByte ) )
- {
- /* 返回申请的内存起始地址并更新索引 */
- pvReturn = pucAlignedHeap + xNextFreeByte;
- xNextFreeByte += xWantedSize;
- }
- }
- ( void ) xTaskResumeAll();
- #if( configUSE_MALLOC_FAILED_HOOK == 1 )
- {
- if( pvReturn == NULL )
- {
- extern void vApplicationMallocFailedHook( void );
- vApplicationMallocFailedHook();
- }
- }
- #endif
- return pvReturn;
- }
函数一开始会将申请的内存数量调整到对齐字节数的整数倍,所以实际分配的内存空间可能比申请内存大。比如对于8字节对齐的系统,申请11字节内存,经过对齐后,实际分配的内存是16字节(8的整数倍)。- size_t xPortGetFreeHeapSize( void )
- {
- return ( configADJUSTED_HEAP_SIZE - xNextFreeByte );
- }
从图1-1和图1-2我们知道,宏configADJUSTED_HEAP_SIZE表示内存堆有效的大小,这个值减去已经分配出去的内存大小,正是我们需要的未分配的内存堆大小。static uint8_t ucHeap[ configTOTAL_HEAP_SIZE ];
局部静态变量pucAlignedHeap指向对齐后的内存堆起始位置。地址对齐的原因在第一种内存管理策略中已经说明。假如内存堆数组ucHeap从RAM地址0x10002003处开始,系统按照8字节对齐,则对齐后的内存堆与第一个内存管理策略一样,如图2-1所示:- typedef struct A_BLOCK_LINK
- {
- struct A_BLOCK_LINK *pxNextFreeBlock; /*指向列表中下一个空闲块*/
- size_t xBlockSize; /*当前空闲块的大小,包括链表结构大小*/
- } BlockLink_t;
两个BlockLink_t类型的局部静态变量xStart和xEnd用来标识空闲内存块的起始和结束。刚开始时,整个内存堆有效空间就是一个空闲块,如图2-2所示。因为要包含的信息越来越多,我们必须舍弃一些信息,舍弃的信息可以在上一幅图中找到。- void *pvPortMalloc( size_t xWantedSize )
- {
- BlockLink_t *pxBlock, *pxPreviousBlock, *pxNewBlockLink;
- static BaseType_t xHeapHasBeenInitialised = pdFALSE;
- void *pvReturn = NULL;
- /* 挂起调度器 */
- vTaskSuspendAll();
- {
- /* 如果是第一次调用内存分配函数,这里先初始化内存堆,如图2-2所示 */
- if( xHeapHasBeenInitialised == pdFALSE )
- {
- prvHeapInit();
- xHeapHasBeenInitialised = pdTRUE;
- }
- /* 调整要分配的内存值,需要增加上链表结构体空间,heapSTRUCT_SIZE表示经过对齐扩展后的结构体大小 */
- if( xWantedSize > 0 )
- {
- xWantedSize += heapSTRUCT_SIZE;
- /* 调整实际分配的内存大小,向上扩大到对齐字节数的整数倍 */
- if( ( xWantedSize & portBYTE_ALIGNMENT_MASK ) != 0 )
- {
- xWantedSize += ( portBYTE_ALIGNMENT - ( xWantedSize & portBYTE_ALIGNMENT_MASK ) );
- }
- }
- if( ( xWantedSize > 0 ) && ( xWantedSize < configADJUSTED_HEAP_SIZE ) )
- {
- /* 空闲内存块是按照块的大小排序的,从链表头xStart开始,小的在前大的在后,以链表尾xEnd结束 */
- pxPreviousBlock = &xStart;
- pxBlock = xStart.pxNextFreeBlock;
- /* 搜索最合适的空闲块 */
- while( ( pxBlock->xBlockSize < xWantedSize ) && ( pxBlock->pxNextFreeBlock != NULL ) )
- {
- pxPreviousBlock = pxBlock;
- pxBlock = pxBlock->pxNextFreeBlock;
- }
- /* 如果搜索到链表尾xEnd,说明没有找到合适的空闲内存块,否则进行下一步处理 */
- if( pxBlock != &xEnd )
- {
- /* 返回内存空间,注意是跳过了结构体BlockLink_t空间. */
- pvReturn = ( void * ) ( ( ( uint8_t * ) pxPreviousBlock->pxNextFreeBlock ) + heapSTRUCT_SIZE );
- /* 这个块就要返回给用户,因此它必须从空闲块中去除. */
- 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;
- }
- }
- 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 )
- {
- uint8_t *puc = ( uint8_t * ) pv;
- BlockLink_t *pxLink;
- if( pv != NULL )
- {
- /* 根据传入的参数找到链表结构 */
- puc -= heapSTRUCT_SIZE;
- /* 预防某些编译器警告 */
- pxLink = ( void * ) puc;
- vTaskSuspendAll();
- {
- /* 将这个块添加到空闲块列表 */
- prvInsertBlockIntoFreeList( ( ( BlockLink_t * ) pxLink ) );
- /* 更新未分配的内存堆大小 */
- xFreeBytesRemaining += pxLink->xBlockSize;
- traceFREE( pv, pxLink->xBlockSize );
- }
- ( void ) xTaskResumeAll();
- }
- }
我们举一个例子,将图2-3 pvReturn指向的内存块释放掉,假设(configADJUSTED_HEAP_SIZE-40)远大于要释放的内存块大小,释放后的内存堆如图2-4所示:- size_t xPortGetFreeHeapSize( void )
- {
- return xFreeBytesRemaining;
- }
局部静态变量xFreeBytesRemaining在内存申请和内存释放函数中多次提到,它用来动态记录未分配的内存堆大小。- void *pvPortMalloc( size_t xWantedSize )
- {
- void *pvReturn;
- vTaskSuspendAll();
- {
- pvReturn = malloc( 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 )
- {
- if( pv )
- {
- vTaskSuspendAll();
- {
- free( pv );
- traceFREE( pv, 0 );
- }
- ( void ) xTaskResumeAll();
- }
- }
static uint8_t ucHeap[ configTOTAL_HEAP_SIZE ];
- typedef struct A_BLOCK_LINK
- {
- struct A_BLOCK_LINK *pxNextFreeBlock; /*指向列表中下一个空闲块*/
- size_t xBlockSize; /*当前空闲块的大小,包括链表结构大小*/
- } BlockLink_t;
与第二种内存管理策略一样,空闲内存块也是以单链表的形式组织起来的,BlockLink_t类型的局部静态变量xStart表示链表头,但第四种内存管理策略的链表尾保存在内存堆空间最后位置,并使用BlockLink_t指针类型局部静态变量pxEnd指向这个区域(第二种内存管理策略使用静态变量xEnd表示链表尾),如图4-1所示。- void *pvPortMalloc( size_t xWantedSize )
- {
- BlockLink_t *pxBlock, *pxPreviousBlock, *pxNewBlockLink;
- void *pvReturn = NULL;
- vTaskSuspendAll();
- {
- /* 如果是第一次调用内存分配函数,则初始化内存堆,初始化后的内存堆如图4-1所示 */
- if( pxEnd == NULL )
- {
- prvHeapInit();
- }
- /* 申请的内存大小合法性检查:是否过大.结构体BlockLink_t中有一个成员xBlockSize表示块的大小,这个成员的最高位被用来标识这个块是否空闲.因此要申请的块大小不能使用这个位.*/
- if( ( xWantedSize & xBlockAllocatedBit ) == 0 )
- {
- /* 计算实际要分配的内存大小,包含链接结构体BlockLink_t在内,并且要向上字节对齐 */
- if( xWantedSize > 0 )
- {
- xWantedSize += xHeapStructSize;
- /* 对齐操作,向上扩大到对齐字节数的整数倍 */
- if( ( xWantedSize & portBYTE_ALIGNMENT_MASK ) != 0x00 )
- {
- xWantedSize += ( portBYTE_ALIGNMENT - ( xWantedSize & portBYTE_ALIGNMENT_MASK ) );
- configASSERT( ( xWantedSize & portBYTE_ALIGNMENT_MASK ) == 0 );
- }
- }
- if( ( xWantedSize > 0 ) && ( xWantedSize <= xFreeBytesRemaining ) )
- {
- /* 从链表xStart开始查找,从空闲块链表(按照空闲块地址顺序排列)中找出一个足够大的空闲块 */
- pxPreviousBlock = &xStart;
- pxBlock = xStart.pxNextFreeBlock;
- while( ( pxBlock->xBlockSize < xWantedSize ) && ( pxBlock->pxNextFreeBlock != NULL ) )
- {
- pxPreviousBlock = pxBlock;
- pxBlock = pxBlock->pxNextFreeBlock;
- }
- /* 如果最后到达结束标识,则说明没有合适的内存块,否则,进行内存分配操作*/
- if( pxBlock != pxEnd )
- {
- /* 返回分配的内存指针,要跳过内存开始处的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 );
- configASSERT( ( ( ( size_t ) pxNewBlockLink ) & portBYTE_ALIGNMENT_MASK ) == 0 );
- pxNewBlockLink->xBlockSize = pxBlock->xBlockSize - xWantedSize;
- pxBlock->xBlockSize = xWantedSize;
- /* 将剩余的空闲块插入到空闲块列表中,按照空闲块的地址大小顺序,地址小的在前,地址大的在后 */
- prvInsertBlockIntoFreeList( pxNewBlockLink );
- }
- /* 计算未分配的内存堆空间,注意这里并不能包含内存碎片信息 */
- xFreeBytesRemaining -= pxBlock->xBlockSize;
- /* 保存未分配内存堆空间历史最小值 */
- if( xFreeBytesRemaining < xMinimumEverFreeBytesRemaining )
- {
- xMinimumEverFreeBytesRemaining = xFreeBytesRemaining;
- }
- /* 将已经分配的内存块标识为"已分配" */
- pxBlock->xBlockSize |= xBlockAllocatedBit;
- pxBlock->pxNextFreeBlock = NULL;
- }
- }
- }
- 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;
- /* 检查这个内存块确实被分配出去 */
- 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();
- }
- }
- }
- }
如图4-2所示的内存堆示意图,如果我们将32字节的“已分配空间2”释放,由于这个内存块的上面和下面都是空闲块,所以在将它插入到空闲块链表的过程在中,会先和“剩余空闲块1”合并,合并后的块再和“剩余空闲块2”合并,这样组成一个大的空闲块,如图4-3所示:- size_t xPortGetFreeHeapSize( void )
- {
- return xFreeBytesRemaining;
- }
- size_t xPortGetMinimumEverFreeHeapSize( void )
- {
- return xMinimumEverFreeBytesRemaining;
- }
- HeapRegion_t xHeapRegions[] =
- {
- { ( uint8_t * ) 0x80000000UL, 0x10000 },
- { ( uint8_t * ) 0x90000000UL, 0xa0000 },
- { NULL, 0 }
- };
两个内存块要按照地址顺序放入到数组中,地址小的在前,因此地址为0x80000000的内存块必须放数组的第一个位置。数组必须以使用一个NULL指针和0字节元素作为结束,以便让内存管理程序知道何时结束。联系客服