FreeRTOS_内存管理

目录

1. 内存管理简介

2. 内存碎片

3. heap_1 内存分配方法

3.1 分配方法简介

4. heap_2 内存分配方法

4.1 分配方法简介

4.2 内存块详解

5. heap_4 内存分配方法

6. FreeRTOS 内存管理实验

6.1 实验程序


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

1. 内存管理简介

        FreeRTOS 创建任务、队列、信号量等的时候有两种方法,一种是动态的申请所需的 RAM。一种是由用户自行定义所需的 RAM,这种方法也叫静态方法,使用静态方法的函数一般以 “Static” 结尾,比如任务创建函数 xTaskCreateStatic(),使用此函数创建任务的时候需要由用户定义任务堆栈。

        使用动态内存管理的时候 FreeRTOS 内核在创建任务、队列、信号量的时候会动态的申请 RAM。标准 C 库中的 malloc() 和 free() 也可以实现动态内存管理,但是如下原因限制了其作用:

  •         在小型的嵌入式系统中效率不高。
  •         会占用很多的代码空间。
  •         它们不是线程安全的。
  •         具有不确定性,每次执行的时间不同。
  •         会导致内存碎片。
  •         使链接器的配置变得复杂。

        不同的嵌入式系统对于内存分配和时间要求不同,因此一个内存分配算法可以作为系统的可选选项。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 源码中。

2. 内存碎片

        学习 FreeRTOS 的内存分配方法之前我们先来看一下什么叫做内存碎片,顾名思义就是小块的、碎片化的内存。那么内存碎片是怎么来的呢?内存碎片是随着内存申请和释放而来的

(1)此时内存堆还没有经过任何操作,为全新的。

(2)此时经过第一次内存分配,一共分出去了 4 块内存块,大小分别为 80B、80B、10B 和 100B。

(3)有些应用使用完内存,进行了释放,从左往右第一个 80B 和后面的 10B 这两个内存块就是释放的内存。如果此时有个应用需要 50B 的内存,那么它可以从两个地方来获取到,一个是最前面的还没被分配过的剩余内存块,另一个就是刚刚释放出来的 80B 的内存块。但是很明显,刚刚释放出来的这个 10B 的内存块就没法用了,除非此时有另外一个应用所需要的内存小于 10B。

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

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

3. heap_1 内存分配方法

3.1 分配方法简介

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

#if(configAPPCATION_ALLOCATED_HEAP==1)
    extern uint8_t ucHeap[configTOTAL_HEAP_SIZE];    //需要用户自行定义内存堆
#else
    static uint8_t ucHeap[configTOTAL_HEAP_SIZE];    //编译器决定
#endif

        当宏 configAPPLICATION_ALLOCATED_HEAP 为 1 的时候需要用户自行定义内存堆,否则的话由编译器来决定,默认都是由编译器来决定的。如果自己定义的话就可以将内存堆定义到外部 SRAM 或者 SDRAM 中。

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

        heap_1 特性如下:

        1. 适用于那些一旦创建好任务、信号量和队列就再也不会删除的应用,实际上大多数的 FreeRTOS 应用都是这样的。

        2. 具有可确定性(执行所花费的时间大多数都是一样的),而且不会导致内存碎片。

        3. 代码实现和内存分配过程都非常简单,内存是从一个静态数组中分配到的,也就是适合于那些不需要动态内存分配的应用。

4. heap_2 内存分配方法

4.1 分配方法简介

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

        heap_2 特性如下:

        1. 可以使用那些可能会重复的删除任务、队列、信号量等的应用中,要注意有内存碎片产生!

        2. 如果分配和释放的内存 n 大小是随机的,那么就要慎重使用了,比如下面的示例:

  •         如果一个应用动态的创建和删除任务,而且任务需要分配的堆栈大小都是一样的,那么 heap_2 就非常合适。如果任务所需的堆栈大小每次都不同,那么 heap_2 就不合适了,因为这样会导致内存碎片的产生,最终导致任务分配不到合适的堆栈!不过 heap_4 就很适合这种情景了。
  •         如果一个应用中所使用的队列存储区域每次都不同,那么 heap_2 就不合适了。
  •         应用需要调用 pvPortMalloc() 和 vPortFree() 来申请和释放内存,而不是通过 FreeRTOS 的其他 API 函数来间接调用,这种情况下 heap_2 不适合。

        3. 如果应用中的任务、队列、信号量和互斥信号量具有不可预料性(如所需的内存大小不能确定,每次所需的内存都不相同,或者说大多数情况下所需的内存都是不同的)的话可能会导致内存碎片。

        4. 具有不确定性,但是也远比标准 C 中的 malloc() 和 free() 效率高!

        heap_2 基本上可以适用于大多数的需要动态分配内存的工程中,而 heap_4 更是具有将内存碎片合并成一个大的空闲内存块(就是内存碎片回收)的功能。

