【FreeRTOS】内存管理

news2025/2/27 14:01:57

目录

  • 1 为什么要自己实现内存管理
  • 2 FreeRTOS的5中内存管理方法
    • 2.1 Heap_1
    • 2.2 Heap_2
    • 2.3 Heap_3
    • 2.4 Heap_4
  • 2.5 Heap_5
  • 3 Heap相关的函数
    • 3.1 pvPortMalloc/vPortFree
    • 3.2 xPortGetFreeHeapSize
  • 3.3 xPortGetMinimumEverFreeHeapSize
    • 3.4 malloc失败的钩子函数

参考《FreeRTOS入门与工程实践(基于DshanMCU-103)》

1 为什么要自己实现内存管理

后续的章节涉及这些内核对象:task、queue、semaphores和event group等。为了让FreeRTOS更容易使用,这些内核对象一般都是动态分配:用到时分配,不使用时释放。使用内存的动态管理功能,简化了程序设计:不再需要小心翼翼地提前规划各类对象,简化API函数的涉及,甚至可以减少内存的使用。

内存的动态管理是C程序的知识范畴,并不属于FreeRTOS的知识范畴,但是它跟FreeRTOS关系是如此紧密,所以我们先学习它。

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

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

注意:我们经常"堆栈"混合着说,其实它们不是同一个东西:

堆,heap,就是一块空闲的内存,需要提供管理函数

  • malloc:从堆里划出一块空间给程序使用
  • free:用完后,再把它标记为"空闲"的,可以再次使用

栈,stack,函数调用时局部变量保存在栈中,当前程序的环境也是保存在栈中

  • 可以从堆中分配一块空间用作栈

Alt

2 FreeRTOS的5中内存管理方法

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

  • 文件在FreeRTOS/Source/portable/MemMang下,它也是放在portable目录下,表示你可以提供自己的函数。
    源码中默认提供了5个文件,对应内存管理的5种方法

  • 文件在 Middlewares\Third_Party\FreeRTOS\Source\portable\MemMang 下,它也是放在“portable”目录下,表示你可以提供自己的函数。

源码中默认提供了5个文件,对应内存管理的5种方法。

参考文章:http://t.csdnimg.cn/wCCzx

文件优点缺点
heap_1.c分配简单,时间确定只分配、不回收
heap_2.c动态分配、最佳匹配碎片、时间不定
heap_3.c调用标准库函数速度慢、时间不定
heap_4.c相邻空闲内存可合并可解决碎片问题、时间不定
heap_5.c在 heap_4 基础上支持分隔的内存块可解决碎片问题、时间

2.1 Heap_1

Heap_1 它只实现了pvPortMalloc,没有实现vPortFree。 只分配 不回收,一点都不浪费空间
如果你的程序不需要删除内核对象,那么可以使用heap_1:

  • 实现最简单
  • 没有碎片问题
  • 一些要求非常严格的系统里,不允许使用动态内存,就可以使用 heap_1

它的实现原理很简单,首先定义一个大数组:

/* Allocate the memory for the heap. */
##if ( configAPPLICATION_ALLOCATED_HEAP == 1 )

/* The application writer has already defined the array used for the RTOS
* heap -  probably so it can be placed in a special segment or address. */
    extern uint8_t ucHeap[ configTOTAL_HEAP_SIZE ];
##else
    static uint8_t ucHeap[ configTOTAL_HEAP_SIZE ];
##endif /* configAPPLICATION_ALLOCATED_HEAP */

然后,对于pvPortMalloc调用时,从这个数组中分配空间。

FreeRTOS在创建任务时,需要2个内核对象: task control block(TCB)、 stack。
使用heap_1时,内存分配过程如下图所示:

  • A:创建任务之前整个数组都是空闲的

  • B:创建第 1 个任务之后,蓝色区域被分配出去了

  • C:创建 3 个任务之后的数组使用情况

在这里插入图片描述

在这里插入图片描述

2.2 Heap_2

