STM32F1+HAL库+FreeTOTS学习21——内存管理

STM32F1+HAL库+FreeTOTS学习21——内存管理

  • 1. 内存管理简介
  • 2. 内存管理相关的API函数
  • 3. 内存管理算法
  • 4. 内存管理实验
    • 4.1. 实验内容
    • 4.2 代码实现
    • 4.3 运行结果
  • 5. 总结

上一期我们学习了FreeRTOS中的低功耗Tickless模式,这一期我们学习最后一个章节:内存管理

1. 内存管理简介

存管理是一个系统的基本组成部分,在 FreeRTOS 中大量地使用了内存管理,比如创建任务、信号量、队列等对象时,都可以从 FreeRTOS 管理的堆中申请内存。FreeRTOS 也向用户提供了应用层的内存申请与释放函数。

但是我们在学习C语言的时候,也会用到一些内存管理的函数,比如malloc、realloc、free、memcpy等等,只需要我们去包含否文件 <stdlib.h>即可,非常方便,为什么要单独开辟一个章节来介绍FreeRTOS中的内存管理呢,直接用C库的不好吗。其实这里面是有门道的,且听我娓娓道来:

使用FreeRTOS自己的内存管理而不用C库的原因有下面几点:

  1. 实时性要求:FreeRTOS 是为嵌入式系统设计的实时操作系统,内存管理的实时性至关重要。标准 C 库的动态内存分配(如 malloc 和 free)通常会引入不确定的延迟,可能导致任务的响应时间不一致,从而影响实时性能。

  2. 安全性:使用C库的内存管理方法,没有线程安全的相关机制,可能会引入不必要的问题。

  3. 内存碎片化:在嵌入式系统中,内存资源通常非常有限。标准的 C 库内存管理可能导致内存碎片化,降低可用内存。而 FreeRTOS 的内存管理机制可以通过固定大小的块分配和管理来减小碎片化的风险。

  4. 可定制性:FreeRTOS 提供了不同的内存分配策略,允许开发者根据特定应用的需求进行定制。这种灵活性使得开发者可以选择最适合其系统和应用场景的内存管理方案。

  5. 小型化和优化:FreeRTOS 设计时考虑了嵌入式系统的资源限制,因此其内存管理方案相对简洁,开销小,能够更好地适应低功耗和小型设备的需求。

  6. 简单性:在一些简单的应用中,FreeRTOS 的内存管理可以提供比标准库更简单的接口,降低了开发的复杂性。

鉴于以上为种种问题,FreeRTOS中提供了多种不同的内存管理方法,以适应不同嵌入式系统的需求,供用户自行选择,移植到自己的系统当中。

前面我们在介绍FreeRTOS移植的时候,就有过这样一步:

在这里插入图片描述
【注】:如果忘记了,可以回去看STM32F1+HAL库+FreeTOTS学习2——STM32移植FreeRTOS

这里面的MemMang存放的就是内存管理的方式,分别为:heap_1.c、heap_2.c、heap_3.c、heap_4.c、heap_5.c

在这里插入图片描述

下面我们来介绍以下这五种内存管理方法的特点:

  1. heap_1:最简单,只允许申请内存,不允许释放内存。
  2. heap_2:允许申请和释放内存,但不能合并相邻的空闲内存块。
  3. heap_3:简单封装 C 库的函数 malloc()和函数 free(),以确保线程安全。
  4. heap_4:允许申请和释放内存,并且能够合并相邻的空闲内存块,减少内存碎片的产生。
  5. heap_5:能够管理多个非连续内存区域的 heap_4。

无论我们使用的是哪一个内存管理方法,他们的API函数都是一样的,下面我们来看一下,FreeRTOS中的API函数

2. 内存管理相关的API函数

  1. pvPortMalloc函数
    此函数用于在FreeRTOS中申请内存,在portable.h文件中有定义,下面我们来看以下函数原型:
/*** @brief       pvPortMalloc:在FreeRTOS中申请内存* @param       xSize : 申请内存的大小* @retval      返回值为该内存的起始地址*/
void * pvPortMalloc( size_t xSize );
  1. vPortFree函数
    此函数用于在FreeRTOS中释放内存,在portable.h文件中有定义,下面我们来看以下函数原型:
