FreeRTOS:4.内存管理

FreeRTOS内存管理

目录

  • FreeRTOS内存管理
    • 1. 为什么不直接使用C库函数的malloc和free函数
    • 2. FreeRTOS的五种内存管理方式
    • 3. heap4源码分析
      • 3.1 堆内存池
      • 3.2 内存块的链表数据结构
      • 3.3 堆的初始化
      • 3.4 堆的内存分配
      • 3.5 堆的内存释放
    • 4. 总结

1. 为什么不直接使用C库函数的malloc和free函数

在C语言的库函数中,有malloc、free等函数,但是在FreeRTOS中,它们不适用:

  • 不适合用在资源紧缺的嵌入式系统中
  • 这些函数的实现过于复杂、占据的代码空间太大
  • 并非线程安全的(thread-safe)
  • 运行有不确定性:每次调用这些函数时花费的时间可能都不相同
  • 内存碎片化
  • 使用不同的编译器时,需要进行复杂的配置
  • 有时候难以调试

2. FreeRTOS的五种内存管理方式

FreeRTOS中内存管理的接口函数为:pvPortMalloc、vPortFree,对应于C库的malloc、free。

在这里插入图片描述

  • heap_1:只分配,不回收;只实现了pvPortMalloc,没有实现vPortFree,因此不会产生碎片问题,分配时间确定
  • heap_2:采用最佳匹配算法,会产生大量内存碎片,没有对内存碎片进行合并,分配时间不定
  • heap_3:采用标准C库的malloc、free,由于并非线程安全,因此heap_3中先暂停rtos调度器,再进行分配,会产生内存碎片,时间不定
  • heap_4:是目前常用的堆管理方式,采用首次适应算法,能够合并相邻内存块,减少内存碎片,同样分配时间不定
  • heap_5:在heap_4的基础上,它可以管理多块、分隔开的内存。

3. heap4源码分析

目前heap_4的使用最为广泛,本文基于heap_4.c的源码进行进一步分析。

源码路径:Middlewares\Third_Party\FreeRTOS\Source\portable\MemMang\heap_4.c``

3.1 堆内存池

/* Allocate the memory for the heap. */
#if( configAPPLICATION_ALLOCATED_HEAP == 1 )/* The application writer has already defined the array used for the RTOSheap - probably so it can be placed in a special segment or address. */extern uint8_t ucHeap[ configTOTAL_HEAP_SIZE ];
#elsestatic uint8_t ucHeap[ configTOTAL_HEAP_SIZE ];
#endif /* configAPPLICATION_ALLOCATED_HEAP */

heap4采用的是线性数组结构,大小为configTOTAL_HEAP_SIZE个字节

如果定义了configAPPLICATION_ALLOCATED_HEAP宏,可以由用户层自行定义内存池的位置

3.2 内存块的链表数据结构

/* Define the linked list structure.  This is used to link free blocks in order
of their memory address. */
typedef struct A_BLOCK_LINK
{struct A_BLOCK_LINK *pxNextFreeBlock;	/*<< The next free block in the list. */size_t xBlockSize;						/*<< The size of the free block. */
} BlockLink_t;

通过BlockLink_t这个链表对空闲的内存块进行管理,这个结构体包括内存块的大小以及指向下一个内存块头的指针,并且由两个静态链表指针xStart、pxEnd来标识开头和结尾。除了xStart以外,其余内存控制块都是在内存池中,用指针的形式进行访问。也就是说,xStart是作为哨兵节点,xStart的pxNextFreeBlock指针就是第一个空闲块节点。

static BlockLink_t xStart, *pxEnd = NULL;

另外,heapMINIMUM_BLOCK_SIZE限制了内存块的最小值,当内存块小于heapMINIMUM_BLOCK_SIZE时,就不再维护这个内存碎片了。

3.3 堆的初始化

通过prvHeapInit函数完成堆的初始化