Heap_2之所以还保留,只是为了兼容以前的代码。新设计中不再推荐使用Heap_2。 建议使用Heap_4来替代Heap_2,更加高效
Heap_2也是在数组上分配内存,跟Heap_1不一样的地方在于:

  • Heap_2 使用最佳匹配算法(best fit)来分配内存

  • 它支持 vPortFree

最佳匹配算法:

  • 假设 heap 有 3 块空闲内存: 5 字节、 25 字节、 100 字节

  • pvPortMalloc 想申请 20 字节

  • 找出最小的、能满足 pvPortMalloc 的内存: 25 字节

  • 把它划分为 20 字节、 5 字节
    ◼ 返回这 20 字节的地址
    ◼ 剩下的 5 字节仍然是空闲状态,留给后续的 pvPortMalloc 使用

与Heap_4相比, Heap_2不会合并相邻的空闲内存,所以Heap_2会导致严重的"碎片化"问题
但是,如果申请、分配内存时大小总是相同的,这类场景下Heap_2没有碎片化的问题。所以它适合这种场景:频繁地创建、删除任务,但是任务的栈大小都是相同的(创建任务时,需要分配TCB和栈, TCB总是一样的)。
虽然不再推荐使用heap_2,但是它的效率还是远高于malloc、 free

使用heap_2时,内存分配过程如下图所示:

  • A:创建了 3 个任务

  • B:删除了一个任务,空闲内存有 3 部分:顶层的、被删除任务的 TCB 空间、被删除任务的 Stack 空间

  • C:创建了一个新任务,因为 TCB、栈大小跟前面被删除任务的 TCB、栈大小一致,所以刚好分配到原来的内存

在这里插入图片描述

heap_2 既分配又释放
在这里插入图片描述
假设buf1是申请了100个字节,有头部,buf2是申请了50个字节,也有头部~

假设释放buf1和buf2,这两块内存是紧密相连的,但是它不能合并到一起,buf1仍然最大能分配100字节,buf2仍然是最大能分配50字节,假设buf3把最后面的内存都用完了,现在再想分配120字节的空间,用heap2这种方法就没有办法分配了!原因&缺点:它没有把空闲的紧密相连的空间合并在一起,所以有严重的碎片问题。

2.3 Heap_3

heap_3一般不用,调用标准库

  • Heap_3 使用标准 C 库里的 malloc、 free 函数,所以堆大小由链接器的配置决定,配置项 configTOTAL_HEAP_SIZE 不再起作用。
  • C库里的malloc、 free函数并非线程安全的, Heap_3中先暂停FreeRTOS的调度器,再去调用这些函数,使用这种方法实现了线程安全.

2.4 Heap_4

跟 Heap_1、 Heap_2 一样, Heap_4 也是使用大数组来分配内存
Heap_4使用首次适应算法(first fit)来分配内存。它还会把相邻的空闲内存合并为一个更大的空闲内存,这有助于较少内存的碎片问题

首次适应算法:

  • 假设堆中有 3 块空闲内存: 5 字节、 200 字节、 100 字节
  • pvPortMalloc 想申请 20 字节
  • 找出第 1 个能满足 pvPortMalloc 的内存: 200 字节
  • 把它划分为 20 字节、 180 字节
  • 返回这 20 字节的地址
  • 剩下的 180 字节仍然是空闲状态,留给后续的 pvPortMalloc 使用

Heap_4会把相邻空闲内存合并为一个大的空闲内存,可以较少内存的碎片化问题。适用
于这种场景:频繁地分配、释放不同大小的内存。

Heap_4的使用过程举例如下:

  • A:创建了 3 个任务
  • B:删除了一个任务,空闲内存有 2 部分:
    ◼ 顶层的
    ◼ 被删除任务的 TCB 空间、被删除任务的 Stack 空间合并起来的
  • C:分配了一个 Queue,从第 1 个空闲块中分配空间
  • D:分配了一个 User 数据,从 Queue 之后的空闲块中分配
  • E:释放的 Queue, User 前后都有一块空闲内存
  • F:释放了 User 数据, User 前后的内存、 User 本身占据的内存, 合并为一个
    大的空闲内存

