FreeROTS学习 内存管理

news2025/1/13 2:17:35

内存管理是一个系统基本组成部分,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.coloradmin.cn/o/2275755.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【西北工业大学主办 | EI检索稳定 | 高H值专家与会报告】2025年航天航空工程与材料技术国际会议(AEMT 2025)

2025 年航天航空工程与材料技术国际会议&#xff08;AEMT 2025&#xff09;将于2025年2月28日至3月2日在中国天津召开。本届会议由西北工业大学主办&#xff0c;由北京航空航天大学、北京理工大学作为支持单位加入&#xff0c;AEIC 学术交流中心协办。 AEMT 2025 旨在汇聚来自全…

目标检测跟踪中的Siamese孪生网络与普通卷积网络(VGG、ResNet)有什么区别?

1、什么是Siamese网络&#xff1f; Siamese网络又叫孪生网络&#xff0c;是一种特殊的神经网络架构&#xff0c;由一对&#xff08;或多对&#xff09;共享参数的子网络组成&#xff0c;用于学习输入样本之间的相似性或关系。最早在 1994 年由 Bromley 等人提出&#xff0c;最…

网络攻击行为可视化分析系统【数据分析 + 可视化】

一、系统背景 随着信息技术的快速发展&#xff0c;网络已成为现代社会不可或缺的一部分。然而&#xff0c;与此同时&#xff0c;网络攻击手段也日益多样化和复杂化&#xff0c;给企业和个人的信息安全带来了极大的威胁。传统的网络攻击分析方法往往依赖于人工分析和处理大量的…

一个运行在浏览器中的开源Web操作系统Puter本地部署与远程访问

文章目录 前言1.关于Puter2.本地部署Puter3.Puter简单使用4. 安装内网穿透5.配置puter公网地址6. 配置固定公网地址 &#x1f4a1; 推荐 前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。【点击跳转到网站…

C语言 操作符_位操作符、赋值操作符、单目操作符

1.位操作符 & - 按&#xff08;2进制&#xff09;位与 | - 按&#xff08;2进制&#xff09;位或 ^ - 按&#xff08;2进制&#xff09;位异或 只适用于整型 例&#xff1a;实现交换两个变量的值&#xff0c;要求不能新建变量 //3^3 0 -> a^a 0 //011 //011 //000 …

图像处理 | 图像二值化

在图像处理领域&#xff0c;图像二值化是一个重要的操作&#xff0c;它将彩色或灰度图像转换为只有两种颜色&#xff08;通常是黑白&#xff09;的图像。二值化广泛应用于文字识别、图像分割、边缘检测等领域&#xff0c;尤其在处理简洁和高对比度的图像时非常有效。本文将深入…

IP 地址与蜜罐技术

基于IP的地址的蜜罐技术是一种主动防御策略&#xff0c;它能够通过在网络上布置的一些看似正常没问题的IP地址来吸引恶意者的注意&#xff0c;将恶意者引导到预先布置好的伪装的目标之中。 如何实现蜜罐技术 当恶意攻击者在网络中四处扫描&#xff0c;寻找可入侵的目标时&…

Web基础之什么是HTTP协议

Q&#xff1a;什么是HTTP协议&#xff1f; 概念&#xff1a;Hyper Text Transfer Protocol&#xff0c;超文本传输协议&#xff0c;规定了浏览器和服务器之间数据传输的规则。 特点&#xff1a; 1&#xff0e;基于TCP协议&#xff1a;面向连接&#xff0c;安全 2&#xff0e;基…

#渗透测试#谷歌扩展学习#编写一个属于自己的谷歌扩展

目录 一、Chrome扩展程序是什么 二、如何自己编写一个简单谷歌扩展 1. 创建项目文件夹 2. 创建 manifest.json 文件 3. 创建 popup.html 文件 4. 创建 popup.js 文件 5. 加载扩展程序到Chrome浏览器 6. 测试扩展程序 三、Chrome插件图标设计技巧 1. 简洁明了 2. 独特…

LayerNorm的思考

文章目录 1. LayerNorm2. 图解3. softmax4. python 代码 1. LayerNorm y x − E [ x ] v a r ( x ) ϵ ∗ γ β \begin{equation} y\frac{x-\mathrm{E}[x]}{\sqrt{\mathrm{var}(x)\epsilon}}*\gamma\beta \end{equation} yvar(x)ϵ ​x−E[x]​∗γβ​​ 2. 图解 矩阵A …

