#include <stdlib.h>
#define MPU_WRAPPERS_INCLUDED_FROM_API_FILE
#include "FreeRTOS.h"
#include "task.h"
#undef MPU_WRAPPERS_INCLUDED_FROM_API_FILE
#define configADJUSTED_HEAP_SIZE ( configTOTAL_HEAP_SIZE - portBYTE_ALIGNMENT )
// 在使用之前初始化堆结构体
static void prvHeapInit( void );
//为堆分配空间 ucHeap[17 * 1024]
static uint8_t ucHeap[ configTOTAL_HEAP_SIZE ];
//定义一个连接链表结构,按照其空闲块的大小连接。
typedef struct A_BLOCK_LINK
{
struct A_BLOCK_LINK *pxNextFreeBlock; //链表中下一个空闲块指针
size_t xBlockSize; //空闲块的大小
} BlockLink_t;
static const uint16_t heapSTRUCT_SIZE = ( ( sizeof ( BlockLink_t ) + ( portBYTE_ALIGNMENT - 1 ) ) & ~portBYTE_ALIGNMENT_MASK );
#define heapMINIMUM_BLOCK_SIZE ( ( size_t ) ( heapSTRUCT_SIZE * 2 ) )
static BlockLink_t xStart, xEnd;//创建xStart和xEnd链表连接体,用来指示开始链表和结束链表
static size_t xFreeBytesRemaining = configADJUSTED_HEAP_SIZE;//跟踪剩余空闲字节数目,但是对于存储碎片没有提示
/* STATIC FUNCTIONS ARE DEFINED AS MACROS TO MINIMIZE THE FUNCTION CALL DEPTH. */
//插入一个空闲块到空闲块链表--其遵守大小排序。小的空闲块在链表开始,大的空闲块在链表末尾。
#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, *pxPreviousBlock, *pxNewBlockLink;
static BaseType_t xHeapHasBeenInitialised = pdFALSE;//标志是否首次使用pvPortMalloc
void *pvReturn = NULL;
vTaskSuspendAll();//将任务调度器挂起
{
//若是初次使用malloc,那么需要初始化block链表
if( xHeapHasBeenInitialised == pdFALSE )
{
prvHeapInit();
xHeapHasBeenInitialised = pdTRUE;
}
//第1次调用pvPortMalloc(),那么需要初始化一些关键量,主要是在xStart和xEnd之间将xHeap.ucHeap插进去,下面是一个简单的单向链接图示
//xStart->xHeap.ucHeap->xEnd->NULL
//对heapSTRUCT_SIZE的定义是这样的:
//static const uint16_t heapSTRUCT_SIZE = ( ( sizeof ( BlockLink_t ) + ( portBYTE_ALIGNMENT - 1 ) ) & ~portBYTE_ALIGNMENT_MASK );
//我觉得这句话讲不出什么道理来,可能和linux中page之间的hole空间类似,是为了防止越界之类在空间上做的额外申请,
//因为xBlockLink数据区作为管理本段内存所使用的关键域,如果因为数据操作越界而被修改,那是相当可怕的尤其在动态内存申请、释放比较频繁的时候
//最后计算效果是这样的:如果sizeof( xBlockLink )=7[注:当然编译器不会让它是这个值,怎么着也是2的倍数,这里只是做个极端的例子(gliethttp)],
//那么heapSTRUCT_SIZE = 7+3=10,不过实际在at91sam7s64上sizeof( xBlockLink )=8,所以heapSTRUCT_SIZE=8
/*要求数量是增强的所有它除了要求的字节数外还包含一个BlockLink_t结构体*/
if( xWantedSize > 0 )
{
xWantedSize += heapSTRUCT_SIZE;
/*确保这个块总是对齐到要求的字节数上 。*/
if( ( xWantedSize & portBYTE_ALIGNMENT_MASK ) != 0 )
{
//和方法1中一样,将xWantedSize调整为4的整倍数。字节对齐,at91sam7s64默认使用4字节对齐所以下面折行程序,将xWantedSize扩展成4字节的倍数,
//比如:xWantedSize=13,那么xWantedSize=13+(4-13%4)=13+3=16(gliethttp)
/* Byte alignment required. */
xWantedSize += ( portBYTE_ALIGNMENT - ( xWantedSize & portBYTE_ALIGNMENT_MASK ) );
}
}
if( ( xWantedSize > 0 ) && ( xWantedSize < configADJUSTED_HEAP_SIZE ) )//若想要的空闲块的大小在0~configTOTAL_HEAP_SIZE
{//xWantedSize是一个合法的数值,那么下面将检测自己是否还有这么大的空闲块,因为在内存释放时不提供小内存块合并整理机制,
//所以小内存块会随着时间和不同大小内存块。动态申请、释放的频繁发生最终变的越来越多,直到没有大内存可以申请到,全部都是小内存为止。
//可以将相邻的小块内存合并成大块内存,很好的解决了内存合并生成大内存块的问题。
//当然这种方法,可能远远不如linux中的Buddy伙伴算法灵活,但是在嵌入式系统中应该能够满足用户对内存的基本需求了。
//[其实可以采用以前一篇文章《一种轻巧的“内存动态分配管理机制”》中提到的动态内存分配和申请方式,
//文章地址http://blog.chinaunix.net/u1/38994/showart_351550.html]
pxPreviousBlock = &xStart;//从空闲内存链表头开始找
pxBlock = xStart.pxNextFreeBlock;
while( ( pxBlock->xBlockSize < xWantedSize ) && ( pxBlock->pxNextFreeBlock != NULL ) )
//空闲块的大小小于需要的大小并且存在下一个空闲块
{
pxPreviousBlock = pxBlock; //将每次寻找到的空闲块都保存到pxPreviousBlock中,以备后面调用
pxBlock = pxBlock->pxNextFreeBlock; //获取下一个空闲块用以检测
}
if( pxBlock != &xEnd ) //若我们没有到整个内存的最后就找到我们需要的空闲块
{
pvReturn = ( void * ) ( ( ( uint8_t * ) pxPreviousBlock->pxNextFreeBlock ) + heapSTRUCT_SIZE );
//pxPreviousBlock->pxNextFreeBlock~pxPreviousBlock->pxNextFreeBlock+heapSTRUCT_SIZE之间的空间存放管理数据pxNextFreeBlock和xBlockSize
pxPreviousBlock->pxNextFreeBlock = pxBlock->pxNextFreeBlock;//把本pxBlock从空闲链表上摘下
//#define heapMINIMUM_BLOCK_SIZE ( ( size_t ) ( heapSTRUCT_SIZE * 2 ) )
//如果本pxBlock剩余的字节数大于heapMINIMUM_BLOCK_SIZE,即:还可以用来申请heapSTRUCT_SIZE个字节数据
//那么切割本pxBlock内存块(注意:xWantedSize是已经包含heapSTRUCT_SIZE的了[gliethttp]),
//pxNewBlockLink为切割出来的新的空闲内存块首地址,已经4字节对齐
if( ( pxBlock->xBlockSize - xWantedSize ) > heapMINIMUM_BLOCK_SIZE )
{
pxNewBlockLink = ( void * ) ( ( ( uint8_t * ) pxBlock ) + xWantedSize );//将新的生成的空闲块的地址存起来
pxNewBlockLink->xBlockSize = pxBlock->xBlockSize - xWantedSize;//计算新空闲内存块包括heapSTRUCT_SIZE控制域和数据域的总大小
pxBlock->xBlockSize = xWantedSize; //将申请到的内存块的大小,填入内存块控制域
//把切割出来的内存块添加到空闲内存链表xStart~xEnd之间(注:xStart~xEnd是按空闲内存大小,以从小到达的顺序链起来的单向链表[gliethttp])
//也就是这样虽然看上去,FreeRTOS可以实现各种内存块的申请,但是因为切割出去的小内存块,在内存释放回收的时候,并不能自动进行内存合并整理,
//也就是空闲内存块不能变大,最后只能导致小内存空闲块越来越多,大内存空闲块越来越少,直到不能申请到大内存块
prvInsertBlockIntoFreeList( ( pxNewBlockLink ) ); //将新生成的空闲块重新插入到链表中[链表内部按空闲块的大小排序]
}
xFreeBytesRemaining -= pxBlock->xBlockSize;//剩余空闲块比特数更新
}
}
traceMALLOC( pvReturn, xWantedSize );
}
(void)xTaskResumeAll();
//在运行上面临界区的程序时,可能有任务需要调度,但因为调度器的挂起而没有被调度,只是给出了登记,而这个xTaskResumeAll函数就是要把放进
//xPendingReadyList链表中的任务节点转移到真正的就绪链表pxReadyTasksLists里面,如果任务是因为tick缺失或者因为在恢复实际走过的滴答数时有
//任务需要抢占CPU,则 xAlreadyYielded 都为真,从而导致下面不会运行,如果没有被抢占也就是说当前还是处于最高级任务,但是上面的延时已经使
//其阻塞,从而在下面发生抢占
#if( configUSE_MALLOC_FAILED_HOOK == 1 )
{
if( pvReturn == NULL )
{
extern void vApplicationMallocFailedHook( void );
vApplicationMallocFailedHook();
}
}
#endif
return pvReturn;//返回申请到的内存数据区地址,pvReturn-heapSTRUCT_SIZE处存放了管理该pvReturn内存的控制数据
}
/*-----------------------------------------------------------*/
void vPortFree( void *pv )
{
uint8_t *puc = ( uint8_t * ) pv;
BlockLink_t *pxLink;
if( pv != NULL )
{
puc -= heapSTRUCT_SIZE;//内存数据区的前heapSTRUCT_SIZE空间存放了管理本段内存数据区的控制数据
pxLink = ( void * ) puc;
vTaskSuspendAll();//锁住调度器
{
//把释放出来的内存块添加到空闲内存链表xStart~xEnd之间
//(注:xStart~xEnd是按空闲内存大小,以从小到达的顺序链起来的单向链表[gliethttp])
//也就是这样虽然看上去,FreeRTOS可以实现各种内存块的申请,但是因为切割出去的小内存块
//在内存释放回收的时候,并不能自动进行内存合并整理,也就是空闲内存块不能变大,
//最后只能导致小内存空闲块越来越多,大内存空闲块越来越少,直到不能申请到大内存块
prvInsertBlockIntoFreeList( ( ( BlockLink_t * ) pxLink ) );//将新的空闲块重新插入到链表中[链表内部按空闲块的大小排序]
xFreeBytesRemaining += pxLink->xBlockSize;
traceFREE( pv, pxLink->xBlockSize );
}
( void ) xTaskResumeAll();
}
}
/*-----------------------------------------------------------*/
size_t xPortGetFreeHeapSize( void )
{
return xFreeBytesRemaining;
}
/*-----------------------------------------------------------*/
void vPortInitialiseBlocks( void )
{
/* This just exists to keep the linker quiet. */
}
/*-----------------------------------------------------------*/
static void prvHeapInit( void )
{
BlockLink_t *pxFirstFreeBlock;
uint8_t *pucAlignedHeap;
//确保堆栈是从一个正确的对齐边界开始的。
pucAlignedHeap = ( uint8_t * ) ( ( ( portPOINTER_SIZE_TYPE ) &ucHeap[ portBYTE_ALIGNMENT ] ) & ( ( portPOINTER_SIZE_TYPE ) ~portBYTE_ALIGNMENT_MASK ) );
//*start存放的空block链表模块的第一个项的指针,主要用来预防编译警告的。
xStart.pxNextFreeBlock = ( void * ) pucAlignedHeap;
xStart.xBlockSize = ( size_t ) 0;
//*xEnd被用来标记block空链表中最后一个项的。
xEnd.xBlockSize = configADJUSTED_HEAP_SIZE;
xEnd.pxNextFreeBlock = NULL;
//首先将整个空间设成一个空闲块
pxFirstFreeBlock = ( void * ) pucAlignedHeap;
pxFirstFreeBlock->xBlockSize = configADJUSTED_HEAP_SIZE;
pxFirstFreeBlock->pxNextFreeBlock = &xEnd;
}
/*-----------------------------------------------------------*/