FreeROTS学习 内存管理

内存管理是一个系统基本组成部分,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 内存管理算法默认并没有定义内存堆,需要用户手动指定内存区域的信息,对其进行初始化。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

JavaFx 21 项目Markdown 预览、编辑、新建、文件树、删除、重命名

项目文件结构 项目的源代码和资源文件存放在以下路径: 源代码: src/main/java/com/kong/markdown/ 包含多个 Java 文件,主要实现了应用的功能: App.java:主类,可能包含应用的启动逻辑。FileService.java:可能与文件操作相关的服务类。MainController.java:控制器类,可…

【Uniapp-Vue3】computed计算属性用法及方法对比

如果我们想要将两个响应式变量进行某种运算&#xff0c;就可以使用computed计算属性。 比如下面这个例子中&#xff0c;输入名和姓合成全名&#xff0c;可以用直接显示的方法&#xff1a; 我们也可以使用computed属性&#xff1a; import {computed} from "vue"; le…

【Linux】模拟Shell命令行解释器

一、知识补充 1.1 snprintf snprintf() 是 C语言的一个标准库函数&#xff0c;定义在<stdio.h>头文件中。 snprintf() 函数的功能是格式化字符串&#xff0c;并将结果存储在指定的字符数组中。该函数的原型如下&#xff1a; int snprintf(char *str, size_t size, con…

逆向 易九批 最新版 爬虫逆向 x-sign ......

声明 本文章中所有内容仅供学习交流&#xff0c;抓包内容、敏感网址、数据接口均已做脱敏处理&#xff0c;严禁用于商业用途和非法用途&#xff0c;否则由此产生的一切后果均与作者无关&#xff0c;若有侵权&#xff0c;请联系我立即删除&#xff01; # 欢迎交流 wjxch1004

LeetCode:108.将有序数组转换为二叉搜索树

跟着carl学算法&#xff0c;本系列博客仅做个人记录&#xff0c;建议大家都去看carl本人的博客&#xff0c;写的真的很好的&#xff01; 代码随想录 LeetCode&#xff1a;108.将有序数组转换为二叉搜索树 给你一个整数数组 nums &#xff0c;其中元素已经按 升序 排列&#xff…

双因素身份验证技术在NPI区域邮件安全管控上的解决思路

在制造业中&#xff0c;NPI&#xff08;New Product Introduction&#xff0c;新产品导入&#xff09;区域是指专门负责新产品从概念到市场推出全过程的部门或团队。NPI 的目标是确保新产品能够高效、高质量地投入生产&#xff0c;并顺利满足市场需求。在支撑企业持续创新和竞争…

基于 GEE 下载逐年 MODIS 地表温度 LST 数据

目录 1 地表温度&#xff08;LST&#xff09; 2 数据准备 3 代码实现 3.1 加载研究区与数据集 3.2 数据预处理与标准化 3.3 逐年批量导出 3.4 可视化结果 4 运行结果 5 完整代码 1 地表温度&#xff08;LST&#xff09; 在遥感领域&#xff0c;地表温度&#xff08;L…

Web渗透测试之XSS跨站脚本 防御[WAF]绕过手法

目录 XSS防御绕过汇总 参考这篇文章绕过 XSS payload XSS防御绕过汇总 服务端知道有网络攻击或者xss攻 Html 通过js代码 标签属性等手段进行一个过滤 不允许出现css的payload 前端过滤 我可以在抓包工具里面修改 抓包工具是不受前端的防御 也 就是浏览器 服务端过滤…

4G、5G移远模块SIM卡热插拔问题解决

4G、5G移远模块SIM卡热插拔问题解决 1、开发环境2、问题阐述3、开启模块热插拔功能4、问题解决5、quectelCM工具(热插拔)6、思路1、开发环境 系统: ubuntu20.04 拨号工具:quectelCM 模块型号:ec20、rm500u 开发平台:RK35XX系列 2、问题阐述 SIM卡热插拔功能已开启,拔掉…

Win11家庭版转专业版

Win11家庭版转专业版&#xff08;亲测有效&#xff09; 第一步 【断网】输入这个密钥&#xff1a; R8NJ8-9X7PV-C7RCR-F3J9X-KQBP6 第二步 点击下一步会自动重启 第三步 【联网】输入这个密钥&#xff1a; F3NWX-VFMFC-MHYYF-BCJ3K-QV66Y 注意 两次输入密钥的地方一致 …