4.2 内存块详解

        同 heap_1 一样,heap_2 整个内存堆为 ucHeap[],大小为 configTOTAL_HEAP_SIZE。可以通过函数 xPortGetFreeHeapSize() 来获取剩余的内存大小

        为了实现内存释放,heap_2 引入了内存块的概念,每分出去的一段内存就是一个内存块,剩下的空闲内存也是一个内存块,内存块大小不定。为了管理内存块又引入了一个链表结构,链表结构如下:

typedef struct A_BLOCK_LINK
{
    struct A_BLOCK_LINK *pxNextFreeBlock;    //指向链表中下一个空闲内存块
    size_t xBlockSize;                       //当前空闲内存块大小
}BlockLink_t;    

        每个内存块前面都会有一个 BlockLink_t 类型的变量来描述此内存块,比如我们现在申请了一个 16 个字节的内存块,那么此内存块结构如下图所示:

        上图中内存块的总大小是 24 个字节,虽然我们只申请了 16 个字节,但是还需要另外 8 个字节来保存 BlockLink_t 类型的结构体变量,xBlockSize 记录的是整个内存块的大小。

        为了方便管理,可用的内存块会被全部组织在一个链表内,局部静态变量 xStart,xEnd 用来记录这个链表的头和尾,这两个变量的定义如下:

static BlockLink_t xStart,xEnd;

5. heap_4 内存分配方法

        heap_4 提供了一个最优的匹配算法,不像 heap_2,heap_4 会将内存碎片合并成一个大的可用内存块,它提供了内存块合并算法。内存堆为 ucHeap[],大小同样为 configTOTAL_HEAP_SIZE。可以通过函数 xPortGetFreeHeapSize() 来获取剩余的内存大小。

        heap_4 特性如下:

        1. 可以用在那些需要重复创建和删除任务、队列、信号量和互斥信号量等的应用中。

        2. 不会像 heap_2 那样产生严重的内存碎片,即使分配的内存大小是随机的。

        3. 具有不确定性,但是远比 C 标准库中的 malloc() 和 free() 函数效率高。

        heap_4 非常适合于那些需要直接调用函数 pvPortMalloc() 和 vPortFree() 来申请和释放内存的应用,注意,我们移植 FreeRTOS 的时候就选择 heap_4!

        heap_4 也使用链表结构来管理空闲内存块,链表结构体与 heap_2 一样。heap_4 也定义了两个局部静态变量 xStart 和 pxEnd 来表示链表头和尾,其中 pxEnd 是指向 BlockLink_t 的指针。

6. FreeRTOS 内存管理实验

        本节我们设计一个小程序来学习 FreeRTOS 的内存申请和释放函数:pvPortMalloc()、vPortFree(),并且观察申请和释放的过程中内存大小的变化情况。

        本实验设计两个任务:start_task 和 malloc_task,这两个任务的任务功能如下:

        start_task:用来创建另外一个任务。

        malloc_task:此任务用于完成内存的申请、释放和使用功能。任务会不断的获取按键情况,当检测到 KEY_UP 按下的时候就会申请内存,当 KEY0 按下以后就会使用申请到的内存,如果检测到 KEY1 按下的话就会释放申请到的内存。

        实验中会用到 3 个按键:KEY0、KEY1 和 KEY_UP,KEY_UP 用于申请内存,KEY0 使用申请到的内存,KEY1 释放申请到的内存。

6.1 实验程序

