文章目录
7 FreeRTOS内存管理
7.1 内存管理
在之前的学习中,我们经常看到静态分配方式和动态分配方式。我们画一下内存图来理解他们的不同。其中,bss区域也叫做ZI-data(bss)区。
7.1.1 C语言的内存分布图
首先我们复习一下C语言的内存图。
7.1.2 动态分配内存时的FreeRTOS的内存图
7.1.3 静态分配内存时的FreeRTOS的内存图
7.2 heap.c文件
首先思考一个问题,为什么FreeRTOS多数情况不直接使用malloc和free来分配内存:
因为:不安全,主要的原因如下:
- 这些函数在小型的嵌入式系统中总是不可用的,因为小型嵌入式设备的RAM内存总是不足的。而malloc和free需要占掉相当大的一部分内存。
- 执行时间并不确定,每一次调用这些函数的执行时间可能都不一样,在实时操作系统中,分配内存的时间必须是可预测的,pvPortMalloc()和pvPortfree()分配内存时的时间是确定的。
- 这两个函数会使得链接器配置十分复杂。
- malloc分配内存时,是可以覆盖别的已经使用的地方的内存的,如果允许malloc时堆空间不够时覆盖其他变量的占据的内存,那么对于没有mmu的单片机来说,是致命的,任务和任务之间分配好的堆就会出现很大的问题。
在之前移植FreeRTOS的时候,有heap1,2,3,4,5五个文件,这五个文件都是用来分配内存的,但是有不一样的特性:
- heap1.c:没有free的函数,只能分配内存但是不释放,是直接在原有的基础上进行对齐累加的,适合小型,不需要释放任何存储空间的代码。
- heap2.c:有free相关函数,会自动释放之前申请过大的内存块,但是不会进行碎片内存的合并。
- heap3.c:是malloc()和free()的抽象层,直接调用C语言库中的这两个函数。同时多加了线程安全措施。
- heap4.c:与heap2.c相同,只不过比起heap.2他拥有了碎片内存合并的功能,所以成为我们最常用的内存文件。
- heap5.c:与heap4.c相同,但是heap5可以跨越不连续区域进行存储,当我们有多篇内存的时候可以使用这种方式。
内存一些重要的宏定义
//系统堆总大小,即ucHeap的大小
#define configTOTAL_HEAP_SIZE ((uint32_t)33 * 1024)
//系统堆对齐字节(8字节)
#define portBYTE_ALIGNMENT 8
//系统堆可分配大小
#define configADJUSTED_HEAP_SIZE (configTOTAL_HEAP_SIZE-portBYTE_ALIGNMENT)
//未初始化的静态数组,即系统堆区
static uint8_t ucHeap[ configTOTAL_HEAP_SIZE ];//就是我们上图中的ucHeap
7.2.1 heap1.c
void * pvPortMalloc( size_t xWantedSize )
{
void * pvReturn = NULL;//申请的内存的首地址,用来返回
static uint8_t * pucAlignedHeap = NULL;//指向堆内存的起始地址。
//对齐操作
#if ( portBYTE_ALIGNMENT != 1 )
{
if( xWantedSize & portBYTE_ALIGNMENT_MASK )
{
/* Byte alignment required. Check for overflow. */
if(( xWantedSize + ( portBYTE_ALIGNMENT - ( xWantedSize & portBYTE_ALIGNMENT_MASK ) ) ) > xWantedSize)
{
xWantedSize += ( portBYTE_ALIGNMENT - ( xWantedSize & portBYTE_ALIGNMENT_MASK ) );
}
else
{
xWantedSize = 0;
}
}
}
#endif /* if ( portBYTE_ALIGNMENT != 1 ) */
vTaskSuspendAll();//挂起任务调度器(注意实现原理是是停止了任务调度器而不是挂起了所有其他的任务),进入临界区,也就是说一个任务内存分配的时候,不可以有其他任务来打断他。(进入临界区的另一个原因是没有MMU操作的都是真实的物理地址,所以内存资源的分配一定要注意防范冲突!!!),同时它支持嵌套操作,他有一个计数器来计数被挂起了几次,相应的挂起几次就需要我们resume几次,同时要注意的是,它相当于使调度器程序停止了而已,此任务并没有停止,所以这个过程中不存在上下文的切换。
{
if( pucAlignedHeap == NULL )
{
//ucheap的对齐操作(向上对齐)
pucAlignedHeap = ( uint8_t * ) ( ( ( portPOINTER_SIZE_TYPE ) & ucHeap[ portBYTE_ALIGNMENT - 1 ] ) & ( ~( ( portPOINTER_SIZE_TYPE ) portBYTE_ALIGNMENT_MASK ) ) );
}
//检查是否有足够的空间进行分配
if( ( xWantedSize > 0 ) &&
( ( xNextFreeByte + xWantedSize ) < configADJUSTED_HEAP_SIZE ) &&
( ( xNextFreeByte + xWantedSize ) > xNextFreeByte ) )
{
/* Return the next free byte then increment the index past this
* block. */
pvReturn = pucAlignedHeap + xNextFreeByte;
xNextFreeByte += xWantedSize;
}
traceMALLOC( pvReturn, xWantedSize );//统计被使用的空间
}
( void ) xTaskResumeAll();//恢复调度器,离开临界区。
#if ( configUSE_MALLOC_FAILED_HOOK == 1 )//malloc失败的钩子函数
{
if( pvReturn == NULL )
{
vApplicationMallocFailedHook();//malloc失败的钩子函数,这里包含了malloc失败后该如何处理。
}
}
#endif
return pvReturn;//返回分配的内存的首地址
}
/*-----------------------------------------------------------*/
void vPortFree( void * pv )
{
//在heap1中没有free操作
( void ) pv;
//在heap1中无法释放内存,所以一旦传入除了NULL以外的地址,configASSERT断言宏由于pv == NULL不成立,所以就会停止系统运行并且向标准输出输出错误原因。
configASSERT( pv == NULL );
}
7.2.2 heap2.c
void * pvPortMalloc( size_t xWantedSize )
{
BlockLink_t * pxBlock;
BlockLink_t * pxPreviousBlock;
BlockLink_t * pxNewBlockLink;
PRIVILEGED_DATA static BaseType_t xHeapHasBeenInitialised = pdFALSE;
void * pvReturn = NULL;
size_t xAdditionalRequiredSize;
vTaskSuspendAll();
{
/* If this is the first call to malloc then the heap will require
* initialisation to setup the list of free blocks. */
if( xHeapHasBeenInitialised == pdFALSE )
{
prvHeapInit();
xHeapHasBeenInitialised = pdTRUE;
}
if( xWantedSize > 0 )
{
/* The wanted size must be increased so it can contain a BlockLink_t
* structure in addition to the requested amount of bytes. Some
* additional increment may also be needed for alignment. */
xAdditionalRequiredSize = heapSTRUCT_SIZE + portBYTE_ALIGNMENT - ( xWantedSize & portBYTE_ALIGNMENT_MASK );
if( heapADD_WILL_OVERFLOW( xWantedSize, xAdditionalRequiredSize ) == 0 )
{
xWantedSize += xAdditionalRequiredSize;
}
else
{
xWantedSize = 0;
}
}
/* Check the block size we are trying to allocate is not so large that the
* top bit is set. The top bit of the block size member of the BlockLink_t
* structure is used to determine who owns t