浅析大语言模型安全和隐私保护国内外标准和政策

过去两年&#xff0c;大模型技术已经普及并逐步渗透到各行各业&#xff0c;2025年注定是大模型应用井喷式发展的一年&#xff0c;AI在快速发展的同时&#xff0c;其带来的安全风险也逐渐凸显。人工智能系统的安全性和隐私保护已经成为社会关注的重点。 附下载&#xff1a;600多…

GelSight Mini视触觉传感器凝胶触头升级:增加40%耐用性,拓展机器人与触觉AI 应用边界

马萨诸塞州沃尔瑟姆-2025年1月6日-触觉智能技术领军企业Gelsight宣布&#xff0c;旗下Gelsight Mini视触觉传感器迎来凝胶触头的更新。经内部测试&#xff0c;新Gel凝胶触头耐用性提升40%&#xff0c;外观与触感与原凝胶触头保持一致。此次升级有效满足了客户在机器人应用中对设…

使用 Docker 安装 Maven 私服 Nexus3

在现代软件开发中&#xff0c;私有仓库是企业管理和分发内部依赖的重要工具&#xff0c;能够提高开发效率、加强依赖管理并确保构建的稳定性。Nexus Repository Manager 作为市场上常见的私服解决方案&#xff0c;不仅支持 Maven&#xff0c;还支持 npm、PyPI 等多种格式&#…

opencv 学习(3)

文章目录 画直线画圆画矩形画多边形文字绘制画板 本部分图形绘制 本节内容比较简单 直接上代码 画直线 import cv2 import numpy as np## 本节画直线## line 参数介绍 ## image 在那个图像上面 画线 ## 开始点借宿点 指定先的开始和结束的位置 ## 颜色 线宽 线性 ## shift…

Java-数据结构-栈与队列(StackQueue)

一、栈(Stack) ① 栈的概念 栈是一种特殊的线性表&#xff0c;它只允许固定一端进行"插入元素"和"删除元素"的操作&#xff0c;这固定的一端被称作"栈顶"&#xff0c;对应的另一端就被称做"栈底"。 &#x1f4da; 栈中的元素遵循后…

带格式 pdf 翻译

支持 openAI 接口&#xff0c;国内 deepseek 接口兼容 openAI 接口&#xff0c; deepseek api 又非常便宜 https://pdf2zh.com/ https://github.com/Byaidu/PDFMathTranslate

【如何从0到1设计测试用例使用Fiddler完成弱网测试】

&#x1f308;个人主页&#xff1a;努力学编程’ ⛅个人推荐&#xff1a; c语言从初阶到进阶 JavaEE详解 数据结构 ⚡学好数据结构&#xff0c;刷题刻不容缓&#xff1a;点击一起刷题 &#x1f319;心灵鸡汤&#xff1a;总有人要赢&#xff0c;为什么不能是我呢 ⭐⭐⭐测试用…

基础项目实战——贪吃蛇(c++)

目录 前言一、 游戏总体框架二、地图绘制三、光标隐藏四、地图定义五、蛇体定义六、蛇体绘制七、蛇体移动八、频率控制九、边界检测十、游戏失败十一、蛇体转向十二、食物生成十三、食物碰撞十四、整体代码十五、结语 前言 各位小伙伴们好久不见&#xff0c;前段时间非常的忙很…

排序:插入、选择、交换、归并排序

排序 &#xff1a;所谓排序&#xff0c;就是使一串记录&#xff0c;按照其中的某个或某些关键字的大小&#xff0c;递增或递减的排列起来的操作。 稳定性 &#xff1a;假定在待排序的记录序列中&#xff0c;存在多个具有相同的关键字的记录&#xff0c;若经过排序&#xff0c;…

Windows service运行Django项目

系统&#xff1a;Windows Service 软件&#xff1a;nssm&#xff0c;nginx 配置Django项目 1、把Django项目的静态文件整理到staticfiles文件夹中 注&#xff1a;settings中的设置 STATIC_URL /static/ STATIC_ROOT os.path.join(BASE_DIR, staticfiles/) STATICFILES_DI…