物联网操作系统学习笔记——内存管理

FreeRTOS内存管理

STM32编译过程及程序组成

物联网操作系统学习笔记——内存管理_第1张图片
source code(源代码)的c文件和汇编S文件通过Keil中的armcc/armasm生成.o文件(Object code),然后再通过armlink工具生成.axf文件(镜像文件,可通过ST-link进行烧录)再通过fromelf生成.bin/.hex文件
程序的组成、存储与运行
物联网操作系统学习笔记——内存管理_第2张图片
左边是C程序组成,右边是STM32程序组成
text:是的代码段:RAM.以上是数据段:ROM

程序存储
物联网操作系统学习笔记——内存管理_第3张图片
生成反汇编指令 fromelf --text -c --output LedTask\LedTask.text LedTask\LedTask.axf

物联网操作系统学习笔记——内存管理_第4张图片
MDK生成的主要文件分析
map文件分析
物联网操作系统学习笔记——内存管理_第5张图片
SCT文件分析
物联网操作系统学习笔记——内存管理_第6张图片
hex文件分析
物联网操作系统学习笔记——内存管理_第7张图片
bin文件分析
生成bin文件 fromelf --bin --output LedTask\LedTask.bin LedTask\LedTask.axf
物联网操作系统学习笔记——内存管理_第8张图片

内存管理概念及其应用

静态内存和动态内存的区别
静态内存:
静态内存是指在程序开始运行时由编译器分配的内存,它的分配是在程序开始编译时完成的,不占用CPU资源。
程序中的各种变量,在编译时系统已经为其分配了所需的内存空间,当该变量在作用域内使用完毕时,系统会
自动释放所占用的内存空间。
变量的分配与释放,都无须程序员自行考虑。
物联网操作系统学习笔记——内存管理_第9张图片
动态内存:
动态内存分配是按输入信息的大小分配所需要的内存单元,他的特点是按需分配,内存分配在堆区。
用户无法确定空间大小,或者空间太大,栈上无法分配时,会采用动态内存分配。
在这里插入图片描述
区别
时间不同:
静态分配发生在程序编译和连接的时候。动态分配则发生在程序调入和执行的时候。
空间不同:
堆都是动态分配的,没有静态分配的堆。栈有2种分配方式:静态分配和动态分配。静态分配是编译器完成的,比如局部变量的分配。动态分配由函数malloc进行分配。不过栈的动态分配和堆不同,他的动态分配是由编译器进行释放,无需我们手工实现。

C标准库-动态内存分配接口

#include 
void *malloc(size_t size);
void free(void *ptr);
void *calloc(size_t nmemb, size_t size);
void *realloc(void *ptr, size_t size);

C标准库动态内存的缺陷
1、这些函数在小型嵌入式系统中并不总是可用的,小型嵌入式设备中的 RAM 不足。
2、它们的实现可能非常的大,占据了相当大的一块代码空间。
他们几乎都不是安全的。
3、它们并不是确定的,每次调用这些函数执行的时间可能都不一样。
4、它们有可能产生碎片。
5、这两个函数会使得链接器配置得复杂。
6、如果允许堆空间的生长方向覆盖其他变量占据的内存,它们会成为 debug 的灾难。

STM32系统内存分配需求
没有虚拟内存机制,需要用户亲自管理。
对内存的分配时间要求更为苛刻,分配内存的时间必须是确定的
RAM比较小,需要比较好的内存分配算法解决内存碎片问题

物联网操作系统学习笔记——内存管理_第10张图片
FreeRTOS动态内存分配
物联网操作系统学习笔记——内存管理_第11张图片
heap_1:只能分配,不能释放

物联网操作系统学习笔记——内存管理_第12张图片
heap_2:支持分配也支持释放,但没有内存碎片管理
物联网操作系统学习笔记——内存管理_第13张图片
heap_3:封装C库的malloc和free。添加了线程保护
heap_4:有分配有释放有管理。
物联网操作系统学习笔记——内存管理_第14张图片
heap_5:可以扩展外部ram