在这里插入图片描述

Heap_4执行的时间是不确定的,但是它的效率高于标准库的malloc、 free。

总结:

heap_4 既分配又释放, heap_4在heap_2的基础上做了一些改进,heap_4合并相邻的空闲内存

针对heap_2的缺点,如果我们再想分配120字节的空间,那就是可以的了,因为buf1的100字节和buf2的50字节合并起来了!

heap_5 用来支持分隔的内存

两块红色的就是分隔的内存,可以用heap_5来管理
在这里插入图片描述
空闲链表头,指向第一个堆,再指向第二个堆,我们需要告诉链表头,有多少个离散的堆

一般的都是用heap_4

2.5 Heap_5

Heap_5 分配内存、释放内存的算法跟 Heap_4 是一样的。
相比于Heap_4, Heap_5并不局限于管理一个大数组:它可以管理多块、分隔开的内存。
在嵌入式系统中,内存的地址可能并不连续,这种场景下可以使用Heap_5。
既然内存时分隔开的,那么就需要进行初始化:确定这些内存块在哪、多大:

  • 在使用 pvPortMalloc 之前,必须先指定内存块的信息

  • 使用 vPortDefineHeapRegions 来指定这些信息

怎么指定一块内存?使用如下结构体:

typedef struct HeapRegion
{
    uint8_t * pucStartAddress; // 起始地址
    size_t xSizeInBytes;       // 大小
} HeapRegion_t;

怎么指定多块内存?使用一个HeapRegion_t数组,在这个数组中,低地址在前、高地址在后。 比如:

HeapRegion_t xHeapRegions[] =
{
  { ( uint8_t * ) 0x80000000UL, 0x10000 }, // 起始地址0x80000000,大小0x10000
  { ( uint8_t * ) 0x90000000UL, 0xa0000 }, // 起始地址0x90000000,大小0xa0000
  { NULL, 0 } // 表示数组结束
 };

vPortDefineHeapRegions函数原型如下:

void vPortDefineHeapRegions( const HeapRegion_t * const pxHeapRegions );

把xHeapRegions数组传给vPortDefineHeapRegions函数,即可初始化Heap_5。

3 Heap相关的函数

这里定义了一个全局数组
在这里插入图片描述

什么时候被用到,初始化堆的时候就用到了
在这里插入图片描述
这个函数在哪里被调用呢
在我们第一次调用malloc函数的时候,就自动初始化了,不需要手动调用HeapInit
在这里插入图片描述
用完之后,再调用pvPortFree就可以了
在这里插入图片描述

3.1 pvPortMalloc/vPortFree

函数原型:

void * pvPortMalloc( size_t xWantedSize );
void vPortFree( void * pv );

作用:分配内存、释放内存。

如果分配内存不成功,则返回值为NULL。

3.2 xPortGetFreeHeapSize

函数原型:

size_t xPortGetFreeHeapSize( void );

当前还有多少空闲内存,这函数可以用来优化内存的使用情况。比如当所有内核对象都分配好后,执行此函数返回2000,那么configTOTAL_HEAP_SIZE就可减小2000。

注意:在heap_3中无法使用。

3.3 xPortGetMinimumEverFreeHeapSize

函数原型:

size_t xPortGetMinimumEverFreeHeapSize( void )

返回:程序运行过程中,空闲内存容量的最小值。

注意:只有heap_4、heap_5支持此函数。

使用:让程序跑一段时间再调用这个函数,如果剩余容量只有两位数了,就很危险了,就需要把3072调的大一些才可以!

  • 这个函数是测试用的

3.4 malloc失败的钩子函数

在pvPortMalloc函数内部:

函数原型:

void * pvPortMalloc( size_t xWantedSize )vPortDefineHeapRegions
{
    ......
    #if ( configUSE_MALLOC_FAILED_HOOK == 1 )
        {
            if( pvReturn == NULL )
            {
                extern void vApplicationMallocFailedHook( void );
                vApplicationMallocFailedHook();
            }
        }
    #endif
    
    return pvReturn;        
}