#include "stm32f4xx.h"  
#include "FreeRTOS.h" //这里注意必须先引用FreeRTOS的头文件,然后再引用task.h
#include "task.h"     //存在一个先后的关系
#include "LED.h"
#include "LCD.h"
#include "Key.h"
#include "usart.h"
#include "delay.h"
#include "string.h"
#include "beep.h"
#include "malloc.h"
#include "timer.h"


//任务优先级
#define START_TASK_PRIO     1   //用来创建另外一个任务。
//任务堆栈大小
#define START_STK_SIZE      128
//任务句柄
TaskHandle_t StartTask_Handler;
//任务函数
void start_task(void *pvParameters);

//任务优先级
#define MALLOC_TASK_PRIO    2   //此任务用于完成内存的申请、释放和使用功能。
                                //任务会不断的获取按键情况,当检测到 KEY_UP 按下的时候就会申请内存,
                                //当 KEY0 按下以后就会使用申请到的内存,如果检测到 KEY1 按下的话就会释放申请到的内存。
//任务堆栈大小
#define MALLOC_STK_SIZE     128
//任务句柄
TaskHandle_t MallocTask_Handler;
//任务函数
void malloc_task(void *p_arg);

int main(void)
{
    NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);     //设置系统中断优先级分组4
    delay_init(168);
    uart_init(115200);
    LED_Init();
    KEY_Init();
    LCD_Init();
    my_mem_init(SRAMIN);        //初始化内部内存池
    
    POINT_COLOR = RED;
    LCD_ShowString(30,10,200,16,16,"ATK STM32F407");
    LCD_ShowString(30,30,200,16,16,"FreeRTOS Example");
    LCD_ShowString(30,50,200,16,16,"Memory Manage");
    LCD_ShowString(30,70,200,16,16,"KEY_UP:Malloc,KEY1:Free");
    LCD_ShowString(30,90,200,16,16,"KEY0:Use Memory");
    LCD_ShowString(30,110,200,16,16,"ATOM@ALIENTEK");
    LCD_ShowString(30,130,200,16,16,"2023/11/09");
    
    LCD_ShowString(30,170,200,16,16,"Total Mem:      Bytes");   //总共内存大小
    LCD_ShowString(30,190,200,16,16,"Free  Mem:      Bytes");   //剩余内存大小
    LCD_ShowString(30,210,200,16,16,"Message:    ");            //内存中存储的信息
    POINT_COLOR = BLUE;
    
    //创建开始任务
    xTaskCreate((TaskFunction_t )start_task,            //任务函数
                (const char*    )"start_task",          //任务名称
                (uint16_t       )START_STK_SIZE,        //任务堆栈大小
                (void*          )NULL,                  //传递给任务函数的参数
                (UBaseType_t    )START_TASK_PRIO,       //任务优先级
                (TaskHandle_t*  )&StartTask_Handler);   //任务句柄
    vTaskStartScheduler();                              //开启任务调度
}

//开始任务任务函数
void start_task(void *pvParameters)
{
    taskENTER_CRITICAL();           //进入临界区
    //创建Malloc任务函数
    xTaskCreate((TaskFunction_t )malloc_task,           //任务函数
                (const char*    )"malloc_task",         //任务名称
                (uint16_t       )MALLOC_STK_SIZE,       //任务堆栈大小
                (void*          )NULL,                  //传递给任务函数的参数
                (UBaseType_t    )MALLOC_TASK_PRIO,      //任务优先级
                (TaskHandle_t*  )&MallocTask_Handler);  //任务句柄
    vTaskDelete(StartTask_Handler); //删除开始任务                 
    taskEXIT_CRITICAL();            //离开临界区
}