static void prvHeapInit( void )
{
BlockLink_t *pxFirstFreeBlock;
uint8_t *pucAlignedHeap;
size_t uxAddress;
size_t xTotalHeapSize = configTOTAL_HEAP_SIZE;/* Ensure the heap starts on a correctly aligned boundary. */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;// 初始化xStart/* xStart is used to hold a pointer to the first item in the list of freeblocks.  The void cast is used to prevent compiler warnings. */xStart.pxNextFreeBlock = ( void * ) pucAlignedHeap;xStart.xBlockSize = ( size_t ) 0;/* pxEnd is used to mark the end of the list of free blocks and is insertedat the end of the heap space. */// 初始化pxEnduxAddress = ( ( size_t ) pucAlignedHeap ) + xTotalHeapSize;uxAddress -= xHeapStructSize;uxAddress &= ~( ( size_t ) portBYTE_ALIGNMENT_MASK );pxEnd = ( void * ) uxAddress;pxEnd->xBlockSize = 0;pxEnd->pxNextFreeBlock = NULL;/* To start with there is a single free block that is sized to take up theentire heap space, minus the space taken by pxEnd. */pxFirstFreeBlock = ( void * ) pucAlignedHeap;pxFirstFreeBlock->xBlockSize = uxAddress - ( size_t ) pxFirstFreeBlock;pxFirstFreeBlock->pxNextFreeBlock = pxEnd;/* Only one block exists - and it covers the entire usable heap space. */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 );
}

该函数主要完成了内存块链表的初始化。初始化完成后,xStart.pxNextFreeBlock指向了内存池对齐后的首地址,pxEnd指针则指向了内存池末尾往前的xHeapStructSize个字节大小位置。

因此,除了内存对齐后减少的空间外,内存池末尾还留有xHeapStructSize个字节存放pxEnd指向的内存头,用于标识末尾。

/* The size of the structure placed at the beginning of each allocated memory
block must by correctly byte aligned. */
static const size_t xHeapStructSize	= ( sizeof( BlockLink_t ) + ( ( size_t ) ( portBYTE_ALIGNMENT - 1 ) ) ) & ~( ( size_t ) portBYTE_ALIGNMENT_MASK );#define portBYTE_ALIGNMENT			8
#define portBYTE_ALIGNMENT_MASK ( 0x0007 )

在stm32中,xHeapStructSize为8字节。

3.4 堆的内存分配

通过pvPortMalloc函数完成堆的内存分配

void *pvPortMalloc( size_t xWantedSize )
{
BlockLink_t *pxBlock, *pxPreviousBlock, *pxNewBlockLink;
void *pvReturn = NULL;vTaskSuspendAll(); // 进入临界区{/* 如果是第一次调用pvPortMalloc,那么就调用prvHeapInit来初始化堆内存池 */if( pxEnd == NULL ){prvHeapInit();}// 判断申请的内存大小是否超过上限if( ( xWantedSize & xBlockAllocatedBit ) == 0 ){/* The wanted size is increased so it can contain a BlockLink_tstructure in addition to the requested amount of bytes. */if( xWantedSize > 0 ){xWantedSize += xHeapStructSize; // 申请的内存要加上额外的内存头8字节/* Ensure that blocks are always aligned to the required numberof bytes. */if( ( xWantedSize & portBYTE_ALIGNMENT_MASK ) != 0x00 ){/* 内存对齐 */xWantedSize += ( portBYTE_ALIGNMENT - ( xWantedSize & portBYTE_ALIGNMENT_MASK ) );}}// 想要申请的内存比剩余的空闲内存小if( ( xWantedSize > 0 ) && ( xWantedSize <= xFreeBytesRemaining ) ){/* 遍历各个空闲内存块,找到第一个空闲内存块,它的大小比想要申请的内存大 */pxPreviousBlock = &xStart;pxBlock = xStart.pxNextFreeBlock;while( ( pxBlock->xBlockSize < xWantedSize ) && ( pxBlock->pxNextFreeBlock != NULL ) ){pxPreviousBlock = pxBlock;pxBlock = pxBlock->pxNextFreeBlock;}/* If the end marker was reached then a block of adequate sizewas	not found. */if( pxBlock != pxEnd ){/* Return the memory space pointed to - jumping over theBlockLink_t structure at its start. */pvReturn = ( void * ) ( ( ( uint8_t * ) pxPreviousBlock->pxNextFreeBlock ) + xHeapStructSize );/* 把 pxBlock 从空闲链表中移除 */pxPreviousBlock->pxNextFreeBlock = pxBlock->pxNextFreeBlock;/* 判断产生的内存碎片是否大于规定的最小内存碎片 */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;if( xFreeBytesRemaining < xMinimumEverFreeBytesRemaining ){xMinimumEverFreeBytesRemaining = xFreeBytesRemaining;}/* The block is being returned - it is allocated and ownedby the application and has no "next" block. */pxBlock->xBlockSize |= xBlockAllocatedBit;pxBlock->pxNextFreeBlock = NULL;}}}traceMALLOC( pvReturn, xWantedSize );}( void ) xTaskResumeAll(); // 退出临界区// 如果定义了分配内存的钩子函数并且分配失败,就去调用#if( configUSE_MALLOC_FAILED_HOOK == 1 ){if( pvReturn == NULL ){extern void vApplicationMallocFailedHook( void );vApplicationMallocFailedHook();}}#endifreturn pvReturn; // 返回分配的内存块(跳过了内存头部的)
}
/*-----------------------------------------------------------*/
  1. 当首次调用pvPortMalloc,会调用prvHeapInit对内存链表进行初始化
  2. 判断申请的内存大小是否超过上限
  3. 遍历空闲内存块链表,找到第一个内存块大小是足够分配的
  4. 将该内存块从链表中剔除,如果剩余空间大小超过了最小内存块大小,那么就创建新的内存块,并插回内存块链表
  5. 如果分配失败,并且用户注册了对应的回调函数,就去调用

