四、内存管理

news2024/11/19 8:22:20

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

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

(2)内存的动态管理是C程序的知识范畴,并不属于FreeRTOS的知识范畴,但是它跟FreeRTOS紧密相关。

(3)在C语言的库函数中,有mallc、free等函数管理内存,但是在FreeRTOS中,它们不适用。

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

2、堆栈

        我们经常"堆栈"混合着说,但堆和栈是不同的东西。

2.1、堆

(1)堆在计算机中有两种理解,这里介绍的是动态内存管理机制。

  • 堆是一种数据结构。
  • 堆是一种动态内存管理机制。

(2)堆是一种动态内存管理机制,它允许程序在运行时动态地分配和释放内存。堆管理器提供了一组函数或操作,用于分配和释放堆内存。堆内存可以被程序中的不同部分共享,并且可以在程序运行时动态地分配和释放。

(3)Keil工程在中文目录下,仿真退出会出错。

2.2、栈

(1)栈(Stack)是一种只能在一端进行插入和删除操作的数据结构,具有先进后出的特性。

(2)在RTOS中,每个任务都得有自己得栈。

(3)栈的功能。

  • 内存管理:在编程中,栈通常用于管理内存。当程序调用一个函数时,函数的参数、返回地址会被压入栈中,变量也会被存储在栈中。当函数返回时,这些值将从栈中弹出,释放内存。这种方式简化了内存管理,确保不会提前释放在占用状态下的内存空间。
  • 表达式求值:栈可以用于解决表达式求值问题。通常情况下,表达式中的操作符和操作数必须按照特定的规则进行计算。栈可以帮助记录操作符状态,并计算操作数。
  • 问题的回溯:栈也常用于问题的回溯,即在出现错误或面临问题时,通过回溯栈中的信息来了解问题发生的情况。
  • 操作系统多任务模式:栈是构建出操作系统多任务模式的基础,但在具体的实现中,需要用汇编代码来实现栈的切换。

3、FreeRTOS中的5种内存管理方法

        FreeRTOS中内存管理的接口函数为:pvPortMalloc 、vPortFree,对应于C库的malloc、free。文件在 FreeRTOS/Source/portable/MemMang 下,它也是放在 portable 目录下,表示你可以提供自己的函数。源码中默认提供了5个文件,对应内存管理的5种方法。

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

参考内容:《掌握FreeRTOS实时内核-实操教程》

3.1、Heap_1

(1)Heap_1只实现了pvPortMalloc,没有实现vPortFree。如果程序不需要删除内核对象,那么可以使用heap_1。

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

(2)它的实现原理很简单,首先定义一个大数组,然后,对于pvPortMalloc调用时,从数组中分配空间。

/*为堆分配内存*/
##if ( configAPPLICATION_ALLOCATED_HEAP == 1 )
/*应用程序的作者已经定义了用于RTOS堆的数组——可能是为了把它放在一个特殊的段或地址中*/
extern uint8_t ucHeap[ configTOTAL_HEAP_SIZE ];
##else
static uint8_t ucHeap[ configTOTAL_HEAP_SIZE ];
##endif /*配置应用程序分配堆*/

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

  • A:创建任务之前整个数组都是空闲的。
  • B:创建第1个任务之后,蓝色区域被分配出去了。
  • C:创建3个任务之后的数组使用情况。

3.2、Heap_2

(1)Heap_2之所以还保留,只是为了兼容以前的代码。新设计中不再推荐使用Heap_2。建议使用Heap_4来替代Heap_2,更加高效。

(2)Heap_2也是在数组上分配内存,跟Heap_1不一样的地方在于:

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

(3)最佳匹配算法:

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

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

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

  • A:创建了3个任务。
  • B:删除了一个任务,空闲内存有3部分:顶层的、被删除任务的TCB空间、被删除任务的Stack空间 。
  • C:创建了一个新任务,因为TCB、栈大小跟前面被删除任务的TCB、栈大小一致,所以刚好分配到原来的内存。

3.3、Heap_3

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

3.4、Heap_4

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

(2)首次适应算法:

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

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

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

(5)Heap_4的使用过程举例如下:

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

3.5、Heap_5

(1)Heap_5分配内存、释放内存的算法跟Heap_4是一样的。相比于Heap_4,Heap_5并不局限于管理一个大数组:它可以管理多块、分隔开的内存。