内存管理函数应用

实验需求
物联网操作系统学习笔记——内存管理_第15张图片
API
物联网操作系统学习笔记——内存管理_第16张图片

void Delay_Task(void const * argument)
{
  /* USER CODE BEGIN Delay_Task */
	EventBits_t KeyEventBits;
	uint8_t *HeapBuf = NULL;
  /* Infinite loop */
  for(;;)
  {
	  /*
		1、修改事件标志组等待状态为 触发后清除标志
		2、检测按键,处理相关功能	  
	  */
	  KeyEventBits = xEventGroupWaitBits(KeyEventGroup,
								KEY3_EVENT_BIT|KEY4_EVENT_BIT|KEY5_EVENT_BIT|KEY6_EVENT_BIT,
								pdTRUE,
								pdFALSE,
								portMAX_DELAY);
	  printf("Key is Down Key Event Bit is %x\r\n",KeyEventBits);
	  switch(KeyEventBits){
		  case KEY3_EVENT_BIT:
			  if(HeapBuf == NULL){
				HeapBuf = pvPortMalloc(100);
			  }
			  else{
				printf("plese press K4 free!\r\n");
			  }				
			break;
		  case KEY4_EVENT_BIT:
			  if(HeapBuf != NULL){
				vPortFree(HeapBuf);
				HeapBuf = NULL;
			  }
			  else{
				printf("plese press K3 Malloc!\r\n");
			  }		  
			break;		 
		  case KEY5_EVENT_BIT:
			  printf("Heap Free Size is%d\r\n",xPortGetFreeHeapSize());
			break;	  
		  default:
			  break;	  
	  }
		osDelay(10);
  }
  /* USER CODE END Delay_Task */
}

内存管理需求

物联网操作系统学习笔记——内存管理_第17张图片
heap_4内存管理方案
物联网操作系统学习笔记——内存管理_第18张图片

Heap_4内存管理数据结构
物联网操作系统学习笔记——内存管理_第19张图片

heap_4内存块初始化
物联网操作系统学习笔记——内存管理_第20张图片

