FreeRTOS系列|内存管理一

2023-05-16

内存管理一

内存管理是一个系统基本组成部分,FreeRTOS中大量使用了内存管理,比如创建任务、信号量、队列等会自动从堆中申请内存。用户应用层代码也可以使用FreeRTOS提供的内存管理函数来申请和释放内存

1. 内存管理简介

FreeRTOS创建任务、信号量、队列等的时候有两种内存申请的方法:一种是动态的申请所需的RAM;一种是由用户自行定义所需的RAM(静态申请)

两者的区别动态内存静态内存
时间不同发生在程序调入和执行的时候发生在程序编译和连接的时候
空间不同堆只能动态分配;栈可以动态分配;动态分配由函数malloc进行(栈的动态分配由编译器进行)栈也可以静态分配;静态分配是编译器完成的

标准C库中的malloc()和free()函数也可以实现动态内存管理,但是其有如下缺陷:

  • 在小型嵌入式系统中效率不高
  • 占据了相当大的一块代码空间
  • 它们几乎都不是安全的
  • 具有不确定性,每次执行的时间不同
  • 有可能产生内存碎片
  • 这两个函数会使得链接器配置得复杂
  • 若允许堆空间的生长方向覆盖其他变量占据的内存,会成为 debug 的灾难

内存碎片是指小块的、碎片化的内存。内存碎片是伴随着内存申请和释放而来的,如下图所示

在这里插入图片描述

FreeRTOS使用pvPortMalloc()函数来替代malloc()申请内存,使用vPortFree()函数来替代 free()释放内存

不同的嵌入式系统对于内存分配和时间要求不同,内存分配算法可作为系统的可选选项,FreeRTOS使用者就可以使用适合的内存分配方法。FreeRTOS提供了5中内存分配方法,这5种方法分别对应heap_1.c、heap_2.c、heap_3.c、heap_4.c、heap_5.c这五个文件。下面将会详见介绍这五种方法的区别

分配方法区别
heap_1.c最简单,但是只能申请内存,不能释放
heap_2.c提供了内存释放函数,但是不能合并内存块,易导致内存碎片
heap_3.c对标准C中的malloc()和free()的简单封装,并提供了线程保护
heap_4.c在heap_2.c的基础上增加了内存块合并功能,降低了内存碎片的产生
heap_5.c在heap_4.c的基础上,支持内存堆使用不连续的内存块

2. heap_1内存分配方法

2.1 分配方法介绍

动态内存分配需要一个内存堆,不管哪种分配方法,FreeRTOS中的内存堆都为uxHeap[],大小为configTOTAL_HEAP_SIZE,其在heap_x.c(x为1~5)中定义

#if (configAPPLICATION_ALLCATED_HEAP == 1)	
	extern uint8_t ucHeap[configTOTAL_HEAP_SIZE]; //需要用户自行定义内存堆
#else
	static uint8_t ucHeap[configTOTAL_HEAP_SIZE]; //编译器决定

heap_1内存分配的特点如下:

  • 适用于创建好任务、信号量和队列就不会删除的应用
  • 具有可确定性,不会导致内存碎片
  • 代码实现和内存分配过程简单,内存是从一个静态数字中分配,适合于不需要动态内存分配的应用

在这里插入图片描述

2.2 内存申请函数

heap_1的内存申请函数pvPortMalloc()源码如下:

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)));
	}
	/* 检查是否有足够的内存供分配,以及是否越界 */
	if(((xNextFreeByte + xWantedSize) < configADJUSTED_HEAP_SIZE) &&((xNextFreeByte + xWantedSize) > xNextFreeByte)){
	  pvReturn = pucAlignedHeap + xNextFreeByte;//返回申请到的内存首地址
	  xNextFreeByte += xWantedSize;
	}
	traceMALLOC( pvReturn, xWantedSize );
  }
  ( void ) xTaskResumeAll();//恢复任务调度器
  #if( configUSE_MALLOC_FAILED_HOOK == 1 )//若使能了内存申请失败钩子函数
  {
	if( pvReturn == NULL ){
	  extern void vApplicationMallocFailedHook(void);//钩子函数,用户自已实现
	  vApplicationMallocFailedHook();
	}
  }
  #endif
  return pvReturn;//成功返回申请到的内存首地址,失败返回NULL
}
2.3 内存释放函数