ExplaineR:集成K-means聚类算法的SHAP可解释性分析 | 可视化混淆矩阵、决策曲线、模型评估与各类SHAP图

集成K-means聚类算法的SHAP可解释性分析 加载数据集并训练机器学习模型 SHAP 分析以提取特征对预测的影响 通过混淆矩阵可视化模型性能 决策曲线分析 模型评估&#xff08;多指标和ROC曲线的目视检查&#xff09; 带注释阈值的 ROC 曲线 加载 SHAP 结果以进行下游分析 与…

Kafka 会丢消息吗?

目录 01 生产者(Producer) 02 消息代理(Broker) 03 消费者(Consumer) 来源:Kafka 会丢消息吗? Kafka 会丢失信息吗? 许多开发人员普遍认为,Kafka 的设计本身就能保证不会丢失消息。然而,Kafka 架构和配置的细微差别会导致消息的丢失。我们需要了解它如何以及何时…

Open FPV VTX开源之第一次出图

Open FPV VTX开源之第一次出图 1. 源由2. 连线2.1 飞控2.2 调试 3. serial3.1 启动log - uboot3.2 登录版本 - linux3.3 获取有线IP 4. ssh - linux5. PixelPilot出图6. 总结7. 参考资料8. 补充 - 8812AU网卡 1. 源由 在《Open FPV VTX开源之硬件规格及组成》章节中&#xff0…

仓颉笔记——写一个简易的web服务并用浏览器打开

创建一个web服务端&#xff0c;同时创建一个客户端去读取这个服务端。 也满足浏览器打开web的需求。 直接上代码。 import net.http.* import std.time.* import std.sync.* import std.log.LogLevel// 1. 构建 Server 实例 let server ServerBuilder().addr("127.0.0.1&…

Trie树算法

Trie树&#xff0c;也称为前缀树或字典树&#xff0c;是一种特殊的树型数据结构。它用于存储一组字符串&#xff0c;使得查找、插入和删除字符串的操作非常高效。类似这种&#xff0c; 模板&#xff1a; 这是用数组来模拟上图中的树的结构&#xff0c;逻辑上和上图结构一致。 …

03-51单片机定时器和串口通信

一、51单片机定时器 1.定时器介绍 1.1为什么要使用定时器 在前面的学习中&#xff0c;用到了 Delay 函数延时&#xff0c;这里学习定时器以后&#xff0c;就可以通过定时器来完成&#xff0c;当然定时器的功能远不止这些&#xff1a; 51 单片机的定时器既可以定时&#xff…

搭建docker私有化仓库Harbor

Docker私有仓库概述 Docker私有仓库介绍 Docker私有仓库是个人、组织或企业内部用于存储和管理Docker镜像的存储库。Docker默认会有一个公共的仓库Docker Hub,而与Docker Hub不同,私有仓库是受限访问的,只有授权用户才能够上传、下载和管理其中的镜像。这种私有仓库可以部…

【深度学习】核心概念-人工神经网络(Artificial Neural Network, ANN)

人工神经网络是一种受生物神经系统启发的机器学习模型&#xff0c;旨在通过连接大量的节点&#xff08;称为神经元或节点&#xff09;来模拟人脑的学习方式。它是一种在监督学习和非监督学习中广泛应用的深度学习模型。 人工神经网络的基本结构 一个人工神经网络通常由以下三个…

切比雪夫插值

切比雪夫插值是一种基于切比雪夫节点的多项式插值方法&#xff0c;其优势是减少插值误差(特别是龙格现象&#xff1a;表现为高维插值时在边缘处插值误差骤增)。本文对其基本操作进行说明。 1. 切比雪夫节点 切比雪夫插值的核心是使用切比雪夫节点作为插值点。切比雪夫节点是切…

ELK的搭建

ELK elk&#xff1a;elasticsearch logstatsh kibana统一日志收集系统 elasticsearch&#xff1a;分布式的全文索引引擎点非关系型数据库,存储所有的日志信息&#xff0c;主和从&#xff0c;最少需要2台 logstatsh&#xff1a;动态的从各种指定的数据源&#xff0c;获取数据…