注意到:动态内存对于内存的消耗是大于应用层申请的内存大小,原因在于内存对齐和内存块头部的开销导致。

堆的内存分配和内存释放,都涉及到将内存块插回空闲链表中,heap4_c实现了空闲内存可合并,一定程度上解决内存碎片问题,就在prvInsertBlockIntoFreeList函数中体现。

prvInsertBlockIntoFreeList函数如下:

static void prvInsertBlockIntoFreeList( BlockLink_t *pxBlockToInsert )
{BlockLink_t *pxIterator;uint8_t *puc;/* 找到一个空闲块,这个块的地址比插入的块地址低,下一个块的地址比插入的块地址高 */for( pxIterator = &xStart; pxIterator->pxNextFreeBlock < pxBlockToInsert; pxIterator = pxIterator->pxNextFreeBlock ){/* Nothing to do here, just iterate to the right position. */}/* 找到插入位置后, 判断这个位置的尾地址能否和插入块衔接起来,如果可以就合并 */puc = ( uint8_t * ) pxIterator;if( ( puc + pxIterator->xBlockSize ) == ( uint8_t * ) pxBlockToInsert ){pxIterator->xBlockSize += pxBlockToInsert->xBlockSize;pxBlockToInsert = pxIterator;}/* 判断插入块能否和 插入位置的下一个内存块衔接起来,如果可以就合并 */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;}}else{pxBlockToInsert->pxNextFreeBlock = pxIterator->pxNextFreeBlock;}/* If the block being inserted plugged a gab, so was merged with the blockbefore and the block after, then it's pxNextFreeBlock pointer will havealready been set, and should not be set here as that would make it pointto itself. */if( pxIterator != pxBlockToInsert ){pxIterator->pxNextFreeBlock = pxBlockToInsert;}}
  1. 首先在空闲内存链表中,找到插入位置,即这个块的地址比插入块地址低,这个块的下一个块地址比插入块地址高
  2. 判断插入块能否和左边的空闲内存块以及右边的空闲内存块合并,也就是边界相等,如果可以就合并成一个大的内存块

所以说:heap4_c所谓的空闲内存可合并,只是合并相邻的内存碎片,这是基于内存池的线性连续而设计的。

因此,为了尽可能的减少内存碎片,提升内存合并的作用,尽可能把上电后不释放的动态内存在初始化阶段申请(比如说动态分配的任务,包括TCB和任务栈),然后对于重复申请释放的动态内存,在初始化阶段结束后再分配和使用。也就是说,堆的前半部分内存都用于不释放的动态内存,然后后半部分就用来一些频繁申请释放的动态内存。

3.5 堆的内存释放

通过vPortFree函数完成堆的内存释放

vPortFree函数如下:

void vPortFree( void *pv )
{
uint8_t *puc = ( uint8_t * ) pv;
BlockLink_t *pxLink;if( pv != NULL ){/* The memory being freed will have an BlockLink_t structure immediatelybefore it. */puc -= xHeapStructSize;/* This casting is to keep the compiler from issuing warnings. */pxLink = ( void * ) puc;/* Check the block is actually allocated. */configASSERT( ( pxLink->xBlockSize & xBlockAllocatedBit ) != 0 );configASSERT( pxLink->pxNextFreeBlock == NULL );if( ( pxLink->xBlockSize & xBlockAllocatedBit ) != 0 ){if( pxLink->pxNextFreeBlock == NULL ){/* The block is being returned to the heap - it is no longerallocated. */pxLink->xBlockSize &= ~xBlockAllocatedBit;vTaskSuspendAll();{/* Add this block to the list of free blocks. */xFreeBytesRemaining += pxLink->xBlockSize;traceFREE( pv, pxLink->xBlockSize );prvInsertBlockIntoFreeList( ( ( BlockLink_t * ) pxLink ) );}( void ) xTaskResumeAll();}}}
}
/*-----------------------------------------------------------*/