heap_1的内存释放函数vPortFree()源码如下:

void vPortFree( void *pv ){
  /* 可以看出没有具体的释放过程,即申请内存成功就不允许释放 */
  ( void ) pv;
  configASSERT( pv == NULL );
}

3. heap_2内存分配方法

3.1 分配方法介绍

heap_2提供了内存释放函数,但是缺点是不会把释放的内存块合并成大的内存块,因此随着不断的申请释放内存,内存堆就会被分为多个大小不一的内存块,也就是会导致内存碎片

内存块:为了实现内存释放,heap_2引入了内存块概念,每分出去一段内存就是一个内存块,剩下的空闲内存也是一个内存块,内存块大小不定。使用链表结构来管理内存块

typedef struct A_BLOCK_LINK
{
	struct A_BLOCK_LINK *pxNextFreeBlock;  //指向下一个空闲内存块
	size_t xBlockSize;  //当前空闲内存块大小
} BlockLink_t;

heap_2内存分配的特点如下:

  • 可使用在可能会重复的删除任务、队列、信号量等的应用中
  • 若分配和释放的内存大小是随机的,不建议使用该分配方法
  • 具有不可确定性,但仍比标准C中的malloc()和free()效率高

在这里插入图片描述

3.2 内存堆初始化函数

内存堆初始化函数prvHeapInit( )源码如下:

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)));
  /* xStart指向空闲内存块链表首 */
  xStart.pxNextFreeBlock = ( void * ) pucAlignedHeap;
  xStart.xBlockSize = ( size_t ) 0;
  /* xEnd指向空闲内存块链表表尾 */
  xEnd.xBlockSize = configADJUSTED_HEAP_SIZE;
  xEnd.pxNextFreeBlock = NULL;
  /* 初始化时只有一个空闲内存块,其大小就是可用的内存堆大小 */
  pxFirstFreeBlock = ( void * ) pucAlignedHeap;
  pxFirstFreeBlock->xBlockSize = configADJUSTED_HEAP_SIZE;
  pxFirstFreeBlock->pxNextFreeBlock = &xEnd;
}

初始化后的内存堆

在这里插入图片描述

3.3 内存块插入函数

heap_2允许内存释放,可用使用内存块插入函数将释放的内存添加到内存链表中

#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;			
}
3.4 内存申请函数

内存申请函数源码如下