/*** @brief       vPortFree:在FreeRTOS中释放内存* @param       pv : 释放内存的起始地址* @retval      无*/
void vPortFree( void * pv );
  1. xPortGetFreeHeapSize函数
    此函数用于在FreeRTOS中获取当下剩余的内存大小,在portable.h文件中有定义,下面我们来看以下函数原型:
/*** @brief       xPortGetFreeHeapSize:在FreeRTOS中获取当下剩余的内存大小* @param       无* @retval      返回值为剩余的内存大小*/
size_t xPortGetFreeHeapSize( void );
  1. xPortGetMinimumEverFreeHeapSize函数
    此函数用于在FreeRTOS中获取历史剩余的大小最小值,在portable.h文件中有定义,下面我们来看以下函数原型:
/*** @brief       xPortGetMinimumEverFreeHeapSize:在FreeRTOS中获取历史剩余的内存最小值* @param       无* @retval      返回值为历史剩余的内存最小值*/
size_t xPortGetFreeHeapSize( void );

上面就是FreeRTOS中内存管理的常用API函数。

3. 内存管理算法

前面我们说到:heap_1.c、heap_2.c、heap_3.c、heap_4.c、heap_5.c。这5个文件中对于内存管理的实现方式各不相同,归根结底就是使用了不同的内存管理方式(算法),但是我们这里不做过多的介绍,因为实在是太多了,感兴趣的可以自己去看一下:正点原子FreeRTOS开发指南中的内存管理章节,具体链接我会放在结尾。

4. 内存管理实验

4.1. 实验内容

在STM32F103RCT6上运行FreeRTOS,通过按键控制,完成对应的内存管理操作,具体要求如下:

  • 创建1个任务,用来按键扫描:
  • 按键0按下,申请内存
  • 按键1按下:释放内存
  • 每1s打印一次系统的堆栈剩余情况
  • 每完成一个步骤,通过串口打印相关信息。

4.2 代码实现

  • 由于本期内容涉及到按键扫描,会用到: STM32框架之按键扫描新思路 ,这里不做过多介绍。我们直接来看代码:
  1. freertos_demo.c
#include "freertos_demo.h"
#include "main.h"
#include "task.h"
#include "key.h"		//包含按键相关头文件/*FreeRTOS*********************************************************************************************//******************************************************************************************************/
/*FreeRTOS配置*//* TASK1 任务 配置* 包括: 任务句柄 任务优先级 堆栈大小 */
#define TASK1_PRIO      1                  /* 任务优先级 */
#define TASK1_STK_SIZE  128                 /* 任务堆栈大小 */
TaskHandle_t            Task1Task_Handler;  /* 任务句柄 */
void task1(void *pvParameters);					/*任务函数*//******************************************************************************************************//*** @brief       FreeRTOS例程入口函数* @param       无* @retval      无*/
void freertos_demo(void)
{taskENTER_CRITICAL();           /* 进入临界区,关闭中断,此时停止任务调度*//* 创建任务1 */xTaskCreate((TaskFunction_t )task1,(const char*    )"task1",(uint16_t       )TASK1_STK_SIZE,(void*          )NULL,(UBaseType_t    )TASK1_PRIO,(TaskHandle_t*  )&Task1Task_Handler);taskEXIT_CRITICAL();            /* 退出临界区,重新开启中断,开启任务调度 */vTaskStartScheduler();		//开启任务调度
}/*** @brief       task1* @param       pvParameters : 传入参数(未用到)* @retval      无*/
void task1(void *pvParameters)
{size_t freeSize = 0;		/* 空闲堆栈大小 */uint16_t count = 0;			while(1){Key_One_Scan(Key_Name_Key0,Key0_Up_Task,Key0_Down_Task);		/* 按键0扫描,按下申请内存堆栈 */Key_One_Scan(Key_Name_Key1,Key1_Up_Task,Key1_Down_Task);		/* 按键0扫描,按下释放内存堆栈 */if(++count > 100){/* 1s打印一次剩余堆栈空间 */freeSize = xPortGetFreeHeapSize();printf("剩余堆栈空间:%d\r\n",freeSize);count = 0;}vTaskDelay(10);}
}	
  1. key.c
