FreeROTS学习 内存管理

devtools/2025/1/12 19:47:59/

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

FreeRTOS 内存管理简介

FreeRTOS 创建任务、队列、信号量等的时候有两种方法,一种是动态的申请所需的 RAM。一种是由用户自行定义所需的 RAM,这种方法也叫静态方法

不同的嵌入式系统对于内存分配和时间要求不同,因此一个内存分配算法可以作为系统的可选选项。FreeRTOS 将内存分配作为移植层的一部分,这样 FreeRTOS 使用者就可以使用自己的合适的内存分配方法。

当内核需要 RAM 的时候可以使用 pvPortMalloc()来替代 malloc()申请内存,不使用内存的时候可以使用 vPortFree()函数来替代 free()函数释放内存。函数 pvPortMalloc()、vPortFree()与函数 malloc()、free()的函数原型类似

FreeRTOS 提供了 5 种内存分配方法,FreeRTOS 使用者可以其中的某一个方法,或者自己的内存分配方法。这 5 种方法是 5 个文件,分为:heap_1.c、heap_2.c、heap_3.c、heap_4.c 和heap_5.c。这 5 个文件在FreeRTOS 源码中

xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

学习内存分配方法之前我们先来看一下什么叫做内存碎片
在这里插入图片描述
有些应用使用完内存,进行了释放,从左往右第一个 80B 和后面的 10B 这两个内存块就是释放的内存。如果此时有个应用需要 50B 的内存,那么它可以从两个地方来获取到,一个是最前面的还没被分配过的剩余内存块,另一个就是刚刚释放出来的 80B 的内存块。但是很明显,刚刚释放出来的这个 10B 的内存块就没法用了,除非此时有另外一个应用所需要的内存小于 10B;

经过很多次的申请和释放以后,内存块被不断的分割、最终导致大量很小的内存块!也就是图中 80B 和 50B 这两个内存块之间的小内存块,这些内存块由于太小导致大多数应用无法使用,这些没法使用的内存块就沦为了内存碎片

内存碎片是内存管理算法重点解决的一个问题,否则的话会导致实际可用的内存越来越少,最终应用程序因为分配不到合适的内存而奔溃!FreeRTOS 的 heap_4.c 就给我们提供了一个解决内存碎片的方法,那就是将内存碎片进行合并组成一个新的可用的大内存块

xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

内存管理方法简介优点缺点
heap_1.c简单静态分配方式,提供单一的内存堆,分配后内存块不会被释放,内存块大小在编译时确定。实现简单,占用资源少,无内存碎片问题,对于资源分配固定的简单系统可靠性高。缺乏灵活性,不能动态释放内存,不适用于需要频繁分配和释放内存的复杂场景。
heap_2.c基于单向链表管理固定大小的内存块,可动态分配和释放内存。对于固定大小内存块的分配和释放操作相对简单高效,适用于内存块大小固定的频繁分配和释放场景,如相同大小任务栈的管理。只能处理固定大小的内存块,存在内存碎片风险,当内存块大小需求不一致时,内存利用率可能较低。
heap_3.c对标准C库的malloc()free()函数进行简单包装。利用标准C库的功能,易于理解和移植,在熟悉标准C库内存管理的情况下可以快速上手。依赖标准C库的性能和特性,可能存在标准C库本身的内存碎片问题,对一些资源受限的嵌入式系统可能不太适用。
heap_4.c采用双向链表管理可变大小的内存块,能合并相邻空闲内存块来提高利用率,可动态分配和释放。能灵活处理不同大小的内存块分配,通过合并空闲内存块提高了内存利用率,适用于复杂多变的内存分配需求。实现相对复杂,占用一定的系统资源用于管理内存链表,内存分配和释放操作可能比简单的方法耗时。
heap_5.c基于heap_4.c的算法扩展到多个不连续的内存区域,可在这些区域间分配和释放内存。能够有效利用分散的内存资源,适用于内存分布不连续的系统,提高了整个系统的内存可用性。管理多个区域的内存增加了复杂性,对内存管理的开销进一步增大,实现和调试难度较高。

xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
字节对齐的目的是什么?
在这里插入图片描述

xxxxxxxxxxxxxxxxxxxxxxxxxxxxx

1、heap_1 内存分配方法

动 态 内 存 分 配 需 要 一 个 内 存 堆 , FreeRTOS 中 的 内 存 堆 ucHeap[] , 大 小 为configTOTAL_HEAP_SIZE,这个前面讲 FreeRTOS 配置的时候就讲过了。不管是哪种内存分配方法,它们的内存堆都为 ucHeap[] , 而且大小都是 configTOTAL_HEAP_SIZE。内存堆在文件heap_x.c (x 为 1~5) 中定义的,比如 heap_1.c 文件:

#if( configAPPLICATION_ALLOCATED_HEAP == 1 ) extern uint8_t ucHeap[ configTOTAL_HEAP_SIZE ]; //需要用户自行定义内存堆 //当宏 configAPPLICATION_ALLOCATED_HEAP 为 1 的时候需要//用户自行定义内存堆,否则的话由编译器来决定,默认都是由编译器//来决定的。如果自己定义的话就可以将内存堆定义//到外部 SRAM 或者 SDRAM 中
#else static uint8_t ucHeap[ configTOTAL_HEAP_SIZE ]; //编译器决定 
#endif 

heap_1 实现起来就是当需要 RAM 的时候就从一个大数组(内存堆)中分一小块出来,大数组(内存堆)的容量为 configTOTAL_HEAP_SIZE,上面已经说了。使用函数 xPortGetFreeHeapSize()可以获取内存堆中剩余内存大小。

适用于那些一旦创建好任务、信号量和队列就再也不会删除的应用,实际上大多数的FreeRTOS 应用都是这样的,代码实现和内存分配过程都非常简单,内存是从一个静态数组中分配到的,也就是适合于那些不需要动态内存分配的应用

void *pvPortMalloc( size_t xWantedSize ) 
{ 	void *pvReturn = NULL; static uint8_t *pucAlignedHeap = NULL; //确保字节对齐 #if( portBYTE_ALIGNMENT != 1 ) //(1) 这是一个条件编译指令。只有当 portBYTE_ALIGNMENT //不等于 1 时,才会编译 #if 和 #endif 之间的代码{ if( xWantedSize & portBYTE_ALIGNMENT_MASK ) //(2) 这里使用按位与操作符 & 来检查 xWantedSize //与 portBYTE_ALIGNMENT_MASK 按位与的结果是否不为零。//如果结果不为零,说明 xWantedSize 不是 portBYTE_ALIGNMENT 的整数倍,//需要进行字节对齐{ //需要进行字节对齐 xWantedSize += ( portBYTE_ALIGNMENT - ( xWantedSize & portBYTE_ALIGNMENT_MASK ) ); //(3) 这一步是进行字节对齐的核心操作。xWantedSize & portBYTE_ALIGNMENT_MASK //得到 xWantedSize 对 portBYTE_ALIGNMENT 取模的结果,即当前 xWantedSize //距离下一个 portBYTE_ALIGNMENT 整数倍的差值。然后用 portBYTE_ALIGNMENT //减去这个差值,得到需要增加的字节数,最后将这个增加的字节数加到 xWantedSize 上,//从而实现字节对齐} } #endif vTaskSuspendAll(); //(4) { if( pucAlignedHeap == NULL ) { //确保内存堆的开始地址是字节对齐的 pucAlignedHeap = ( uint8_t * ) ( ( ( portPOINTER_SIZE_TYPE )\ //(5) &ucHeap[ portBYTE_ALIGNMENT ] ) &\ ( ~( ( portPOINTER_SIZE_TYPE )\ portBYTE_ALIGNMENT_MASK ) ) ); } //检查是否有足够的内存供分配,有的话就分配内存 if( ( ( xNextFreeByte + xWantedSize ) < configADJUSTED_HEAP_SIZE ) && //(6) ( ( xNextFreeByte + xWantedSize ) > xNextFreeByte ) ) { pvReturn = pucAlignedHeap + xNextFreeByte; //(7) xNextFreeByte += xWantedSize; //(8) } traceMALLOC( pvReturn, xWantedSize ); } ( void ) xTaskResumeAll(); //(9) #if( configUSE_MALLOC_FAILED_HOOK == 1 ) //(10) { if( pvReturn == NULL ) { extern void vApplicationMallocFailedHook( void ); vApplicationMallocFailedHook(); } } #endif return pvReturn; //(11) 
} 