void *pvPortMalloc( size_t xWantedSize ){
  BlockLink_t *pxBlock, *pxPreviousBlock, *pxNewBlockLink;
  static BaseType_t xHeapHasBeenInitialised = pdFALSE;
  void *pvReturn = NULL;
  vTaskSuspendAll();
  {
	/* 第一次申请内存需要初始化内存堆 */
	if( xHeapHasBeenInitialised == pdFALSE ){
	  prvHeapInit();
	  xHeapHasBeenInitialised = pdTRUE;
	}
	/* 内存大小字节对齐,实际申请时还要加上结构体空间 */
	if( xWantedSize > 0 ){
	  xWantedSize += heapSTRUCT_SIZE;
	  /* xWantedSize做字节对齐 */
	  if( ( xWantedSize & portBYTE_ALIGNMENT_MASK ) != 0 ){
		/* 字节对齐 */
		xWantedSize += (portBYTE_ALIGNMENT - (xWantedSize&portBYTE_ALIGNMENT_MASK));
	  }
	}
	/* 申请的内存大小合理,则进行内存分配 */
	if((xWantedSize > 0) && (xWantedSize < configADJUSTED_HEAP_SIZE)){
	  /* 从xStart开始查找大小满足的内存块 */
	  pxPreviousBlock = &xStart;
	  pxBlock = xStart.pxNextFreeBlock;
	  while((pxBlock->xBlockSize < xWantedSize)&&(pxBlock->pxNextFreeBlock != NULL)){
		pxPreviousBlock = pxBlock;
		pxBlock = pxBlock->pxNextFreeBlock;
	  }
	  /* 找到的可用内存块不能是链表尾xEnd */
	  if( pxBlock != &xEnd ){
		/* 返回申请到的内存首地址 */
		pvReturn=(void *)(((uint8_t *)pxPreviousBlock->pxNextFreeBlock)+heapSTRUCT_SIZE);
		/* 内存块已经被申请掉了,将该内存块从空闲内存块链表中移除  */
		pxPreviousBlock->pxNextFreeBlock = pxBlock->pxNextFreeBlock;
		/* 如果申请到的内存减去所需内存还大于heapMINIMUM_BLOCK_SIZE */
		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;
}
3.5 内存释放函数

内存释放函数主要是将需要释放的内存所在的内存块添加到空闲内存块链表中

void vPortFree( void *pv ){
  uint8_t *puc = ( uint8_t * ) pv;
  BlockLink_t *pxLink;
  if( pv != NULL ){
	/* 要释放的内存首地址,需要减去heap结构体的内存大小 */
	puc -= heapSTRUCT_SIZE;
	/* 防止编译器报错 */
	pxLink = ( void * ) puc;
	vTaskSuspendAll();
	{
	  /* 将内存块添加到空闲内存块链表中 */
	  prvInsertBlockIntoFreeList( ( ( BlockLink_t * ) pxLink ) );
	  xFreeBytesRemaining += pxLink->xBlockSize;
	  traceFREE( pv, pxLink->xBlockSize );
	}
	( void ) xTaskResumeAll();
  }
}

4. heap_3内存分配方法

4.1 分配方法介绍

heap_3是对标准C中的malloc()和free()的简单封装,并做了线程保护

heap_3内存分配的特点如下:

  • 需要编译器提供一个内存堆,编译器库要提供malloc()和free()函数
  • 通过修改启动文件中的Heap_Size来修改内存堆的大小(STM32中)
  • configTOTAL_HEAP_SZIE不起作用
  • 具有不确定性,并且会增加代码量
4.2 内存申请函数

内存申请函数

void *pvPortMalloc( size_t xWantedSize ){
  void *pvReturn;
  vTaskSuspendAll();//挂起任务调度器,提供线程保护
  {
	pvReturn = malloc( xWantedSize );//调用malloc申请内存
	traceMALLOC( pvReturn, xWantedSize );
  }
  ( void ) xTaskResumeAll();//恢复任务调度器
  #if( configUSE_MALLOC_FAILED_HOOK == 1 )
  {
	if( pvReturn == NULL ){
	  extern void vApplicationMallocFailedHook( void );
	  vApplicationMallocFailedHook();
	}
  }
  #endif
  return pvReturn;
}
4.3 内存释放函数

内存释放函数

void vPortFree( void *pv ){
  if( pv ){
	vTaskSuspendAll();//挂起任务调度器,提供线程保护
	{
	  free( pv );//调用free释放内存
	  traceFREE( pv, 0 );
	}
	( void ) xTaskResumeAll();//恢复任务调度器
  }
}

5. heap_4内存分配方法

5.1 分配方法介绍

heap_4提供了一个最优的匹配算法,与heap_2不同,heap_4会将内存碎片合并成一个大的可用内存块

heap_4内存分配的特点如下:

  • 可用在需要重复创建和删除任务、队列、信号量等的应用中
  • 不会像heap_2那样产生严重的内存碎片
  • 具有不确定性,但比标准C中的malloc()和free()效率高

在这里插入图片描述

5.2 内存堆初始化函数

内存堆初始化函数

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;
  /* xStars为空闲内存块链表头 */
  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);
}

完成初始化后的内存堆

在这里插入图片描述

5.3 内存块插入函数

内存块插入函数用来将某个内存块插入到空闲内存块链表中,其源码如下

static void prvInsertBlockIntoFreeList( BlockLink_t *pxBlockToInsert ){
  BlockLink_t *pxIterator;
  uint8_t *puc;
  /* 遍历空闲内存块链表,查找内存块插入点 */
  for(pxIterator=&xStart;pxIterator->pxNextFreeBlock<pxBlockToInsert;pxIterator=pxIterator->pxNextFreeBlock){
	/* 不做任何处理 */
  }
  /* 若插入的内存块可以和前一个内存块合并的话就合并两个内存块 */
  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();
  }
}
5.4 内存申请函数