(2)在嵌入式系统中,内存的地址可能并不连续,这种场景下可以使用Heap_5。

(3)既然内存时分隔开的,那么就需要进行初始化:确定这些内存块在哪、多大:

  • 在使用pvPortMalloc之前,必须先指定内存块的信息。
  • 使用vPortDefineHeapRegions来指定这些信息。

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

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

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

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

(6)vPortDefineHeapRegions函数原型如下,把xHeapRegions数组传给vPortDefineHeapRegions函数,即可初始化Heap_5。

void vPortDefineHeapRegions( const HeapRegion_t * const pxHeapRegions );

4、Heap相关的函数

4.1、pvPortMalloc/vPortFree

(1)函数原型:

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

(2)作用:分配内存、释放内存。
(3)如果分配内存不成功,则返回值为NULL。

4.2、xPortGetFreeHeapSize

(1)函数原型:

size_t xPortGetFreeHeapSize( void );

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

4.3、xPortGetMinimumEverFreeHeapSize

(1)函数原型:

size_t xPortGetMinimumEverFreeHeapSize( void );

(2)返回:程序运行过程中,空闲内存容量的最小值。
(3)注意:只有heap_4、heap_5支持此函数。

4.4、malloc失败的钩子函数

(1)在pvPortMalloc函数内部:

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

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

  • 在FreeRTOSConfig.h中,把configUSE_MALLOC_FAILED_HOOK定义为1。
  • 提供vApplicationMallocFailedHook函数。
  • pvPortMalloc失败时,才会调用此函数

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

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

相关文章

使用yolov5进行安全帽检测填坑指南

参考项目 c​​​​​​​​​​​​​​GitHub - PeterH0323/Smart_Construction: Base on YOLOv5 Head Person Helmet Detection on Construction Sites,基于目标检测工地安全帽和禁入危险区域识别系统,🚀😆附 YOLOv5 训练自己的…

Unity导入google.protobuf失败,无法找到google命名空间

问题: 1.刚开始把protobuf的文件夹直接从其他项目里(unity2021)里复制到unity(2020)版本,当时报错protobuf.dll的依赖项system.memory版本不对。 2.没有使用原来的protobuf文件了。使用vs2019的NuGet管理包来下载Google.Protobuf ,仍然报错找…

爬虫学得好,然后呢?最新Python人工智能就业班课程

课程链接: 私信:达内 课程介绍: 【达内最新Python人工智能就业班课程目录】 📚 1. Python核心 🧠 2. 面向对象程序设计 🔮 3. Python高级 💻 4. 阶段项目实战 🐧 5. Linux操作系…

PHP海外代购管理系统mysql数据库web结构apache计算机软件工程网页wamp

一、源码特点 PHP 海外代购管理系统是一套完善的web设计系统,对理解php编程开发语言有帮助,系统具有完整的源代码和数据库,系统主要采用B/S模式开发。 代码下载 https://download.csdn.net/download/qq_41221322/88229435 论文 https://…

python的全局解释锁(GIL)

一、介绍 全局解释锁(Global Interpreter Lock,GIL)是在某些编程语言的解释器中使用的一种机制。在Python中,GIL是为了保证解释器线程安全而引入的。 GIL的作用是在解释器的执行过程中,确保同一时间只有一个线程可以…

win11安装ubuntu 子系统安装过程及注意事项

第一步 :安装系统必须组件 由于子系统是系统自带组件,需要安装软件支持 第二步:应用商店安装 ubuntu 编辑 编辑 这个时候打开会报错 第三步,运行linux子系统 选择Windows PowerShell 以管理员身份运行) 输入&#…

docker基础操作练习

目录 1.安装docker服务,配置镜像加速器 2.下载系统镜像(Ubuntu、 centos) 3.基于下载的镜像创建两个容器 (容器名一个为自己名字全拼,一个为首名字字母) 4.容器的启动、 停止及重启操作 5.怎么查看正在…

微信个人号二次开发过程、微信ipad协议

友情链接:geweapi.com 点击即可访问 大家好,今天给大家介绍下ipad的具体情况以及特点 傻瓜式API,掌握JAVA、Go、PHP、Python等任意一种后端代码,你就可以 通过API 搭建一个 微信机器人功能 ,用来自动管理微信消息 我们…

常用curl参数及样例讲解

1 缘起 后端/后台项目开发过程中,有两个阶段的接口测试和验证,自测阶段,通过Postman构建请求, 自建一些参数,测试功能以及边界条件,这些都是可以自行掌控的,当完成功能验证与前端对接时&#x…