补充:
portBYTE_ALIGNMENT 是一个在特定编程环境(尤其是嵌入式系统或与硬件交互紧密的代码中)经常使用的宏定义或常量。它用于指定字节对齐的规则,即数据存储时按照多少字节的边界进行对齐;

指定对齐单位:它的值表示数据在内存中存储时需要对齐的字节数。例如,如果 portBYTE_ALIGNMENT 被定义为 4,意味着数据存储时会按照 4 字节的边界进行对齐。这通常是为了满足特定硬件架构对数据访问的要求,不同的硬件架构可能对数据的对齐方式有不同的规定,以提高内存访问的效率和稳定性

条件编译与对齐处理:在代码中,常通过 portBYTE_ALIGNMENT 来进行条件编译和字节对齐的逻辑判断。就像你提供的代码中,通过检查 portBYTE_ALIGNMENT 是否不等于 1 来决定是否执行后续的字节对齐操作。如果 portBYTE_ALIGNMENT 等于 1,说明不需要进行特殊的字节对齐处理,因为所有数据默认已经按 1 字节对齐;而当 portBYTE_ALIGNMENT 大于 1 时,需要对数据大小进行调整,使其满足指定的对齐要求。

portBYTE_ALIGNMENT_MASK 的作用
portBYTE_ALIGNMENT_MASK 是一个与 portBYTE_ALIGNMENT 相关的掩码值。通常,portBYTE_ALIGNMENT 是 2 的幂次方,例如 2、4、8 等。当 portBYTE_ALIGNMENT 是 2 的幂次方时,portBYTE_ALIGNMENT_MASK 的值为 portBYTE_ALIGNMENT - 1
例如:
如果 portBYTE_ALIGNMENT 为 4(二进制 100),那么 portBYTE_ALIGNMENT_MASK 为 3(二进制 011)。
如果 portBYTE_ALIGNMENT 为 8(二进制 1000),那么 portBYTE_ALIGNMENT_MASK 为 7(二进制 0111)。
在这里插入图片描述

在这里插入图片描述

2、heap_2 内存分配方法

heap_2提供了一个更好的分配算法,不像heap_1那样,heap_2提供了内存释放函数。 heap_2不会把释放的内存块合并成一个大块,这样有一个缺点,随着你不断的申请内存,内存堆就会被分为很多个大小不一的内存(块),也就是会导致内存碎片

3、heap_3 内存分配方法

这个分配方法是对标准 C 中的函数 malloc()和 free()的简单封装,FreeRTOS 对这两个函数做了线程保护

4、heap_4 内存分配方法

heap_4 提供了一个最优的匹配算法,不像 heap_2,heap_4会将内存碎片合并成一个大的可用内存块,它提供了内存块合并算法。内存堆为ucHeap[ ],大小同样为configTOTAL_HEAP_SIZE。

可以通过函数xPortGetFreeHeapSize() 来获取剩余的内存大小

在这里插入图片描述
它采用双向链表结构来管理内存,并能够合并相邻的空闲内存块。这使得内存的利用率得到提高。在资源有限的嵌入式系统中,高效的内存利用至关重要。比如,当一个任务结束并释放其占用的内存块后,heap_4.c 可以将该内存块与相邻的空闲内存块合并为一个更大的空闲内存块,以便后续分配给需要较大内存空间的其他任务或资源。