释放内存比较简单,就是把申请释放的内存地址前移xHeapStructSize找到内存头部的位置,

然后进入临界区,把这个内存块插入到空闲链表当中,并增大xFreeBytesRemaining变量(记录当前空闲内存大小的变量)的大小,最后退出临界区。

4. 总结

  • heap_4的堆内存池是建立在一片线性连续的内存上的(全局数组)。
  • 在申请和释放内存时,有查询空闲块链表的操作,其最坏的时间复杂度是On,时间是不确定的。
  • 另外由于需要内存头部来维护空闲链表以及内存对齐,这导致了实际可分配的动态内存小于分配的这个线性连续的内存。
  • heap4只能合并相邻的内存碎片,并不能彻底解决内存碎片问题。

参考学习:

freeRTOS动态内存heap4源码分析_freertos heap4-CSDN博客

【FreeRTOS】FreeRTOS内存管理的五种方式-CSDN博客

FreeRTOS 内存管理策略_freertos查看内存碎片功能-CSDN博客

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/351695.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Cisco Packet Tracer实验(四)

生成树协议&#xff08;Spanning Tree Protocol&#xff09; 交换机在目的地址未知或接收到广播帧时是要进行广播的。如果交换机之间存在回路/环路&#xff0c;那么就会产生广播循环风暴&#xff0c;从而严重影响网络性能。 而交换机中运行的STP协议能避免交换机之间发生广播…

【云原生| K8S系列】Kubernetes Daemonset,全面指南

Kubernetes中的DaemonSet是什么? Kubernetes是一个分布式系统&#xff0c;Kubernetes平台管理员应该有一些功能可以在所有节点上运行特定于平台的应用程序。例如&#xff0c;在所有Kubernetes节点上运行日志代理。 这就是Daemonset发挥作用的地方。 Daemonset是一个原生的K…

【Mybatis-Plus】根据自定义注解实现自动加解密

背景 我们把数据存到数据库的时候&#xff0c;有些敏感字段是需要加密的&#xff0c;从数据库查出来再进行解密。如果存在多张表或者多个地方需要对部分字段进行加解密操作&#xff0c;每个地方都手写一次加解密的动作&#xff0c;显然不是最好的选择。如果我们使用的是Mybati…

每一个男人都曾有一个机器人的梦想

每一个男人都曾有一个机器人的梦想 我也有 每一个男人都曾有一个机器人的梦想。对于我来说&#xff0c;这个梦想始于童年时代&#xff0c;那时变形金刚风靡一时&#xff0c;几乎所有80后的孩子都为之疯狂。我是80后中的一员&#xff0c;那时候的科技还远没有如今这般发达&#…

【初阶数据结构】深入解析单链表:探索底层逻辑(无头单向非循环链表)

&#x1f525;引言 本篇将深入解析单链表:探索底层逻辑&#xff0c;理解底层是如何实现并了解该接口实现的优缺点&#xff0c;以便于我们在编写程序灵活地使用该数据结构。 &#x1f308;个人主页&#xff1a;是店小二呀 &#x1f308;C语言笔记专栏&#xff1a;C语言笔记 &…

dp练习题