所以,如果想使用这个钩子函数:

  • 在FreeRTOSConfig.h中,把configUSE_MALLOC_FAILED_HOOK定义为1

  • 提供vApplicationMallocFailedHook函数

  • pvPortMalloc失败时,才会调用此函数

在这里插入图片描述

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

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

相关文章

CleanMyMac是否有必要购买?2024年6.18有什么优惠活动?

CleanMyMac X是专业的Mac应用卸载,清理优化,软件管理更新工具,兔八哥爱分享获取cleanmymac激活码更安全,让你的Mac电脑焕然一新 CleanMyMacX是一款强大的Mac清理和优化工具,针对系统垃圾、恶意软件和隐私保护提供解决方案。重度用户因其高效性能和全面功能可能需要…

Python学习从0开始——Kaggle时间序列002

Python学习从0开始——Kaggle时间序列002 一、作为特征的时间序列1.串行依赖周期 2.滞后序列和滞后图滞后图选择滞后 3.示例 二、混合模型1.介绍2.组件和残差3.残差混合预测4.设计混合模型5.使用 三、使用机器学习进行预测1.定义预测任务2.为预测准备数据3.多步骤预测策略3.1 M…

unidbg讲解V1

前言 unidbg是什么? unidbg是一个Java项目,可以帮助我们去模拟一个安卓或IOS设备,用于去执行so文件中的算法,从而不需要再去逆向他内部的算法。最终会产出一个jar包,可以被python进行调用。 如何使用unidbg? 下载github上开源的项目:https://github.com/zhkl0228/un…

Java基础面试重点-3

41. 简述线程生命周期(状态) 其它参考《多线程重点》中的说法。三种阻塞: 等待阻塞: 运行的线程执行o.wait()方法(该线程已经持有锁),JVM会把该线程放入等待队列中。同步阻塞: 运行的线程在获取对象的同步…

Kafka高频面试题整理

文章目录 1、什么是Kafka?2、kafka基本概念3、工作流程4、Kafka的数据模型与消息存储机制1)索引文件2)数据文件 5、ACKS 机制6、生产者重试机制:7、kafka是pull还是push8、kafka高性能高吞吐的原因1)磁盘顺序读写:保证了消息的堆积2)零拷贝机…

YOLOv9改进策略 | 损失函数篇 | 利用SlideLoss助力YOLOv9有效涨点(附代码 + 完整修改方式)

一、本文介绍 本文给大家带来的是分类损失 SlideLoss损失函数,我们之前看那的那些IoU都是边界框回归损失,和本文的修改内容并不冲突,所以大家可以知道损失函数分为两种一种是分类损失另一种是边界框回归损失,上一篇文章里面我们总…

SIGMOD 2024会议现场第二弹:中国论文接收量断崖式领先,引领全球数据创新潮流

今天是 ACM SIGMOD Conference(ACM Special Interest Group on Management of Data Conference),即国际数据管理大会举办的第五天!在这个充满活力的学术盛会上,全球顶尖学者、行业专家和学生热情汇聚,共享数…

Java项目:111 基于SpringBoot的在线家具商城设计与实现

作者主页:舒克日记 简介:Java领域优质创作者、Java项目、学习资料、技术互助 文中获取源码 项目介绍 本系统有管理员和用户两个角色,包括前台商城平台及后台管理系统。 前台商城系统包含首页门户、商品推荐、商品搜索、商品展示、购物车、订…

警惕!这本SCIE正在被​“On Hold”!