内存申请函数

void *pvPortMalloc( size_t xWantedSize ){
  BlockLink_t *pxBlock, *pxPreviousBlock, *pxNewBlockLink;
  void *pvReturn = NULL;
  vTaskSuspendAll();
  {
	/* 若第一次调用,则需初始化内存堆 */
	if( pxEnd == NULL ){
	  prvHeapInit();
	}
	else{
	  mtCOVERAGE_TEST_MARKER();
	}
	/* 需要申请的内存块大小的最高位不能为1,最高位是用来记录内存块有没有被使用 */
	if( ( xWantedSize & xBlockAllocatedBit ) == 0 ){
	  /*  实际申请的内存数需要加上结构体的大小 */
	  if( xWantedSize > 0 ){
		xWantedSize += xHeapStructSize;
		/* xWantedSize做字节对齐 */
		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();
	  }
	  
	  if((xWantedSize > 0) && (xWantedSize <= xFreeBytesRemaining)){
		/* 从xStart开始,查找大小满足所需内存数的内存块 */
		pxPreviousBlock = &xStart;
		pxBlock = xStart.pxNextFreeBlock;
		while((pxBlock->xBlockSize < xWantedSize)&&(pxBlock->pxNextFreeBlock != NULL)){
		  pxPreviousBlock = pxBlock;
		  pxBlock = pxBlock->pxNextFreeBlock;
		}
		/* 如果找到的内存块是pxEnd的话就表示没有内存可以分配 */
		if( pxBlock != pxEnd ){
		  /* 找到内存块后将内存首地址保存到pvReturn中 */
		  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 );
		  }
		  else{
			mtCOVERAGE_TEST_MARKER();
		  }
		  xFreeBytesRemaining -= pxBlock->xBlockSize;//更新内存堆剩余内存大小
		  if( xFreeBytesRemaining < xMinimumEverFreeBytesRemaining ){
			xMinimumEverFreeBytesRemaining = xFreeBytesRemaining;
		  }
		  else{
			mtCOVERAGE_TEST_MARKER();
		  }
		  /* 内存块申请成功,标记此内存块已经被使用 */
		  pxBlock->xBlockSize |= xBlockAllocatedBit;
		  pxBlock->pxNextFreeBlock = NULL;
		}
		else{
		  mtCOVERAGE_TEST_MARKER();
		}
	  }
	  else{
		mtCOVERAGE_TEST_MARKER();
	  }
	}
	else{
	  mtCOVERAGE_TEST_MARKER();
	}
	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;
}
5.5 内存释放函数

内存释放函数

void vPortFree( void *pv ){
  uint8_t *puc = ( uint8_t * ) pv;
  BlockLink_t *pxLink;
  if( pv != NULL ){
	/* 获取内存块的BlockLink_t结构体 */
	puc -= xHeapStructSize;
	/* 防止编译器报错 */
	pxLink = ( void * ) puc;
	/* 确认要释放的内存块是被使用了的 */
	configASSERT( ( pxLink->xBlockSize & xBlockAllocatedBit ) != 0 );
	configASSERT( pxLink->pxNextFreeBlock == NULL );
	if( ( pxLink->xBlockSize & xBlockAllocatedBit ) != 0 ){
	  if( pxLink->pxNextFreeBlock == NULL ){
		/* 将xBlockSize最高位清零,重新标记次内存块没有使用 */
		pxLink->xBlockSize &= ~xBlockAllocatedBit;
		vTaskSuspendAll();
		{
		  /* 将内存块插入到空闲内存链表中 */
		  xFreeBytesRemaining += pxLink->xBlockSize;
		  traceFREE( pv, pxLink->xBlockSize );
		  prvInsertBlockIntoFreeList( ( ( BlockLink_t * ) pxLink ) );
		}
		( void ) xTaskResumeAll();
	  }
	  else{
		mtCOVERAGE_TEST_MARKER();
	  }
    }
    else{
	  mtCOVERAGE_TEST_MARKER();
    }
  }
}

6. heap_5内存分配方法