先来一个简单dp练习 class Solution { public:int rob(vector<int>& nums) {int n nums.size();vector<int> a(n 1);int ans nums[0]; a[0] nums[0];if (n 1) return ans;a[1] max(nums[0], nums[1]);ans max(ans, a[1]);if (n 2) return ans;for (i…

[DDR4] DDR 简史

依公知及经验整理&#xff0c;原创保护&#xff0c;禁止转载。 专栏 《深入理解DDR4》 存和硬盘&#xff0c;这对电脑的左膀右臂&#xff0c;共同扛起了存储的重任。内存以其超凡的存取速度闻名&#xff0c;但一旦断电&#xff0c;内存中的数据也会消失。它就像我们的工作桌面&…

【工作】计算机行业相关的十六类工作简介

本文简单介绍了计算机行业相关的工作类别&#xff0c;共16种&#xff0c;包括常见招聘要求与平均工资。平均工资信息来源&#xff1a;米国企业点评职场社区glassdoor&#xff08;https://www.glassdoor.com/index.htm&#xff09; &#xff08;一&#xff09;软件工程师 软件…

Element-UI - 解决el-table中图片悬浮被遮挡问题

在开发中&#xff0c;发现element-ui在el-table中添加图片悬浮显示时&#xff0c;会被单元格遮挡的问题。通过查询得到的解决办法&#xff0c;大多是修改.el-table类中相关样式属性&#xff0c;但经过验证发现会影响到其他正常功能的使用。对于此问题解决其实也并不难&#xff…

《人生海海》读后感

麦家是写谍战的高手&#xff0c;《暗算》《风声》等等作品被搬上荧屏后&#xff0c;掀起了一阵一阵的收视狂潮。麦家声名远扬我自然是知道的&#xff0c;然而我对谍战似乎总是提不起兴趣&#xff0c;因此从来没有拜读过他的作品。这几天无聊时在网上找找看看&#xff0c;发现了…

数据结构笔记-2、线性表

2.1、线性表的定义和基本操作 如有侵权请联系删除。 2.1.1、线性表的定义&#xff1a; ​ 线性表是具有相同数据类型的 n (n>0) 个数据元素的有限序列&#xff0c;其中 n 为表长&#xff0c;当 n 0 时线性表是一个空表。若用 L 命名线性表&#xff0c;则其一般表示为&am…

爬虫初学篇

初次学习爬虫&#xff0c;知识笔记小想 目录&#x1f31f; 一、&#x1f349;基础知识二、&#x1f349;http协议&#xff1a;三、&#x1f349;解析网页(1) xpath的用法&#xff1a;(2) bs4解析器的解释&#xff1a;(3) python字符编码的错误&#xff1a;(4) 正则表达式&#…

第零篇——数学到底应该怎么学?

目录 一、背景介绍二、思路&方案三、过程1.思维导图2.文章中经典的句子理解3.学习之后对于投资市场的理解4.通过这篇文章结合我知道的东西我能想到什么&#xff1f; 四、总结五、升华 一、背景介绍 宏观讲解数学定位&#xff0c;数学学习方式方法&#xff0c;再次详细学习…

Golang | Leetcode Golang题解之第160题相交链表

题目&#xff1a; 题解&#xff1a; func getIntersectionNode(headA, headB *ListNode) *ListNode {if headA nil || headB nil {return nil}pa, pb : headA, headBfor pa ! pb {if pa nil {pa headB} else {pa pa.Next}if pb nil {pb headA} else {pb pb.Next}}retu…

单调栈(续)、由斐波那契数列讲述矩阵快速降幂技巧

在这里先接上一篇文章单调栈&#xff0c;这里还有单调栈的一道题 题目一&#xff08;单调栈续&#xff09; 给定一个数组arr&#xff0c; 返回所有子数组最小值的累加和 就是一个数组&#xff0c;有很多的子数组&#xff0c;每个数组肯定有一个最小值&#xff0c;要把所有子…

【stm32-新建工程】

stm32-新建工程 ■ 下载相关STM32Cube官方固件包&#xff08;F1&#xff0c;F4&#xff0c;F7&#xff0c;H7&#xff09;■ 1. ST官方搜索STM32Cube■ 2. 搜索 STM32Cube■ 3. 点击获取软件■ 4. 选择对应的版本下载■ 5. 输入账号信息■ 6. 出现下载弹框&#xff0c;等待下载…

MySQL 使用 MyFlash 快速恢复误删除、误修改数据

一、MyFlash MyFlash 是由美团点评公司技术工程部开发并维护的一个开源工具&#xff0c;主要用于MySQL数据库的DML操作的回滚。这个工具通过解析binlog日志&#xff0c;帮助用户高效、方便地进行数据恢复。MyFlash的优势在于它提供了更多的过滤选项&#xff0c;使得回滚操作变…

云计算在保险行业的应用:太平财险团财险理赔新核心业务系统案例

随着科技的快速发展&#xff0c;云计算技术已经成为推动保险行业数字化转型的重要力量。云计算为保险公司提供了弹性、可扩展的计算资源&#xff0c;使其能够灵活应对业务高峰和低谷&#xff0c;提高业务运营效率和风控水平。太平财险与太平金科联合开发的“团财险理赔新核心业…

LLVM 中 的 pass 及其管理机制

概述 LLVM 编译器框架的核心概念是任务调用和执行 编译器开发者将IR分解为不同的处理对象&#xff0c;并将其处理过程实现为单独的pass类型。在编译器初始化&#xff0c;pass被实例化&#xff0c;并被添加到pass管理中 pass 管理器(pass manager) 以流水线的方式将各个独立的…

HCS-华为云Stack-容器网络

HCS-华为云Stack-容器网络 容器隧道overlay VPC网络