赞
踩
每当创建任务、队列、互斥量、软件定时器、信号量或事件组时,RTOS内核会为它们分配RAM。标准函数库中的malloc()和free()函数有些时候能够用于完成这个任务,但是:
- 在嵌入式系统中,它们并不总是可以使用的;
- 它们会占用更多宝贵的代码空间;
- 它们没有线程保护;
- 它们不具有确定性(每次调用执行的时间可能会不同);
当RTOS内核需要RAM时,调用**pvPortMalloc()函数来代替malloc()函数。当RAM要被释放时,调用vPortFree()**函数来代替free()函数。
xWantedSize += ( portBYTE_ALIGNMENT - ( xWantedSize & portBYTE_ALIGNMENT_MASK ) );
xWantedSize 是要申请的字节数
portBYTE_ALIGNMENT是字节对齐数
portBYTE_ALIGNMENT_MASK 是字节对齐掩码
一、heap_4.c
第四种内存分配方法与第二种比较相似,只不过增加了一个合并算法,将相邻的空闲内存块合并成一个大块。与第一种和第二种内存管理策略一样,内存堆仍然是一个大数组,定义为:
static uint8_t ucHeap[ configTOTAL_HEAP_SIZE];
1.1 内存申请:pvPortMalloc()
使用一个链表结构来跟踪记录空闲内存块。结构体定义为:
typedef struct A_BLOCK_LINK
{
struct A_BLOCK_LINK *pxNextFreeBlock; /*指向列表中下一个空闲块*/
size_t xBlockSize; /*当前空闲块的大小,包括链表结构大小*/
} BlockLink_t;
第四种内存管理策略和第二种内存管理策略还有一个很大的不同是:第四种内存管理策略的空闲块链表不是以内存块大小为存储顺序,而是以内存块起始地址大小为存储顺序,地址小的在前,地址大的在后。这也是为了适应合并算法而作的改变。
函数中会用到几个局部静态变量在这里简单说明一下:
- xFreeBytesRemaining:表示当前未分配的内存堆大小
- xMinimumEverFreeBytesRemaining:表示未分配内存堆空间历史最小值。这个值跟xFreeBytesRemaining有很大区别,只有记录未分配内存堆的最小值,才能知道最坏情况下内存堆的使用情况。
- xBlockAllocatedBit:这个变量在第一次调用内存申请函数时被初始化,将它能表示的数值的最高位置1。比如对于32位系统,这个变量被初始化为0x80000000(最高位为1)。内存管理策略使用这个变量来标识一个内存块是否空闲。如果内存块被分配出去,则内存块链表结构成员xBlockSize按位或上这个变量(即xBlockSize最高位置1),在释放一个内存块时,会把xBlockSize的最高位清零。
内存申请过程:
- 首先计算实际要分配的内存大小,判断申请内存合法性,如果合法则从链表头xStart开始查找,如果某个空闲块的xBlockSize字段大小能容得下要申请的内存,则将这块内存取出合适的部分返回给申请者,剩下的内存块组成一个新的空闲块,按照空闲块起始地址大小顺序插入到空闲块链表中,地址小的在前,地址大的在后。
/* 定义一个结构体 */ typedef struct A_BLOCK_LINK { #ifdef MTK_SUPPORT_HEAP_DEBUG uint32_t magic_header; #endif struct A_BLOCK_LINK *pxNextFreeBlock; /*<< The next free block in the list. */ size_t xBlockSize; /*<< The size of the free block. */ #ifdef MTK_SUPPORT_HEAP_DEBUG uint32_t xLinkRegAddr; #endif /* MTK_SUPPORT_HEAP_DEBUG */ } BlockLink_t; /* 创建两个结构体变量,分别指向单链表的头和尾部 */ static BlockLink_t xStart, *pxEnd = NULL; void *pvPortMalloc( size_t xWantedSize ) { BlockLink_t *pxBlock, *pxPreviousBlock, *pxNewBlockLink; void *pvReturn = NULL; vTaskSuspendAll(); //将所有的任务挂起 { if( pxEnd == NULL ) { prvHeapInit(); //进入堆初始化,根据系统的需要,分配合法合理的堆空间 } else { mtCOVERAGE_TEST_MARKER(); } /* 检查申请分配的内存是否具有合法性和检查申请的内存是否过大 */ 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 ); } else { mtCOVERAGE_TEST_MARKER(); } } else { mtCOVERAGE_TEST_MARKER(); } /* 首先遍历链表,找到第1块能比申请空间大小大的空闲块,修改空闲块的信息,记录用户可用的内存首地址。 */ 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 ); #ifdef MTK_SUPPORT_HEAP_DEBUG pxPreviousBlock->pxNextFreeBlock->xLinkRegAddr = xLinkRegAddr; configASSERT(pxPreviousBlock->pxNextFreeBlock->magic_header == MAGIC_HEAP_OVERHEADER); #endif /* MTK_SUPPORT_HEAP_DEBUG */ /* 将已经分配出去的内存块从空闲块链表中删除 */ pxPreviousBlock->pxNextFreeBlock = pxBlock->pxNextFreeBlock; /* 如果剩下的内存足够大,则组成一个新的空闲块 */ /* 如果分配出去的空闲块比申请的空间大很多,则将该空闲块进行分割,把剩余的部分重新添加到链表中。*/ if( ( pxBlock->xBlockSize - xWantedSize ) > heapMINIMUM_BLOCK_SIZE ) { /* 在剩余内存块的起始位置放置一个链表结构并初始化链表成员*/ pxNewBlockLink = ( void * ) ( ( ( uint8_t * ) pxBlock ) + xWantedSize ); configASSERT( ( ( ( uint32_t ) pxNewBlockLink ) & portBYTE_ALIGNMENT_MASK ) == 0 ); pxNewBlockLink->xBlockSize = pxBlock->xBlockSize - xWantedSize; pxBlock->xBlockSize = xWantedSize; #ifdef MTK_SUPPORT_HEAP_DEBUG pxNewBlockLink->magic_header = MAGIC_HEAP_OVERHEADER; #endif /* 将剩余的空闲块插入到空闲块列表中,按照空闲块的地址大小顺序,地址小的在前,地址大的在后 */ prvInsertBlockIntoFreeList( pxNewBlockLink ); } else { mtCOVERAGE_TEST_MARKER(); } xFreeBytesRemaining -= pxBlock->xBlockSize; /* 保存未分配内存堆空间历史最小值 */ if( xFreeBytesRemaining < xMinimumEverFreeBytesRemaining ) { xMinimumEverFreeBytesRemaining = xFreeBytesRemaining; } else { mtCOVERAGE_TEST_MARKER(); } /* 将已经分配的内存块标识为"已分配" */ pxBlock->xBlockSize |= xBlockAllocatedBit; pxBlock->pxNextFreeBlock = NULL; xNumberOfSuccessfulAllocations++; } else { mtCOVERAGE_TEST_MARKER(); } } else { mtCOVERAGE_TEST_MARKER(); } } else { mtCOVERAGE_TEST_MARKER(); } /* traceMALLOC( pvReturn, xWantedSize)是一个宏,用于输出内存分配的调试信息,这个宏定义在FreeRTOS.h中,默认为空,如果需要将这些调试信息输出到串口...*/ 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( ( ( ( uint32_t ) pvReturn ) & portBYTE_ALIGNMENT_MASK ) == 0 ); return pvReturn; }
下面是链表的初始化
heap_2.c中链表的尾部数据并未保存在链表内,是以变量的形式存在的。heap_4.c中的链表尾部数据结构保存在链表空间尾部。
//关于堆栈的初始化 //第一步:起始地址做字节对齐,保存pucAlignedHeap 可用空间大小为xTotalHeapSize //第二步:计算首尾 ,这里需要注意的是链表的尾部指针是保存到该地址尾部的 //第三部:完成链表的初始化,记录内存块信息 static void prvHeapInit( void ) { BlockLink_t *pxFirstFreeBlock; uint8_t *pucAlignedHeap; size_t uxAddress; size_t xTotalHeapSize = configTOTAL_HEAP_SIZE; //起始地址做字节对齐处理 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链表的尾 uxAddress = ( ( size_t ) pucAlignedHeap ) + xTotalHeapSize; uxAddress -= xHeapStructSize; uxAddress &= ~( ( size_t ) portBYTE_ALIGNMENT_MASK ); pxEnd = ( void * ) uxAddress; pxEnd->xBlockSize = 0; pxEnd->pxNextFreeBlock = NULL; //开始时候将内存堆整个看作为一个空闲内存块 pxFirstFreeBlock = ( void * ) pucAlignedHeap; pxFirstFreeBlock->xBlockSize = uxAddress - ( size_t ) pxFirstFreeBlock; pxFirstFreeBlock->pxNextFreeBlock = pxEnd; xMinimumEverFreeBytesRemaining = pxFirstFreeBlock->xBlockSize; //记录最小的空闲内存块大小 xFreeBytesRemaining = pxFirstFreeBlock->xBlockSize; //剩余内存堆大小 /* Work out the position of the top bit in a size_t variable. */ xBlockAllocatedBit = ( ( size_t ) 1 ) << ( ( sizeof( size_t ) * heapBITS_PER_BYTE ) - 1 );//用来标记内存块是否被使用 }
空闲块链表的插入:会判断前后的空闲块能否合并,解决内存碎片化的问题。
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(); } //检查是否可以与后面的内存合并 puc = ( uint8_t * ) pxBlockToInsert; if( ( puc + pxBlockToInsert->xBlockSize ) == ( uint8_t * ) pxIterator->pxNextFreeBlock ) { if( pxIterator->pxNextFreeBlock != pxEnd ) { //合并 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(); } }
在pvPortMalloc()中,若分配出去的空闲块比申请的内存大太多,则需要将内存进行分割,并把分割出的部分重新添加至链表中。
在heap_4.c中的重点为:
将分割出来的空闲块重新添加到链表中的过程,即使用prvInsertBlockIntoFreeList()将其空闲块添加至原本的链表中,则不会产生内存碎片。
static void prvInsertBlockIntoFreeList( BlockLink_t *pxBlockToInsert ) { BlockLink_t *pxIterator; uint8_t *puc; /* 遍历找到分割出来的内存块的下一个内存块,将pxIterator指向它 */ for( pxIterator = &xStart; pxIterator->pxNextFreeBlock < pxBlockToInsert; pxIterator = pxIterator->pxNextFreeBlock ) { /* Nothing to do here, just iterate to the right position. */ } puc = ( uint8_t * ) pxIterator; /* 可以合并的标准为pxIterator的首地址加上pxIterator的块大小之后等于pxBlockToInsert的首地址。相等就说明两个块是相邻的。 */ if( ( puc + pxIterator->xBlockSize ) == ( uint8_t * ) pxBlockToInsert ) { pxIterator->xBlockSize += pxBlockToInsert->xBlockSize; pxBlockToInsert = pxIterator; } else { mtCOVERAGE_TEST_MARKER(); } puc = ( uint8_t * ) pxBlockToInsert; /* FreeRTOS再试着将pxBlockToInsert和pxIterator指向的下一个空闲块进行合并。可合并的标准和刚刚说的一样,只是这次用pxBlockToInsert的首地址加上pxBlockToInsert的块大小与pxIterator指向的下一个块地址比较。*/ if( ( puc + pxBlockToInsert->xBlockSize ) == ( uint8_t * ) pxIterator->pxNextFreeBlock ) { if( pxIterator->pxNextFreeBlock != pxEnd ) { pxBlockToInsert->xBlockSize += pxIterator->pxNextFreeBlock->xBlockSize; pxBlockToInsert->pxNextFreeBlock = pxIterator->pxNextFreeBlock->pxNextFreeBlock; } else { pxBlockToInsert->pxNextFreeBlock = pxEnd;//若是没有合并,则需要修改链表的next指针 } } else { pxBlockToInsert->pxNextFreeBlock = pxIterator->pxNextFreeBlock; } if( pxIterator != pxBlockToInsert ) { pxIterator->pxNextFreeBlock = pxBlockToInsert; } else { mtCOVERAGE_TEST_MARKER(); } }
1.2 内存释放vPortFree()
判断指针合法性的时候多了两个条件,一个是检查回收的块大小最高位是否为1,为1才是合法的,毕竟是分配出去了嘛。第二个是Next指针是否为空,为空了说明那是pxEnd,那就不能回收了。在这两个判断之前也有这两个条件的断言configASSERT(),定义在FreeRTOS.h里,同样也是定义为空,可能是留给用户另外用的吧。
源代码解析:
/* 放置在每个分配的内存块开头的结构的大小必须正确对齐字节。*/ static const size_t xHeapStructSize = ( ( sizeof( BlockLink_t ) + ( portBYTE_ALIGNMENT - 1 ) ) & ~portBYTE_ALIGNMENT_MASK ); void vPortFree( void *pv ) { uint8_t *puc = ( uint8_t * ) pv; BlockLink_t *pxLink; if( pv != NULL ) { /* 被释放的内存在它之前会有一个BlockLink_t结构,向前偏移,重新找回BlockLink_t */ /* 根据参数地址找出内存块链表结构 */ puc -= xHeapStructSize; /* 这种类型转换是为了防止编译器发出警告 */ pxLink = ( void * ) puc; /* 检查块是否被实际分配。 */ configASSERT( ( pxLink->xBlockSize & xBlockAllocatedBit ) != 0 ); configASSERT( pxLink->pxNextFreeBlock == NULL ); #ifdef MTK_SUPPORT_HEAP_DEBUG configASSERT(pxLink->magic_header == MAGIC_HEAP_OVERHEADER); #endif /* 。*/ if( ( pxLink->xBlockSize & xBlockAllocatedBit ) != 0 ) { if( pxLink->pxNextFreeBlock == NULL ) { /* The block is being returned to the heap - it is no longer allocated. */ pxLink->xBlockSize &= ~xBlockAllocatedBit; #ifdef MTK_SUPPORT_HEAP_DEBUG configASSERT(((BlockLink_t *)((uint8_t *)puc + pxLink->xBlockSize))->magic_header == MAGIC_HEAP_OVERHEADER); #endif vTaskSuspendAll();//通过挂起调度器来创建临界区,挂起调度器有些时候也被称为锁定调度器 { /* Add this block to the list of free blocks. */ xFreeBytesRemaining += pxLink->xBlockSize; traceFREE( pv, pxLink->xBlockSize ); prvInsertBlockIntoFreeList( ( ( BlockLink_t * ) pxLink ) ); xNumberOfSuccessfulFrees++; } ( void ) xTaskResumeAll();//挂起,直到调度器被唤醒后才会得到执行 } else { mtCOVERAGE_TEST_MARKER(); } } else { mtCOVERAGE_TEST_MARKER(); } } }
赞
踩
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。