6.1 分配方法介绍

heap_5使用与heap_4相同的合并算法,内存管理基本相同。但是heap_5允许内存堆跨越多个不连续的内存段。比如外接了SRAM的STM32,如果使用heap_4的话就只能在内部RAM和外部SRAM中二选一,若使用heap_5则两个都可以一起作为内存堆来使用

使用heap_5的话,需要在调用API函数之前先调用函数 vPortDefineHeapRegions() 来对内存堆做初始化处理,在内存堆初始化完成之前禁止调用内存申请函数,其函数原型如下:

void vPortDefineHeapRegions( (const HeapRegion_t *) xHeapRegions)
//其参数是一个HeapRegion_t类型的数组
typedef struct HeapRegion
{
	uint8_t *pucStartAddress;	//内存块的起始地址
	size_t xSizeInBytes;		//内存段大小
}HeapRegion_t;

以STM32F103为例,现有两个内存段:内部SRAM和外部SRAM,起始地址分别为:0x20000000、0x68000000,大小分别为:64KB,1MB,那么数组就如下:

HeapRegion_t xHeapRegions[] = 
{
	{(uint8_t *)0x20000000UL,0x10000},	//内部SRAM内存
	{(uint8_t *)0x68000000UL,0x100000},	//外部SRAM内存
	{NULL,0}							//必须添加的数组结尾
}

heap_5的内存申请与释放函数和heap_4的基本一样,可参考heap_4的内存申请与释放函数

关注我的公众号,在公众号里发如下消息,即可获取相应的工程源代码:

FreeRTOS内存管理实例

在这里插入图片描述

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