//Malloc任务函数
void malloc_task(void *pvParameters)
{
    u8 *buffer;
    u8 times,i,key=0;
    u32 freemen;
    
    LCD_ShowxNum(110,170,configTOTAL_HEAP_SIZE,5,16,0); //显示内存总容量  8*10+30=110
    
    while(1)
    {
        key=KEY_Scan(0);
        switch(key)
        {
            case WKUP_PRES:
                buffer=pvPortMalloc(30);
                printf("申请到的内存地址为:%#x\r\n",(int)buffer); //“#x”表示将整数值以十六进制格式输出,并且在输出结果前添加“0x”前缀
                break;
            case KEY1_PRES:
                if(buffer!=NULL)
                    vPortFree(buffer);  //释放内存
                    buffer=NULL;
                    break;
            case KEY0_PRES:
                if(buffer!=NULL)    //buffer内存可用,使用内存
                {
                    times++;
                    sprintf((char*)buffer,"User %d Times",times);   //向buffer中填写一些数据
                    LCD_ShowString(94,210,200,16,16,buffer);    
                }
                break;
        }
        freemen=xPortGetFreeHeapSize();     //获取剩余内存大小
        LCD_ShowxNum(110,190,freemen,5,16,0);   //显示内存总容量
        i++;
        if(i==50)
        {
            i=0;
            LED0=~LED0;
        }
        vTaskDelay(10);
    }
}


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

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

相关文章

【刚体姿态运动学】角速度和欧拉角速率的换算关系的详细推导

0 引言 本文以一种新的角度推导刚体姿态运动学,也即角速度和欧拉角速率之间的换算,不同于相似博文的地方在于,本文旨在从原理上给出直观清晰生动的解释。将详细过程记录于此,便于后续学习科研查找需要。 1 符号 符号含义 { E }…

STM32 GPIO

STM32 GPIO GPIO简介 GPIO(General Purpose Input Output)通用输入输出口,也就是我们俗称的IO口 根据使用场景,可配置为8种输入输出模式 引脚电平:0V~3.3V,部分引脚可容忍5V 数据0就是低电平&#xff0c…

一篇带你精通php

华子目录 什么是phpphp发展史平台支持和数据库支持网站静态网站和动态网站的区别静态网站动态网站的特点 关键名词解析服务器概念IP的概念域名DNS端口 web程序的访问流程静态网站访问流程动态网站访问流程 php标记脚本标记标准标记(常用) php注释 什么是…

Linux Hadoop平台伪分布式安装

Linux Hadoop 伪分布式安装 1. JDK2. Hadoop3. MysqlHive3.1 Mysql8安装3.2 Hive安装 4. Spark4.1 Maven安装4.2 Scala安装4.3 Spark编译并安装 5. Zookeeper6. HBase 版本概要: jdk: jdk-8u391-linux-x64.tar.gzhadoop:hadoop-3.3.1.tar.gzh…

Spring Ioc 容器启动流程