【欧亚科睿学术】 近期,经小编查询,一本近乎百分百录用率、且生信友好的“毕业神刊”——JOURNAL OF BIOLOGICAL REGULATORS AND HOMEOSTATIC AGENTS被科睿唯安打上了“On Hold”标识。 图片来源:科睿唯安(2024年6月13日查&#…

UML类图之间的关系与对应的代码关系

UML类图之间的关系与对应的代码关系 1. 依赖关系1.1 图解1.2代码实现 2. 关联关系2.1图解2.2代码实现 3. 聚合关系3.1图解3.2代码实现 4. 组合关系4.1图解4.2代码实现 5. 泛化关系5.1图解5.2代码实现 6. 实现关系6.1图解6.2代码实现 在UML中,共有四种关系&#xff1…

第 3 章:Spring Framework 中的 AOP

第 3 章:Spring Framework 中的 AOP 讲完了 IoC,我们再来聊聊 Spring Framework 中的另一个重要内容——面向切面编程,即 AOP。它是框架中众多功能的基础,例如声明式事务就是依靠 AOP 来实现的。此外,Spring 还为我们…

发布中文文档类资源仓库-ChineseDocumentPDF

引言 今天中午,排队打饭间隙,刷到新闻,说是:360AILAB-NLP团队开源了中文论文、研报文档场景的轻量化版式分析模型360LayoutAnalysis。 面向中文论文及研报两个场景的轻量化版式分析模型已经开源: Github地址&#x…

wms海外仓系统排名分析:哪个才更适合中小海外仓

对中小型海外仓来说,想在竞争激烈的市场下生存,关键就在于是否能改变自己落后的仓储管理模式,提升客户满意度和业务流畅度。 wms海外仓系统作为这一领域的关键工具,可以说在很大程度上决定了海外仓的业务标准化程度发展。不过现在…

VMware安装Debian,Debian分区,虚拟机使用NAT模式联网,Linux设置静态IP

官网 https://www.debian.org/download stable是稳定版 win下amd64就行,macOs装arm架构的 安装Debian虚拟机 教程里没有的只管往下点就完了 哪个都行 选镜像 选安装位置 别超过宿主机内核就行 看你需求 NAT模式 虚拟 看你需求 其他的也检查一下 图形安装 选中文 继…

C++:SLT容器-->deque

C:SLT容器-->deque 1. 构造函数2. deque 赋值操作3. deque 大小操作4. deque 插入和删除5. deque 容器数据存取6. deque 排序操作 双端数组&#xff0c;可以对头部和尾部进行插入删除操作 需要导入头文件#include <deque> 1. 构造函数 deque deqT; // 默认构造函数 de…

6.13.1 使用残差神经网络堆叠集成进行乳腺肿块分类和诊断的综合框架

计算机辅助诊断 (CAD) 系统需要将肿瘤检测、分割和分类的自动化阶段按顺序集成到一个框架中&#xff0c;以协助放射科医生做出最终诊断决定。 介绍了使用堆叠的残差神经网络 (ResNet) 模型&#xff08;即 ResNet50V2、ResNet101V2 和 ResNet152V2&#xff09;进行乳腺肿块分类…

单向桥式整流电容滤波电路

假设&#xff1a;1.忽略整流电路内阻&#xff1b;2. 足够大。 一、空载情况 刚开始上升&#xff0c;通过D1、D3给后面供电&#xff0c;这种情况下电容就要被充电&#xff0c;根据前面的假设&#xff0c;设整流电路没有内阻&#xff0c;所以电容充电速度非常快&#xff0c;随着…

Java—集合简述

集合类继承结构图 Collection|---------------------| | | Set List Queue| | | SortedSet ArrayList Deque| LinkedList | NavigableSet Vector ArrayDeque| Stack | TreeSet …

外卖跑腿APP开发指南:探索同城O2O系统源码技术要点

同城O2O系统作为这类服务的技术支撑平台&#xff0c;承载了外卖跑腿APP的开发与运行。本篇文章&#xff0c;小编将深入探讨同城O2O系统源码的技术要点&#xff0c;为外卖跑腿APP的开发提供指导与参考。 一、同城O2O系统概述 同城O2O系统是一种基于地理位置的线上到线下服务平台…

centos7.9部署k8s的几种方式

文章目录 一、常见的k8s部署方式1、使用kubeadm工具部署2、基于二进制文件的部署方式3、云服务提供商的托管 Kubernetes 服务4、使用容器镜像部署或自动化部署工具 二、使用kubeadm工具部署1、硬件准备&#xff08;虚拟主机&#xff09;2、环境准备2.1、所有机器关闭防火墙2.2、…