FreeRTOS系列|内存管理一 的相关文章

  • Spring4.3.0 Junit4.11 initializationError(org.junit.runner.manipulation.Filter)

    Spring4 3 0 Junit4 11 initializationError org junit runner manipulation Filter 昨天手欠 xff0c 在项目中把Spring3 2 14版本升级到4 3 0版本
  • zookeeper入门(一)——ZooKeeper伪集群安装

    zookeeper入门 xff08 一 xff09 ZooKeeper伪集群安装 在进行本篇文章之前 xff0c 先请大家了解一下zookeeper xff08 后面的文章为了省事有可能直接使用zk缩写来替代 xff09 xff0c 关于z
  • zookeeper入门(二)——zk客户端脚本使用

    zookeeper入门 xff08 二 xff09 zk客户端脚本使用 在上一篇文章zookeeper入门 xff08 一 xff09 ZooKeeper伪集群安装我们讲了在单机进行zk伪集群安装 xff0c 本篇文章我们来讲一下zk提供的
  • 事务基础知识

    数据库事务 数据库事务定义 xff0c 满足4个特性 xff1a 原子性 xff08 Atomic xff09 一致性 xff08 Consistency xff09 隔离性 xff08 Isolation xff09 和持久性 xff08
  • MySQL事务隔离级别

    1 MySQL所支持的事务隔离级别 MySQL所支持的事务隔离级别 xff1a READ UNCOMMITTED READ COMMITTED REPEATABLE READ SERIALIZABLE 其中 REPEATABLE READ是
  • Thrift第一个示例

    第一步 xff1a 引入thrift依赖包 compile span class hljs keyword group span span class hljs string 39 org apache thrift 39 span nam
  • FreeRTOS系列|计数信号量

    计数信号量 1 计数信号量简介 计数型信号量有以下两种典型用法 事件计数 xff1a 每次事件发生 xff0c 事件处理函数将释放信号量 xff08 信号量计数值加1 xff09 xff0c 其他处理任务会获取信号量 xff08 信号量计数
  • Redis学习——01.redis安装

    下载 tar xzvf redis span class hljs number 3 2 span span class hljs number 10 span span class hljs preprocessor tar span s
  • IDEA常用设置

    显示主题 建议使用Darcula Appearance gt Theme 编辑器字体 建议使用Courier New或者Consolas Editor gt Font gt Font 打开自动编译 Compiler gt Build pro
  • Windows下执行Linux命令

    常用的工具 Cygwin xff08 http www cygwin com xff09 Cygwin是一个在windows平台上运行的类UNIX模拟环境 xff0c 详细参见百度百科 xff1a https baike baidu com
  • Linux网络编程 - 多线程服务器端的实现(1)

    引言 本来 xff0c 线程在 Windows 中的应用比在 Linux 平台中的应用更广泛 但 Web 服务的发展迫使 UNIX 系列的操作系统开始重视线程 由于 Web 服务器端协议本身具有的特点 xff0c 经常需要同时向多个客户端提
  • 访问带有用户名、密码保护的 URL

    一 URL xff0c 统一资源定位器 指向互联网上的 资源 xff0c 可协议名 主机 端口和资源组成 如 http username password 64 host 8080 directory file query ref Comp
  • 【RT-Thread】STM32F1片内Flash实现Bootloader

    目录 前言1 开发环境搭建2 Bootloader制作3 APP程序制作4 OTA固件打包5 Ymodem升级小结 前言 RT Thread官网对于Bootloader的实现方案有非常详细的描述 xff0c 目前支持F1 F4 L4系列单片
  • SDVOE和传统矩阵的区别

    SDVOE最显著的特点 xff1a 分辨率高 xff0c 最高支持4KP60 4 4 4 图像质量好 xff0c 完全可以达到无压缩效果延时小 xff0c Genlock模式下4K30延时只有不到0 1ms xff0c 链路上嵌入千兆网络
  • GD32的DMA配置

    参考 GD32F4xx 用户手册 DMA 控制器由 4 部分组成 xff1a AHB 从接口配置 DMA xff1b 两个 AHB 主接口进行数据传输 xff1b 两个仲裁器进行 DMA 请求的优先级管理 xff1b 数据处理和计数 DMA
  • nuttx杂记

    1 设置自启动应用 修改deconfig文件下的 CONFIG INIT ENTRYPOINT 参数即可 2 消息队列使用 以下是Nuttx系统中使用queue create函数创建队列的示例代码 xff1a include lt stdi
  • linux下使用jlink 调试 stm32的破事

    安装libusb sudo apt get install libusb 安装readline wget c ftp ftp gnu org gnu readline readline 6 2 tar gz tar zxvf readlin
  • FreeRTOS系列|软件定时器

    软件定时器 MCU一般都自带定时器 xff0c 属于硬件定时器 xff0c 但是不同的MCU其硬件定时器数量不同 xff0c 有时需要考虑成本的问题 在硬件定时器不够用的时候 xff0c FreeRTOS也提供了定时器功能 xff0c 不过
  • 视频芯片选择

    常用的视频芯片记录 HDMI TI ITE Explore Silicon image ADI semtech https www semtech com Realtek MACRO http www mitinc co kr module
  • 眼图里的那些破事

    1 眼图基本概念 1 1 眼图的形成原理 眼图是一系列数字信号在示波器上累积而显示的图形 xff0c 它包含了丰富的信息 xff0c 从眼图上可以观察出码间串扰和噪声的影响 xff0c 体现了数字信号整体的特征 xff0c 从而估计系统优劣