以创新点亮前路,戴尔科技开辟数实融合新格局

编辑:阿冒 设计:沐由 2023年,对于戴尔科技而言是特殊的一年,这是戴尔科技进入中国市场第25个年头——“巧合”的是,这25年也是中国产业经济发展最快,人们工作与生活发生变化最大的四分之一个世纪。 2023年&…

C# 观察者模式

一、概述 观察者模式是一种常用的设计模式,它属于行为型模式。在C#中,观察者模式通过定义一种一对多的依赖关系,使得当一个对象的状态发生变化时,所有依赖于它的对象都会得到通知并自动更新。这种模式可以实现松耦合,…

关键点检测中的对象关键点相似度

在不断发展的计算机视觉领域,理解物体的精确结构和姿态至关重要。无论是检测杂乱场景中的特定物体,还是实时分析人体姿势,关键点都起着至关重要的作用。对象上的这些独特点通常对应于角、边缘或其他可识别部分,用作识别和跟踪对象的锚点。但是我们如何衡量这些检测到的关键…

Java版本说明

java7 当谈到Java 7对应的JDK版本时,Java SE 7是Java 7的官方JDK版本。Java SE(Standard Edition)是Java平台的标准版本,它提供了Java编程语言的核心库和工具。 Java SE 7的JDK版本是JDK 7。你可以通过以下链接下载Java SE 7的J…

最新AI系统ChatGPT程序源码/支持GPT4/自定义训练知识库/GPT联网/支持ai绘画(Midjourney)+Dall-E2绘画/支持MJ以图生图

一、前言 SparkAi系统是基于国外很火的ChatGPT进行开发的Ai智能问答系统。本期针对源码系统整体测试下来非常完美,可以说SparkAi是目前国内一款的ChatGPT对接OpenAI软件系统。 那么如何搭建部署AI创作ChatGPT?小编这里写一个详细图文教程吧&#xff01…

Redis中常见的缓存穿透、缓存击穿、缓存雪崩、缓存预热解决方案

文章目录 一、缓存穿透1. 什么是缓存穿透2. 解决方案2.1 无效的key存放到Redis2.2 引入布隆过滤器2.3 如何选择: 二、缓存击穿1. 什么是缓存击穿2. 解决方案 三、缓存雪崩1. 什么是缓存雪崩2. 解决方案2.1 均匀过期2.2 热点数据缓存永远不过期2.3 采取限流降级的策略…

注册中心/配置管理 —— SpringCloud Consul

Consul 概述 Consul 是一个可以提供服务发现,健康检查,多数据中心,key/Value 存储的分布式服务框架,用于实现分布式系统的发现与配置。Cousul 使用 Go 语言实现,因此天然具有可移植性,安装包仅包含一个可执…

【C++学习手札】一文带你认识C++虚继承​​

食用指南:本文在有C基础的情况下食用更佳 🍀本文前置知识:C虚函数(很重要,内部剖析) ♈️今日夜电波:僕らのつづき—柊優花 1:06 ━━━━━━️💟──────── 3:51 …

将Nginx源码数组结构(ngx_array.c)和内存池代码单独编译运行,附代码

在上面一篇的基础上把Nginx源码数组结构也摘录下来,也增加了测试代码,编译运行。 https://blog.csdn.net/katerdaisy/article/details/132358883 《将nginx内存池代码单独编译运行,了解nginx内存池工作原理,附代码》 核心代码&…

C语言刷题训练DAY.8

1.计算单位阶跃函数 解题思路&#xff1a; 这个非常简单&#xff0c;只需要if else语句即可完成 解题代码&#xff1a; #include <stdio.h>int main() {int t 0;while(scanf("%d",&t)!EOF){if (t > 0)printf("1\n");else if (t < 0)pr…

LVS-DR集群(一台LVS,一台CIP,两台web,一台NFS)的构建以及LVS-DR模式工作原理和特点

一.LVS-DR工作模式原理和特点 1.工作模式 2.模式特点 二.构建环境 1.五台关闭防火墙&#xff0c;关闭selinux&#xff0c;拥有固定IP&#xff0c;部署有http服务的虚拟机&#xff0c;LVS设备下载ipvsadm工具&#xff0c;NFS 设备需要下载rpcbind和nfs-utils 2.实现功能 3…