Spring容器的启动流程 本文基于 Spring 5.3.23 基于XML文件 public void test() {ApplicationContext applicationContext new ClassPathXmlApplicationContext("applicationContext.xml");User user applicationContext.getBean("user", User.class)…

MySQL大表数据导入到MongoDB

修改参数 &#xff0c;开启into outfile的功能 secure_file_priv/home/backups/mysql_outfile 重启数据库是参数生效 按条件导出MySQL数据 select * from receipt_receive_log where gmt_create > 2020-04-13 00:00:00 and gmt_create< 2020-07-13 00:00:00 INTO O…

微信小程序真机调试连接状态一直在正常和未链接之间反复横跳?

背景&#xff1a;小程序真机调试的时候&#xff0c;发现真机的network不显示接口调用情况&#xff0c;控制台也没有输出内容。具体如下所示&#xff1b; 解决方法&#xff1a; 1、确保手机端连接的网络和微信开发者工具网络一致&#xff0c;比如用同一个WiFi 2、真机自动调试…

极狐GitLab CI 助力 .Net 项目研发效率和质量双提升

目录 .NET nuget 自动生成测试包&#xff08;prerelease&#xff09;版本号 .NET 版本号规范 持续集成自动打包 持续集成自动修改版本号 .NET 行级增量代码规范——拯救老项目 本地全量代码规范 行级增量代码规范 很多团队或开发者都会使用 C#、VB 等语言开发 .Net 应用…

ROS源码安装应用,VSCode

ROS源码安装应用 安装一下VSCode 前置文章 到安装程序的目录中: 完成克隆 编译 catkin_make打开ros核心 roscore打开应用程序 rosrun turtlesim turtlesim_node安装一下VSCode deb下载地址 sudo dpkg -i code_1.84.1-1699275408_amd64.deb添加项目工程到工作空间&#xff…

MyBatis中文网

MyBatis中文网https://mybatis.net.cn/ MyBatis 是一款优秀的持久层框架&#xff0c;它支持自定义 SQL、存储过程以及高级映射。MyBatis 免除了几乎所有的 JDBC 代码以及设置参数和获取结果集的工作。MyBatis 可以通过简单的 XML 或注解来配置和映射原始类型、接口和 Ja…

go语法入门2

字符串 使用双引号或反引号引起来的任意个字符。它是字面常量。 func main() {var a "abc\n测试" // \n换行fmt.Println(a) } abc 测试func main() {var a "abc\n\t测试" \\换行后在tabfmt.Println(a) } abc测试func main() {var a abc测试 …

MySQL binlog 日志解析后的exec_time导致表示什么时间?

1. exec_time 到底表示什么时间&#xff1f; MySQL binlog日志解析后&#xff0c;我们能看到会有 exec_time &#xff0c;从字面意思理解这个记录的是执行时间&#xff0c;那这个记录的到底是单条sql的执行时间&#xff1f;还是事务的执行时间&#xff1f;下面通过测试来解读一…

ruoyi前后端分离版本开发框架解读---让你快速入门

后端结构 com.ruoyi ├── common // 工具类 │ └── annotation // 自定义注解 │ └── config // 全局配置 │ └── constant // 通用常量 │ └── core …

AcWing99. 激光炸弹

题目 地图上有 N N N 个目标&#xff0c;用整数 X i , Y i X_i,Y_i Xi​,Yi​ 表示目标在地图上的位置&#xff0c;每个目标都有一个价值 W i W_i Wi​。 注意&#xff1a;不同目标可能在同一位置。 现在有一种新型的激光炸弹&#xff0c;可以摧毁一个包含 R R RR RR 个…

“Git 在团队协作中的优化实践“

文章目录 引言&#xff1a;一、Git 简介1.1 Git 基本概念1.2 Git 原理与工作流程 二、 Git 与 SVN 的区别三、Git 的常用命令及操作四、Git 的理论知识&#xff1a;总结&#xff1a; 引言&#xff1a; 随着技术的不断演进和团队的不断发展&#xff0c;代码管理变得越来越重要。…

不用流氓软件,如何在户外使用手机听下载到家中电脑里的音乐文件呢?

文章目录 本教程解决的问题是&#xff1a;按照本教程方法操作后&#xff0c;达到的效果是本教程使用环境&#xff1a;1 群晖系统安装audiostation套件2 下载移动端app 很多老铁想在上班路上听点喜欢的歌或者相声解解闷儿&#xff0c;于是打开手机上的某雅软件和某音乐软件点进去…

2.docker镜像的导入导出

目录 概述docker 常用命令下载导出导入镜像结束 概述 docker 常用命令 本章节使用到的命令&#xff0c;总结在此&#xff0c;后面有使用案例。 命令作用docker images显示镜像docker rmi $(docker images -q)删除系统上所有的镜像docker rmi -f强制删除多个镜像 &#xff1a…

istio 学习笔记

参考&#xff1a;istio简介和基础组件原理&#xff08;服务网格Service Mesh&#xff09;-CSDN博客 Istio 微服务框架 服务治理。 Istio的关键功能: HTTP/1.1&#xff0c;HTTP/2&#xff0c;gRPC和TCP流量的自动区域感知负载平衡和故障切换。 通过丰富的路由规则&#xf…

module ‘torch‘ has no attribute ‘_six‘

主要问题是torchvision的问题 在122服务器上的scvi-env2环境中 import torch import torch.nn as nnimport numpy as npfrom tqdm import tqdm from torchvision.utils import save_image, make_grid # Model Hyperparametersdataset_path ./datasetscuda True DEVICE tor…

小白学爬虫:通过商品ID获取1688跨境属性数据接口|1688商品属性接口|1688一件代发数据接口|1688商品详情接口

通过商品ID获取1688跨境属性数据接口可以使用1688开放平台提供的API接口实现。以下是获取跨境属性数据的基本步骤&#xff1a; 1、点击获取测试key和secret 2、构造请求参数&#xff0c;包括商品ID和其他必要参数&#xff0c;如接口权限、请求类型等。 3、通过API接口链接&…