/* USER CODE BEGIN 2 */#include "freertos_demo.h"
#include "key.h"
#include "usart.h"/* 申请的内存地址 */
uint8_t *buf = NULL;/* 按键0按下申请内存 */
void Key0_Down_Task(void)
{/* 申请大小为100的堆栈空间,返回值为堆栈空间的起点 */buf = pvPortMalloc(100);printf("bufAddress: %p\r\n",buf);
}void Key0_Up_Task(void)
{}/* 按键1按下释放内存 */
void Key1_Down_Task(void)
{/* 释放内存,如果buf为NULL,则直接返回 */vPortFree(buf);/* 正常释放之后需要将buf置为NULL,但是由于这里buf被重复使用,vPortFree()函数传入NULL,会直接返回,不会报错。为了使效果更明显,这里就补注释了*/
//	buf = NULL;printf("bufDeleted\r\n");
}
void Key1_Up_Task(void)
{}
void Key2_Down_Task(void)
{}
void Key2_Up_Task(void)
{}
void WKUP_Down_Task(void)
{}
void WWKUP_Up_Task(void)
{}void Key_One_Scan(uint8_t KeyName ,void(*OnKeyOneUp)(void), void(*OnKeyOneDown)(void))
{static uint8_t Key_Val[Key_Name_Max];    //按键值的存放位置static uint8_t Key_Flag[Key_Name_Max];   //KEY0~2为0时表示按下,为1表示松开,WKUP反之Key_Val[KeyName] = Key_Val[KeyName] <<1;  //每次扫描完,将上一次扫描的结果左移保存switch(KeyName){case Key_Name_Key0:  Key_Val[KeyName] = Key_Val[KeyName] | (HAL_GPIO_ReadPin(KEY0_GPIO_Port, KEY0_Pin));    //读取Key0按键值break;case Key_Name_Key1:  Key_Val[KeyName] = Key_Val[KeyName] | (HAL_GPIO_ReadPin(KEY1_GPIO_Port, KEY1_Pin));   //读取Key1按键值break;case Key_Name_Key2:  Key_Val[KeyName] = Key_Val[KeyName] | (HAL_GPIO_ReadPin(KEY2_GPIO_Port, KEY2_Pin));   //读取Key2按键值break;
//        case Key_Name_WKUP:  Key_Val[KeyName] = Key_Val[KeyName] | (HAL_GPIO_ReadPin(WKUP_GPIO_Port, WKUP_Pin));   //读取WKUP按键值
//            break; default:break;}
//    if(KeyName == Key_Name_WKUP)     //WKUP的电路图与其他按键不同,所以需要特殊处理
//    {
//        //WKUP特殊情况
//        //当按键标志为1(松开)是,判断是否按下,WKUP按下时为0xff
//        if(Key_Val[KeyName] == 0xff && Key_Flag[KeyName] == 1)
//        {
//            (*OnKeyOneDown)();
//           Key_Flag[KeyName] = 0;
//        }
//        //当按键标志位为0(按下),判断按键是否松开,WKUP松开时为0x00
//        if(Key_Val[KeyName] == 0x00 && Key_Flag[KeyName] == 0)
//        {
//            (*OnKeyOneUp)();
//           Key_Flag[KeyName] = 1;
//        } 
//    }
//    else                               //Key0~2按键逻辑判断
//    {//Key0~2常规判断//当按键标志为1(松开)是,判断是否按下if(Key_Val[KeyName] == 0x00 && Key_Flag[KeyName] == 1){(*OnKeyOneDown)();Key_Flag[KeyName] = 0;}//当按键标志位为0(按下),判断按键是否松开if(Key_Val[KeyName] == 0xff && Key_Flag[KeyName] == 0){(*OnKeyOneUp)();Key_Flag[KeyName] = 1;}  }//}
/* USER CODE END 2 */

4.3 运行结果

在这里插入图片描述
【注1】:运行结果中的报错是因为重复释放同一块内存导致的,代码中有解释过,需要注意一下。

【注2】:如果重复申请内存,但是获取内存地址的指针是同一个,对导致申请完内存之后的上一片内存丢失,而导致内存泄漏,这种情况需要避免发生。

5. 总结

不出意外的话,这一期将会是我们FreeRTOS入门系列的最后一期内容(当然后面如果遇到了相关的问题,我也会记录在这里面的),回想一路走来,感触颇深啊,不过也确实学到了很多内容,对FreeRTOS有了较为深入的理解。我把这段时间学习的内容整理了以下,放到gitee上面去了,如果有需要的话,自行食用哈(只适用于个人学习,出了问题概不负责!!!)。

在这里插入图片描述

这里是仓库地址:https://gitee.com/bu-xiang-xie-dai-ma-de-wo/free-rtos-code

以上就是本期的所有内容,创造不易,点个关注再走呗。

在这里插入图片描述

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

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

相关文章

Linux高手进阶

查看系统资源占用&#xff1a; top&#xff1a;查看CPU、内存使用情况&#xff0c;类似 windows 的任务管理器 默认 5秒刷新一次语法&#xff1a; 直接输入 top按 q 或 ctrl c 退出内容详解&#xff1a; 第一行&#xff1a; top&#xff1a;命令名称 -系统时间up 23:57 min &…

vue3学习记录-单文件组件 CSS 功能

vue3学习记录-单文件组件 CSS 功能 1.组件作用域 CSS1.1为什么要用到样式穿透&#xff08;&#xff1a;deep&#xff08;&#xff09;&#xff09;1.2 插槽选择器:slotted(div)1.3 全局选择器:global 2.CSS Modules2.1 基本用法2.2 自定义注入名称2.3 与组合式 API 一同使用 3.…

postman如何安装旧版本不升级(以9.31和11.10版本为例)

postman版本超过10.x&#xff08;包含10.x)&#xff0c;有个大的麻烦&#xff0c;就是需要登录账号&#xff0c;如果网络不佳&#xff08;其实是外网受限&#xff09;,那就很难受了 功能页面都进不去了&#xff01;而8.x /9.x等以下版本就不需要登录了。 比如9.31.30这个版本就…

Cesium基础-(Entity)-(Corridor 走廊)

里边包含Vue、React框架代码详细步骤、以及代码详细解释 4、Corridor 走廊 以下是 CorridorGeometry 类的属性、方法和静态方法,以表格形式展示: 属性 属性名类型默认值描述positionsArray.定义走廊中心的坐标点数组。widthnumber走廊

PHP程序开发基础

PHP简介及其开发环境与工具一、PHP简介 PHP是一种流行的服务器端脚本语言&#xff0c;最初由拉斯姆斯勒多父于1994年创建&#xff0c;旨在通过C语言编写小程序以统计网站的访问流量。自那时以来&#xff0c;PHP已经经历了多个版本的迭代&#xff0c;功能不断增强&#xff0c;应…

使用Canvas绘制地图

既然是通过canvas来绘制地图&#xff0c;那肯定是需要地图的数据信息的。接下来跟着我的脚步去实现这些细节。 地图数据 地图数据怎么来呢&#xff1f;当然是怎么简单怎么来 npm i surbowl/world-geo-json-zh 这个第三方包是简体中文 Geo JSON 世界地图&#xff0c;带有国家…

丁子晴作品《指尖的爱的温度》荣获“金犊奖”全球最佳新锐奖

第33届时报金犊奖颁奖盛典于10月18日在中国成都西部智谷数字体验中心隆重举行。丁子晴的作品《指尖的爱的温度》在激烈的竞争中脱颖而出,荣获了第33届“金犊奖”全球最佳新锐奖。享有盛誉的“金犊奖”是一个全球性的奖项,以其专业严谨、创意水平高的特点,被业界誉为“青年创意的…

Opensearch集群部署【docker、服务器、Helm多种部署方式】

操作系统兼容性 我们建议在 Red Hat Enterprise Linux (RHEL) 或使用systemd的基于 Debian 的 Linux 发行版上安装 OpenSearch &#xff0c;例如 CentOS、Amazon Linux 2 和 Ubuntu Long-Term Support (LTS)。OpenSearch 应该适用于大多数 Linux 发行版&#xff0c;但我们只测…

高级java每日一道面试题-2024年10月22日-JVM篇-JVM堆栈概念,何时销毁对象?

如果有遗漏,评论区告诉我进行补充 面试官: JVM堆栈概念,何时销毁对象? 我回答: JVM堆栈概念 栈&#xff08;Stack&#xff09;&#xff1a; 定义&#xff1a;栈是Java虚拟机为每个线程分配的内存区域&#xff0c;用于存储线程执行时的局部变量、操作数栈、动态链接和方法返…

串口调试工具

https://download.csdn.net/download/jinhuding/89933087?spm1001.2014.3001.5501

boost笔记:boost::Graph中找出所有环

1. 问题描述 本文描述了找出一个有向连通图中所有的环的解决方案 测试用到的有向连通图 2. 自写算法 通过深度优先遍历算法&#xff0c;发现回边时&#xff0c;即存在环的原理来找出环。对于用共享边的环&#xff0c;以下算法有些环找不出来&#xff0c;如上图中的2->8…

DriftingBlues: 1渗透测试

靶机&#xff1a;DriftingBlues: 1 DriftingBlues: 1 ~ VulnHubhttps://www.vulnhub.com/entry/driftingblues-1,625/ 攻击机&#xff1a;kail linux 2024 1,将两台虚拟机网络连接都改为NAT模式&#xff0c;并查看靶机的MAC地址 2&#xff0c;攻击机上做主机扫描发现靶机 靶机I…

【C++单调栈 记忆化搜索】1130. 叶值的最小代价生成树|1919

本文涉及的基础知识点 C单调栈 C记忆化搜索 C动态规划 LeetCode1130. 叶值的最小代价生成树 给你一个正整数数组 arr&#xff0c;考虑所有满足以下条件的二叉树&#xff1a; 每个节点都有 0 个或是 2 个子节点。 数组 arr 中的值与树的中序遍历中每个叶节点的值一一对应。 每…

【我的 PWN 学习手札】setcontext + ROP

堆上的setcontext利用系列还有&#xff1a; 【我的 PWN 学习手札】setcontext shellcode-CSDN博客 目录 前言 一、setcontext gadget 二、setcontext ROP &#xff08;一&#xff09;setcontext设置寄存器 &#xff08;二&#xff09;ROP链布置 三、图示 四、模板与…

【算法】Kruskal最小生成树算法

目录 一、最小生成树 二、Kruskal算法求最小生成树 三、代码 一、最小生成树 什么是最小生成树&#xff1f; 对于一个n个节点的带权图&#xff0c;从中选出n-1条边&#xff08;保持每个节点的联通&#xff09;构成一棵树&#xff08;不能带环&#xff09;&#xff0c;使得…

信号完整性SI总结【小登培训】

信号完整性问题的根源通常在于阻抗不匹配、串扰、时序误差、电磁辐射和电源噪声。解决这些问题需要从PCB设计、布线、材料选择、匹配和屏蔽等多个方面综合考虑&#xff0c;并结合眼图分析等工具进行调试和优化。确保信号完整性对于高速电路设计尤为重要&#xff0c;影响系统的可…

【蓝桥杯选拔赛真题78】python电话号码 第十五届青少年组蓝桥杯python选拔赛真题 算法思维真题解析

目录 python电话号码 一、题目要求 1、编程实现 2、输入输出 二、算法分析 三、程序编写 四、程序说明 五、运行结果 六、考点分析 七、 推荐资料 1、蓝桥杯比赛 2、考级资料 3、其它资料 python电话号码 第十五届蓝桥杯青少年组python比赛选拔赛真题 一、题目要…

2022NOIP练习总结

种花 1.本题是一道前缀和优化加上枚举的问题。先考虑 C 因为 F 是 C 下边随便加一个点&#xff0c;所以只要求出 C 就求出了 F 。 注意到&#xff0c;并没有要求上下行一样&#xff0c;唯一的要求是 C 的两个横要隔一行&#xff0c;这就是问题的突破点&#xff0c;这题很明显…

【Spring Boot】元注解

元注解 1.元注解1.1 Target1.2 Retention1.3 Inherited1.4 Documented1.5 interface 2.自定义注解2.1 创建自定义注解类2.2 实现业务逻辑2.3 使用自定义注解 1.元注解 元注解就是定义注解的注解&#xff0c;是 Java 提供的用于定义注解的基本注解。 注解 说明 Retention是注解…