5、heap_5内存分配算法

heap5 内存管理算法是在 heap4 内存管理算法的基础上实现的,但是 heap5 内存管理算法在 heap4 内存管理算法的基础上实现了管理多个非连续内存区域的能力。

heap_5 内存管理算法默认并没有定义内存堆,需要用户手动指定内存区域的信息,对其进行初始化。


http://www.ppmy.cn/devtools/149556.html

相关文章

【Uniapp-Vue3】v-model双向绑定的实现原理

我们想要实现双向绑定&#xff0c;只靠v-bind是无法完成的&#xff1a; 但是我们可以通过添加input事件实时修改iptvalue中的值实现双向绑定&#xff1a; 以上是使用v-bind结合input实现的双向绑定&#xff0c;可以直接使用v-model去实现双向绑定&#xff1a; 注意&#xff1a;…

【FlutterDart】tolyui_feedback组件例子效果(23 /100)

上效果图 有12种位置展示效果&#xff1b;很能满足大部分需要 代码如下&#xff1a; import package:flutter/material.dart; import package:tolyui_feedback/tolyui_feedback.dart;class TolyTooltipDemo extends StatelessWidget {const TolyTooltipDemo({super.key});ove…

大数据学习(32)-spark基础总结

&&大数据学习&& &#x1f525;系列专栏&#xff1a; &#x1f451;哲学语录: 承认自己的无知&#xff0c;乃是开启智慧的大门 &#x1f496;如果觉得博主的文章还不错的话&#xff0c;请点赞&#x1f44d;收藏⭐️留言&#x1f4dd;支持一下博主哦&#x1f91e…

设计模式--策略模式【行为型模式】

设计模式的分类 我们都知道有 23 种设计模式&#xff0c;这 23 种设计模式可分为如下三类&#xff1a; 创建型模式&#xff08;5 种&#xff09;&#xff1a;单例模式、工厂方法模式、抽象工厂模式、建造者模式、原型模式。结构型模式&#xff08;7 种&#xff09;&#xff1…

PHP多功能投票小程序源码

多功能投票小程序&#xff1a;全方位打造专属投票盛宴的得力助手 &#x1f389; &#x1f527; 基于先进的ThinkPHP框架与Uniapp技术深度融合&#xff0c;我们匠心独运&#xff0c;精心雕琢出一款功能全面、操作便捷的投票小程序&#xff0c;旨在为您带来前所未有的投票体验。…

python学opencv|读取图像(二十九)使用cv2.getRotationMatrix2D()函数旋转缩放图像

【1】引言 前序已经学习了如何平移图像&#xff0c;相关文章链接为&#xff1a; python学opencv|读取图像&#xff08;二十七&#xff09;使用cv2.warpAffine&#xff08;&#xff09;函数平移图像-CSDN博客 在此基础上&#xff0c;我们尝试旋转图像的同时缩放图像。 【2】…

如何进行单体前后端项目的微服务改造

如何进行单体前后端项目的微服务改造 引言 随着互联网技术的快速发展&#xff0c;传统的单体架构&#xff08;Monolithic Architecture&#xff09;逐渐显现出其局限性。对于大型应用来说&#xff0c;单体架构可能会导致开发效率低下、部署困难以及扩展性差等问题。因此&…

Postman接口测试03|执行接口测试、全局变量和环境变量、接口关联、动态参数、断言

目录 七、Postman 1、安装 2、postman的界面介绍 八、Postman执行接口测试 1、请求页签 3、响应页签 九、Postman的环境变量和全局变量 1、创建环境变量和全局变量可以解决的问题 2、postman中的操作-全局变量 1️⃣手动设置 2️⃣代码设置 3️⃣界面获取 4️⃣代…