FreeRTOS内存管理
source code(源代码)的c文件和汇编S文件通过Keil中的armcc/armasm生成.o文件(Object code),然后再通过armlink工具生成.axf文件(镜像文件,可通过ST-link进行烧录)再通过fromelf生成.bin/.hex文件
程序的组成、存储与运行
左边是C程序组成,右边是STM32程序组成
text:是的代码段:RAM.以上是数据段:ROM
程序存储
生成反汇编指令 fromelf --text -c --output LedTask\LedTask.text LedTask\LedTask.axf
MDK生成的主要文件分析
map文件分析
SCT文件分析
hex文件分析
bin文件分析
生成bin文件 fromelf --bin --output LedTask\LedTask.bin LedTask\LedTask.axf
静态内存和动态内存的区别
静态内存:
静态内存是指在程序开始运行时由编译器分配的内存,它的分配是在程序开始编译时完成的,不占用CPU资源。
程序中的各种变量,在编译时系统已经为其分配了所需的内存空间,当该变量在作用域内使用完毕时,系统会
自动释放所占用的内存空间。
变量的分配与释放,都无须程序员自行考虑。
动态内存:
动态内存分配是按输入信息的大小分配所需要的内存单元,他的特点是按需分配,内存分配在堆区。
用户无法确定空间大小,或者空间太大,栈上无法分配时,会采用动态内存分配。
区别
时间不同:
静态分配发生在程序编译和连接的时候。动态分配则发生在程序调入和执行的时候。
空间不同:
堆都是动态分配的,没有静态分配的堆。栈有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比较小,需要比较好的内存分配算法解决内存碎片问题
FreeRTOS动态内存分配
heap_1:只能分配,不能释放
heap_2:支持分配也支持释放,但没有内存碎片管理
heap_3:封装C库的malloc和free。添加了线程保护
heap_4:有分配有释放有管理。
heap_5:可以扩展外部ram
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 */
}
/*
内存堆初始化
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内存块申请
/*
内存块申请
根据传入大小,返回内存块指针,无可用空间返回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内存块插入
/*
把内存块插入到空闲内存块中
*/
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();
}
}
}