/*
	内存堆初始化
	1、宏定义
		#if portBYTE_ALIGNMENT == 8					对齐字节数
		#define portBYTE_ALIGNMENT_MASK ( 0x0007 )	对齐掩码
		#endif
	2、内存块链表节点长度
		这里保证可被8整除,内存操作效率高
	static const size_t xHeapStructSize	= ( sizeof( BlockLink_t ) + ( ( size_t ) ( portBYTE_ALIGNMENT - 1 ) ) ) & ~( ( size_t ) portBYTE_ALIGNMENT_MASK );
*/
static void prvHeapInit( void )
{
	BlockLink_t *pxFirstFreeBlock;
	uint8_t *pucAlignedHeap;
	size_t uxAddress;
	size_t xTotalHeapSize = configTOTAL_HEAP_SIZE;

	/* 获取内存堆数组首地址 */
	uxAddress = ( size_t ) ucHeap;
	//当addr 后三位不等于0,就需要字节对齐
	//不能被8整除,8字节对齐,就是能够被8整除
	if( ( uxAddress & portBYTE_ALIGNMENT_MASK ) != 0 )
	{
		//uxAddress = uxAddress +7???
		uxAddress += ( portBYTE_ALIGNMENT - 1 );
		//把后三位变成0,保证在8字节对齐上
		uxAddress &= ~( ( size_t ) portBYTE_ALIGNMENT_MASK );
		//获取了总可用长度,有低地址到高地址增长
		xTotalHeapSize -= uxAddress - ( size_t ) ucHeap;
	}
	//对齐后的首地址
	pucAlignedHeap = ( uint8_t * ) uxAddress;

	/* 
		初始化了头结点xStart 
		下一个可用空闲块为对齐后的首地址
		头结点的内存大小为0
		解析:
			xStart 分配在全局内存中
			不用于存储块记录,只用链表操作查找用
	*/
	xStart.pxNextFreeBlock = ( void * ) pucAlignedHeap;
	xStart.xBlockSize = ( size_t ) 0;

	/* 
		初始化尾节点
		1、获取整个内存堆尾地址
		2、减去一个链表节点长度
		3、再去保证地址在8字节对齐上
		4、把尾节点进行赋值
		5、尾节点下一个指向NULL

	*/
	uxAddress = ( ( size_t ) pucAlignedHeap ) + xTotalHeapSize;
	uxAddress -= xHeapStructSize;
	uxAddress &= ~( ( size_t ) portBYTE_ALIGNMENT_MASK );
	pxEnd = ( void * ) uxAddress;
	pxEnd->xBlockSize = 0;
	pxEnd->pxNextFreeBlock = NULL;

	/* 
		初始化内存堆的第一个空闲块
		1、首地址为内存堆可用空闲首地址
		2、减去尾地址,获取到可用空间大小
		3、下个指向尾节点
	*/
	pxFirstFreeBlock = ( void * ) pucAlignedHeap;
	pxFirstFreeBlock->xBlockSize = uxAddress - ( size_t ) pxFirstFreeBlock;
	pxFirstFreeBlock->pxNextFreeBlock = pxEnd;

	/* 更新剩余内存信息 */
	xMinimumEverFreeBytesRemaining = pxFirstFreeBlock->xBlockSize;
	xFreeBytesRemaining = pxFirstFreeBlock->xBlockSize;

	/*标志位置位,32Bit 最高位为1  主要用于判断内存块类型使用*/
	xBlockAllocatedBit = ( ( size_t ) 1 ) << ( ( sizeof( size_t ) * heapBITS_PER_BYTE ) - 1 );
}

heap_4内存块申请

物联网操作系统学习笔记——内存管理_第21张图片