随机推荐

  • IIC的地址

    7位寻址 在7位寻址过程中 xff0c 从机地址在启动信号后的第一个字节开始传输 xff0c 该字节的前7位为从机地址 xff0c 第8位为读写位 xff0c 其中0表示写 xff0c 1表示读 图1 xff1a 7位寻址 I2C总线规范规
  • ODR, BSRR, BRR的差别

    ODR寄存器可读可写 xff1a 既能控制管脚为高电平 xff0c 也能控制管脚为低电平 管脚对于位写1 gpio 管脚为高电平 xff0c 写 0 为低电平 BSRR 只写寄存器 xff1a color 61 Red 既能控制管脚为高电平
  • ACAP究竟是什么

    Xilinx推出Versal系列 xff0c 号称业界首款ACAP xff0c 自适应计算加速平台 ACAP不仅是一个新的处理器 xff0c 而且是新的产品类型 作为率先推出ACAP这样类型产品的公司 xff0c 这也是赛灵思的核心竞争力所
  • ISE 14.7 调试错误笔记

    1 ERROR Pack 2530 The dual data rate register 34 U sys ctl ODDR2 inst 2 34 failed to join an OLOGIC component as require
  • HDMI 4K分辨率 时序

    参考 HDMI1 4标准 High Definition Multimedia Interface Specification 这份文件放在百度网盘共享了 xff0c 上传到文档平台会被封禁 xff0c 如果侵权 xff0c 麻烦联系我删除
  • 深度学习CPU,GPU,NPU,TPU以及其计算能力单位

    处理器运算能力单位 TOPS是Tera Operations Per Second的缩写 xff0c 1TOPS代表处理器每秒钟可进行一万亿次 xff08 10 12 xff09 操作 与此对应的还有GOPS xff08 Giga Oper
  • SSD数据集增强方法

    coding utf 8 import numpy as np import random import cv2 import glob import os import xml etree cElementTree as ET def r
  • 目标检测图像增强

    https blog csdn net wei guo xd article details 74199729 常用的图像扩充方式有 xff1a 水平翻转 xff0c 裁剪 xff0c 视角变换 xff0c jpeg压缩 xff0c 尺度变
  • FreeRTOS系列|低功耗管理

    低功耗管理 很多应用场合对于空耗的要求很严格 xff0c 比如可穿戴低功耗产品 物联网低功耗产品等 一般MCU都有相应的低功耗模式 xff0c 裸机开发时可以使用MCU的低功耗模式 FreeRTOS也提供了一个叫Tickless的低功耗模式
  • PELCO-D

    https blog csdn net subfate article details 36644419 在搞visca的同时顺便也搞了pelco 这里再做个笔记 pelco xff0c 中文翻译为 派尔高 xff0c 在行文和写代码过程
  • 图像去模糊算法 deblur

    图像去模糊算法 循序渐进 附完整代码 https www cnblogs com cpuimage p 9735150 html xff08 后面要对比smartdeblur xff0c deblur gan xff09 关于图像模糊算法的
  • 点云数据文件常用格式

    点云数据文件常用格式 文件类型汇总 OFF Object File FormatPLY Polygon File Format also known as the Stanford Triangle FormatPTS Laser scan
  • deeplab介绍

    论文 Encoder Decoder with Atrous Separable Convolution for Semantic Image Segmentation 链接 https www paperweekly site paper
  • 皱纹检测Wrinkle-detection

    基于图像处理的皱纹检测算法 https github com bulingda Wrinkles detection blob master Wrinkle py 基于RCNN 毛孔检测 https github com jack16888
  • VINS slam , imu fusion

    VINS 基本介绍 VINS Mono 和 VINS Mobile 是香港科技大学沈劭劼老师开源的单目视觉惯导 SLAM 方案 2017年发表于 IEEE Transactions on Robotics 另外 xff0c VINS 的最新
  • VCS-Verdi ubuntu 安装

    前言 金鱼博主今天又花了大半天重装VCS 43 Verdi xff0c 现在记录一下 xff0c 以备下回重装 顺带一提 xff0c 我的安装环境是虚拟机的Ubuntu 16 04 参考 1 安装流程参考自 xff1a https blog
  • Opengl简介

    OpenGL xff08 英语 xff1a Open Graphics Library xff0c 译名 xff1a 开放图形库或者 开放式图形库 xff09 是用于渲染2D 3D矢量图形的跨语言 跨平台的应用程序编程接口 xff08 AP
  • PCIE 协议分析工具

    推荐两个实用的PCIe工具软件 Felix 电子技术应用 AET 中国科技核心期刊 最丰富的电子设计资源平台
  • PCIe扫盲系列博文连载目录篇

    PCIe扫盲系列博文连载目录篇 xff08 第一阶段 xff09 Felix 电子技术应用 AET 中国科技核心期刊 最丰富的电子设计资源平台 chinaaet com 1 前言篇 xff1a PCIe扫盲 PCIe简介 xff1a htt
  • FreeRTOS系列|内存管理一

    内存管理一 内存管理是一个系统基本组成部分 xff0c FreeRTOS中大量使用了内存管理 xff0c 比如创建任务 信号量 队列等会自动从堆中申请内存 用户应用层代码也可以使用FreeRTOS提供的内存管理函数来申请和释放内存 1 内存