参考链接: link.
什么是堆
c语言中有两个概念经常一起被提及,堆和栈。
栈一般是系统调用,用于函数调用中保存现场(局部变量等)。不难想象,函数调用中主函数往往是最先执行,调用其他函数,然后最后返回。而最深层次的函数(内部不调用其他任何函数的函数)往往是执行完就离开,所以使用栈管理函数调用最为合适。
堆则一般是由程序员调用并管理。最直观的,一般malloc函数和free函数都是操作的这个空间,不过freeRTOS中一般则是使用freeRTOS中定义的函数(pvPortMalloc和vPortFree代替malloc和free函数)。
heap_1/2/4.c共有部分
freeRTOS是怎么代替c语言中的malloc和free函数的呢,可以看以下代码,代码的作用是申请一个很大的数组。
#if( configAPPLICATION_ALLOCATED_HEAP == 1 )
extern uint8_t ucHeap[ configTOTAL_HEAP_SIZE ];
#else
static uint8_t ucHeap[ configTOTAL_HEAP_SIZE ];
这里我的理解是,freeRTOS层使用c语言申请内存的方式申请一个很大的数组(此处configTOTAL_HEAP_SIZE 的数值是15360),极端情况下,我们把可以申请到的所有空间全部申请下来。然后用户层调用的(freeRTOS的)pvPortMalloc和vPortFree函数则是在此数组中操作,操作的函数原型自然是freeRTOS函数层中定义的。
heap_1.c
/*
* The simplest possible implementation of pvPortMalloc(). Note that this
* implementation does NOT allow allocated memory to be freed again.
*/
以上是heap_1.c开头处的一段注释,此文件最大的特点就是只负责申请内存,不负责内存的释放。所以这里我们只看pvPortMalloc函数。
在实际分配内存之前,函数要先进行一些操作,其中...
代表省略的代码(下同)。
void *pvPortMalloc( size_t xWantedSize )
{
...
#if( portBYTE_ALIGNMENT != 1 )
{
if( xWantedSize & portBYTE_ALIGNMENT_MASK )
{
/* Byte alignment required. */
xWantedSize += ( portBYTE_ALIGNMENT - ( xWantedSize & portBYTE_ALIGNMENT_MASK ) );
}
}
#endif
...
以上代码的作用是将要申请的内存区间扩充到比xWantedSize
大的最小的portBYTE_ALIGNMENT
的倍数。此处xWantedSize & portBYTE_ALIGNMENT_MASK
执行的操作应该等同于xWantedSize % portBYTE_ALIGNMENT
,至于为什么要这么写我猜测是因为位运算要更快一些。
之后还要对申请到的数组进行同样的操作:
pucAlignedHeap = ( uint8_t * ) ( ( ( portPOINTER_SIZE_TYPE ) &ucHeap[ portBYTE_ALIGNMENT ] ) & \
( ~( ( portPOINTER_SIZE_TYPE ) portBYTE_ALIGNMENT_MASK ) ) );
此处将原先申请到的数组的首地址调整到portBYTE_ALIGNMENT
的倍数,不然之前的操作就没有意义了。试想,如果首地址不是portBYTE_ALIGNMENT
的整数倍,之后即使按照portBYTE_ALIGNMENT
的整数倍申请地址,申请到地址也不是portBYTE_ALIGNMENT
的整数倍。
之后才是实际的内存分配的内容:
//确保1,申请的地址没有超过内存中数组的最大值
//2,地址没有溢出
if( ( ( xNextFreeByte + xWantedSize ) < configADJUSTED_HEAP_SIZE ) &&
( ( xNextFreeByte + xWantedSize ) > xNextFreeByte ) )
{
//返回当前申请到的地址
pvReturn = pucAlignedHeap + xNextFreeByte;
xNextFreeByte += xWantedSize;
}
如果定义了宏configUSE_MALLOC_FAILED_HOOK
,内存分配失败时会调用vApplicationMallocFailedHook
函数
#if( configUSE_MALLOC_FAILED_HOOK == 1 )
{
if( pvReturn == NULL )
{
extern void vApplicationMallocFailedHook( void );
vApplicationMallocFailedHook();
}
}
#endif
至此,heap_1.c中pvPortMalloc
函数基本结束。
heap_2.c
/*
* A sample implementation of pvPortMalloc() and vPortFree() that permits
* allocated blocks to be freed, but does not combine adjacent free blocks
* into a single larger block (and so will fragment memory). See heap_4.c for
* an equivalent that does combine adjacent blocks into single larger blocks.
*/
相比于heap_1.c中“管杀不管埋”的内存分配,heap_2.c不仅可以申请内存,还可以释放内存。美中不足的是,它不会合并相邻的空闲内存块。不过注释中也说明这一点会在heap_4.c中改进。
heap_2.c使用链表表示内存中的占用(空闲)情况,链表节点通过指针和一个size_t类型的数据确定了整个堆区的空闲内存的分配情况,链表项里的指针指向的是下一个链表项结点。那么如何确定空闲内存的地址呢:用户申请内存后,内存中实际分配的地址有两部分,第一部分是这个链表项,紧挨着链表项的才是用户申请的地址,所以链表项地址加上链表项结点的大小就是用户申请分配的地址。
typedef struct A_BLOCK_LINK
{
struct A_BLOCK_LINK *pxNextFreeBlock;
size_t xBlockSize;
} BlockLink_t;
prvHeapInit函数
第一次调用pvPortMalloc
函数时,会先执行prvHeapInit
函数,目的是初始化上面的链表。
static void prvHeapInit( void )
{
BlockLink_t *pxFirstFreeBlock;
uint8_t *pucAlignedHeap;
//同heap_1.c中一样,将数组的首位对齐到portPOINTER_SIZE_TYPE 的整数倍
pucAlignedHeap = ( uint8_t * ) ( ( ( portPOINTER_SIZE_TYPE ) &ucHeap[ portBYTE_ALIGNMENT ] ) & ( ~( ( portPOINTER_SIZE_TYPE ) portBYTE_ALIGNMENT_MASK ) ) );
//用于指向链表的首地址(对齐过后的)
xStart.pxNextFreeBlock = ( void * ) pucAlignedHeap;
xStart.xBlockSize = ( size_t ) 0;
//标记链表的结尾
xEnd.xBlockSize = configADJUSTED_HEAP_SIZE;
xEnd.pxNextFreeBlock = NULL;
//刚开始,只有一个空闲的内存块,其大小是整个内存空间
pxFirstFreeBlock = ( void * ) pucAlignedHeap;
pxFirstFreeBlock->xBlockSize = configADJUSTED_HEAP_SIZE;
pxFirstFreeBlock->pxNextFreeBlock = &xEnd;
}
pvPortMalloc函数
同heap_1.c,每次申请内存都要将要申请的内存大小拓展到portBYTE_ALIGNMENT
的倍数,不同的是,这里的申请的内存除了用户申请使用的之外,还要额外添加A_BLOCK_LINK
需要的内存。毕竟每次申请内存都要创建一个链表结点做记录,C语言层面能被申请的内存都被申请完了(ucHeap[ configTOTAL_HEAP_SIZE ])
,自然要负责将这一部分的内存也申请下来。举个例子,假设A_BLOCK_LINK
需要的内存大小是12,用户申请50个内存,那么至少要分配50+12=62个内存,这还不算内存对齐操作之后的部分,代码如下:
if( xWantedSize > 0 )
{
xWantedSize += heapSTRUCT_SIZE;
/* Ensure that blocks are always aligned to the required number of bytes. */
if( ( xWantedSize & portBYTE_ALIGNMENT_MASK ) != 0 )
{
/* Byte alignment required. */
xWantedSize += ( portBYTE_ALIGNMENT - ( xWantedSize & portBYTE_ALIGNMENT_MASK ) );
}
}
以上都是对xWantedSize
进行操作,操作完成后,从链表的第一项开始查找空闲内存比xWantedSize
大的项:
pxPreviousBlock = &xStart;
pxBlock = xStart.pxNextFreeBlock;
while( ( pxBlock->xBlockSize < xWantedSize ) && ( pxBlock->pxNextFreeBlock != NULL ) )
{
pxPreviousBlock = pxBlock;
pxBlock = pxBlock->pxNextFreeBlock;
}
如果pxBlock
不是xEnd
,代表找到了满足要求的链表项:
//返回地址,由于申请时连带申请了A_BLOCK_LINK链表项,所以地址要加heapSTRUCT_SIZE ,返回的是用户实际申请的地址
pvReturn = ( void * ) ( ( ( uint8_t * ) pxPreviousBlock->pxNextFreeBlock ) + heapSTRUCT_SIZE );
//由于当前列表项的内存空间被占用,不是free状态,所以将此链表项删除
pxPreviousBlock->pxNextFreeBlock = pxBlock->pxNextFreeBlock;
//如果原先空闲内存大于要分配的内存,分配完内存后原内存分为两个部分
//第一部分是分配出去的,第二的部分是依旧空闲的内存
if( ( pxBlock->xBlockSize - xWantedSize ) > heap