/*
	内存块申请
	根据传入大小,返回内存块指针,无可用空间返回NULL
	1、全局变量
		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();
		}

		/*
			1、首地址最高位不能为1,因为用于内存块判断使用的

		*/
		if( ( xWantedSize & xBlockAllocatedBit ) == 0 )
		{
			/*  */
			if( xWantedSize > 0 )
			{
				//加上链表节点长度,这也就是解释了 我们申请了100字节,但是实际占用了112,为什么多出了12个字节
				xWantedSize += xHeapStructSize;

				/* 保证能被8整除 */
				if( ( xWantedSize & portBYTE_ALIGNMENT_MASK ) != 0x00 )
				{
					/* Byte alignment required. */
					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 ) )
			{
				/* 
					查找可用空闲块
					单向链表,先从头结点开始
					1、pxPreviousBlock 开始遍历
					
					遍历结束条件 大小满足或者没有空闲块了

				*/
				pxPreviousBlock = &xStart;
				pxBlock = xStart.pxNextFreeBlock;
				while( ( pxBlock->xBlockSize < xWantedSize ) && ( pxBlock->pxNextFreeBlock != NULL ) )
				{
					pxPreviousBlock = pxBlock;
					pxBlock = pxBlock->pxNextFreeBlock;
				}

				/* 是否有空闲块 */
				if( pxBlock != pxEnd )
				{
					/* 获取分配的内存首地址(块的首地址+节点长度) */
					pvReturn = ( void * ) ( ( ( uint8_t * ) pxPreviousBlock->pxNextFreeBlock ) + xHeapStructSize );

					/* 删除 已经分配的节点*/
					pxPreviousBlock->pxNextFreeBlock = pxBlock->pxNextFreeBlock;

					/* If the block is larger than required it can be split into
					two. */
					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();

	configASSERT( ( ( ( size_t ) pvReturn ) & ( size_t ) portBYTE_ALIGNMENT_MASK ) == 0 );
	return pvReturn;
}

Heap_4内存块插入

物联网操作系统学习笔记——内存管理_第22张图片

/*
	把内存块插入到空闲内存块中
*/
static void prvInsertBlockIntoFreeList( BlockLink_t *pxBlockToInsert )
{
	BlockLink_t *pxIterator;
	uint8_t *puc;

	/*  
		找到pxBlockToInsert位置
		pxIterator->pxNextFreeBlock > pxBlockToInsert 表示已经找到
		之后pxIterator地址在pxBlockToInsert实际物理地址之前		
	*/
	for( pxIterator = &xStart; pxIterator->pxNextFreeBlock < pxBlockToInsert; pxIterator = pxIterator->pxNextFreeBlock )
	{
	}

	/* 
		判断是否可以合并 (向上合并)
		1、内存块长度进行累加
		2、要插入的地址,变成合并后的地址
	*/
	puc = ( uint8_t * ) pxIterator;
	if( ( puc + pxIterator->xBlockSize ) == ( uint8_t * ) pxBlockToInsert )
	{
		pxIterator->xBlockSize += pxBlockToInsert->xBlockSize;
		pxBlockToInsert = pxIterator;
	}
	else
	{
		mtCOVERAGE_TEST_MARKER();
	}

	/* 
		判断是否可以合并 (向下合并)
		1、再判断是否为尾节点
		2、内存块长度进行累加
		3、把要合并的内存块从空闲链表中移除
	*/
	puc = ( uint8_t * ) pxBlockToInsert;
	if( ( puc + pxBlockToInsert->xBlockSize ) == ( uint8_t * ) pxIterator->pxNextFreeBlock )
	{
		if( pxIterator->pxNextFreeBlock != pxEnd )
		{
			/* Form one big block from the two blocks. */
			pxBlockToInsert->xBlockSize += pxIterator->pxNextFreeBlock->xBlockSize;
			pxBlockToInsert->pxNextFreeBlock = pxIterator->pxNextFreeBlock->pxNextFreeBlock;
		}
		else
		{
			pxBlockToInsert->pxNextFreeBlock = pxEnd;
		}
	}
	/*
		1、这里没有向下合并操作,直接插入就可以,连接到空闲链表中	
	*/
	else
	{
		pxBlockToInsert->pxNextFreeBlock = pxIterator->pxNextFreeBlock;
	}
	/* 
		1、这里没有向上合并操作,直接插入就可以,连接到空闲链表中	
	*/
	if( pxIterator != pxBlockToInsert )
	{
		pxIterator->pxNextFreeBlock = pxBlockToInsert;
	}
	else
	{
		mtCOVERAGE_TEST_MARKER();
	}
}

heap_4内存块释放

/*
	释放内存块
	参数:传入要释放的内存块地址
*/
void vPortFree( void *pv )
{
uint8_t *puc = ( uint8_t * ) pv;
BlockLink_t *pxLink;
	//判断内存块有效
	if( pv != NULL )
	{
		/*获取传入内存块的节点地址 */
		puc -= xHeapStructSize;
		pxLink = ( void * ) puc;
		//判断最高位为1
		if( ( pxLink->xBlockSize & xBlockAllocatedBit ) != 0 )
		{
			//下个节点为NULL
			if( pxLink->pxNextFreeBlock == NULL )
			{
				/*最高位清除置位为0 */
				pxLink->xBlockSize &= ~xBlockAllocatedBit;
				//挂起调度器
				vTaskSuspendAll();
				{
					/* 更新剩余空间大小 */
					xFreeBytesRemaining += pxLink->xBlockSize;
					//插入到空闲链表中去
					prvInsertBlockIntoFreeList( ( ( BlockLink_t * ) pxLink ) );
				}
				//恢复调度器
				( void ) xTaskResumeAll();
			}
			else
			{
				mtCOVERAGE_TEST_MARKER();
			}
		}
		else
		{
			mtCOVERAGE_TEST_MARKER();
		}
	}
}

你可能感兴趣的:(操作系统)