单片机实现动态内存管理

1.简介

        多数传统的单片机并没有动态内存管理功能。单片机通常具有有限的存储资源,包括固定大小的静态RAM(SRAM)用于数据存储和寄存器用于特定功能。这些资源在编译时被分配并且在程序的整个生命周期中保持不变。

2.动态内存管理好处

  1. 灵活性和效率:动态内存管理可以根据程序的需要,在运行时动态分配和释放内存空间。这种灵活性使得程序能够更高效地利用可用的内存资源,避免了静态分配固定大小内存的限制。

  2. 节省内存空间:动态内存管理允许程序只在需要时分配内存,释放不再使用的内存。这样可以避免静态内存分配导致的内存浪费,提高内存利用率。

  3. 支持动态数据结构:许多数据结构,如链表、树等,大小在运行时无法预先确定,需要动态分配内存以存储变量数量可变的元素。动态内存管理使得这些动态数据结构的实现变得简单和高效。

  4. 扩展性:使用动态内存管理,可以根据程序的需求动态地调整内存大小。这使得程序能够适应不同的输入规模和需求变化,提供更好的扩展性和灵活性。

3.如何自行实现动态内存管理 

3.1 步骤

  1. 分配内存空间:首先,需要实现一个分配内存空间的函数。该函数需要检查内存池中是否有足够的空闲内存。如果有空闲内存,则将其标记为已使用,并返回指向所分配内存块的指针。如果没有足够的空闲内存,则需要采取相应的策略,例如返回空指针或扩展内存池。

  2. 释放内存空间:当不再需要分配的内存块时,需要实现一个释放内存空间的函数。该函数需要接收指向待释放内存块的指针,并将其标记为空闲状态,以便后续的内存分配可以再次利用它。

  3. 管理内存池:需要设计和管理一个内存池,也称为内存堆。内存池是一个预先分配的内存区域,用于存储动态分配和释放的内存块。需要跟踪每个已分配内存块的状态(已使用或空闲),以及其大小和地址信息。

  4. 错误处理和边界检查:在实现动态内存管理时,必须考虑各种错误情况和边界条件。例如,分配失败、重复释放内存、越界访问等。需要在代码中加入相应的检查和错误处理机制,以确保内存管理的正确性和安全性。

 3.2方案

     1.增加外部SRAM或者DRAM,使用外部空间来作为动态内存管理,这种方法需要,编写外部SRAM的驱动,并将外部的SRAM地址映射到芯片地址中。

        例如以下驱动控制器:

        STM32系列微控制器的FSMC(Flexible Static Memory Controller)是一种专门设计用于连接外部存储器设备(如SRAM、NOR Flash等)的控制器。

        NXP LPC系列微控制器的EMC(External Memory Controller)来支持与外部存储器设备的数据交互可以连接多种类型的存储器,如SRAM、NOR Flash、SDRAM等。

        TM4C系列微控制器的嵌入式外部存储器接口(EMIFA),通过该接口可以连接外部存储器设备,并支持SRAM、NOR Flash、NAND Flash等多种存储器类型。

      2.使用内部SRAM的静态变量存储区来当作动态内存管理,这部分空间不允许再当静态空间来操作,这种方法无需依赖外部控制器,但是同时也无法管理更大的动态空间。

4.实现

        动态内存管理的本质就是对一段地址的管理。它涉及到在运行时动态分配和释放内存空间,并管理各个内存块的状态可用性。

4.1直接使用静态变量

#define MEM_MAX_SIZE 100*1024                       //100K
static unsigned char mem1base[MEM_MAX_SIZE];		//内部SRAM内存池

4.2使用外部SRAM地址需要映射

static unsigned char mem2base[MEM_MAX_SIZE] __attribute__((at(0XC0000000))); //外部SDRAM内存池

这里的地址0XC0000000只是示例 需要按照上文提到驱动控制器进行修改。

4.3 数据结构设计 

typedef struct _MemBlockList
{union {struct {unsigned int valid : 1;	  // 0 表示未使用 1表示使用了unsigned int length : 31; // 长度};int info; // 整体的信息(部分)};struct _MemBlockList *next; // 下一块
} MemBlockList;//共占用8个字节typedef struct _MemoryManager
{void *start;void *end;unsigned int size;MemBlockList *head;
} MemoryManager;static MemoryManager g_memoryManager;

MemBlockList用于管理一块连续的内存区域,并维护了一个内存块链表,用于跟踪空闲和已分配的内存块,MemoryManager用于表示内存管理器。通过使用这两个结构体,可以实现对一块连续内存的管理,包括内存块的分配和释放。g_memoryManager是一个全局的内存管理器变量,用于在程序中跟踪和管理内存块的分配和释放情况。

4.4 动态内存的初始化

bool memoryManagerInit(void)
{int size = MEM_MAX_SIZE;g_memoryManager.end = memXbase + size;g_memoryManager.start = memXbase;g_memoryManager.size = size;g_memoryManager.head = (MemBlockList *)g_memoryManager.start;g_memoryManager.head->length = g_memoryManager.size - sizeof(MemBlockList);g_memoryManager.head->valid = 0;g_memoryManager.head->next = NULL;	printf("memoryManagerInit success : %d KB\n", size / 1024);return true;
}

4.6 动态内存的申请

void *memoryManager_malloc(int size)
{int free_size = getMaxFreeBlockSize();if (free_size >= size){MemBlockList *ptr = g_memoryManager.head;MemBlockList *free_block = NULL;/* 指针对齐,保证了 currentSize 是指针 sizeof(void*) 的整数倍大小 */int n = size / sizeof(void*);int currentSize = n * sizeof(void*);if (size % sizeof(void*) != 0) {currentSize += sizeof(void*);}bool isNeedCut = false; //是否需要分割MemBlockList *node = getFreeBlock(currentSize, &isNeedCut); if (node == NULL){printf("malloc size = %d faile !!!!!\n", size);print_mem_info();return (void *)(NULL);}/* 标记内存块使用了 */node->valid = 1;unsigned char *p = (unsigned char *)node;if (isNeedCut){p += sizeof(MemBlockList) + currentSize;free_block = (MemBlockList *)(p);free_block->length = node->length - currentSize - sizeof(MemBlockList);free_block->valid = 0;free_block->next = node->next;node->next = free_block;node->length = currentSize;}p = (unsigned char *)node;p += sizeof(MemBlockList);//偏移8个字节为真正使用的malloc地址checkMem();print_mem_info();return (void *)(p);}return NULL;
}

  1. 首先,通过调用getMaxFreeBlockSize函数获取当前可用的最大内存块的大小,以判断是否能够满足请求的内存大小。

  2. 如果最大可用内存块的大小大于等于所需的内存大小size,则尝试分配内存。

  3. 通过getFreeBlock函数查找一个合适大小的空闲内存块node,并返回该内存块的指针。如果没有足够大的空闲内存块,则打印错误消息并返回NULL

  4. 将找到的内存块node标记为已使用(valid = 1)。

  5. 计算实际分配的内存块大小currentSize,并判断是否需要对内存块进行分割。

  6. 如果需要进行分割,将原内存块的长度更新为currentSize,并在其后创建一个新的空闲内存块free_block,其长度为原内存块长度减去currentSizesizeof(MemBlockList)的大小。

  7. 返回分配的内存块的起始地址,即node指针偏移sizeof(MemBlockList)字节后的地址。

4.7 动态内存的释放

void memoryManager_free(void *ptr)
{if (ptr != NULL){if ((ptr > g_memoryManager.start) && (ptr < g_memoryManager.end)){/* 计算地址对应的原始节点 */MemBlockList *source_node = (MemBlockList *)((unsigned char *)ptr - sizeof(MemBlockList));/* 找到node的前一个节点和下一个节点 */MemBlockList *previous_node = g_memoryManager.head;while (previous_node && previous_node->next != source_node){previous_node = previous_node->next;}MemBlockList *next_node = source_node->next;checkMem();source_node->valid = 0;MemBlockList *connect_node;if (previous_node && (previous_node->valid == 0))// 前一个节点是否空闲{connect_node = source_node->next;int size = source_node->length  + sizeof(MemBlockList);if (next_node && (next_node->valid == 0))// 下一个节点是否空闲{connect_node = next_node->next;size += next_node->length + sizeof(MemBlockList);}previous_node->next = connect_node;previous_node->length += size;}else{connect_node = source_node->next;int size =  source_node->length;if (next_node &&  (next_node->valid == 0)){ connect_node = next_node->next;size += next_node->length + sizeof(MemBlockList);}source_node->next = connect_node;source_node->length = size;}checkMem();}else{printf("memoryManager_free not allowd this address = %p(%p --- %p)\n", ptr, g_memoryManager.start,  g_memoryManager.end);}}
}
  1. 首先判断参数ptr是否为NULL,如果是NULL,则直接返回。

  2. 接下来,检查ptr指向的内存地址是否在内存管理器所管理的内存范围内(g_memoryManager.startg_memoryManager.end之间)。如果不在范围内,则打印错误消息并返回。

  3. 计算ptr对应的原始内存节点source_node的地址,即ptr指针向前偏移sizeof(MemBlockList)字节。

  4. 遍历内存块链表,找到source_node的前一个节点previous_node和下一个节点next_node

  5. source_node标记为未使用(valid = 0)。

  6. 根据前一个节点previous_node和下一个节点next_node的状态,进行内存块合并操作。

    • 如果前一个节点previous_node存在且为空闲(valid = 0),则将source_node与前一个节点连接,并将原来的内存长度包括前一个节点的长度进行合并。

    • 否则,将source_node单独作为一个内存块,且不与前一个节点连接。

  7. 最后,调用checkMem函数检查内存状态。

5. 实现代码总览

#ifndef NULL
#define NULL ((void *)0)
#endiftypedef struct _MemBlockList
{union {struct {unsigned int valid : 1;	  // 0 表示未使用 1表示使用了unsigned int length : 31; // 长度};int info; // 整体的信息(部分)};struct _MemBlockList *next; // 下一块
} MemBlockList;//共占用8个字节typedef struct _MemoryManager
{void *start;void *end;unsigned int size;MemBlockList *head;
} MemoryManager;static MemoryManager g_memoryManager;/* 获取当前内存中空闲的最大块大小 */
static unsigned int getMaxFreeBlockSize(void)
{MemBlockList *node = g_memoryManager.head;unsigned int size = 0;while (node){if(node->valid == 0){if (node->length > size){size = node->length;}}node = node->next;}return size;
}/* 获取当前内存中 满足size条件下 最小的内存块 */
static MemBlockList *getFreeBlock(unsigned int size, bool *isNeedCutMem)
{MemBlockList *current_node = g_memoryManager.head;MemBlockList *ret_node = NULL;unsigned int min_size = 0xffffffff;*isNeedCutMem = false;while (current_node){if (current_node->valid == 0 && current_node->length >= size) //块是否空闲{bool current_cut = false;if (current_node->length >= (size + sizeof(MemBlockList) + (sizeof(void *))))current_cut = true;if (current_node->length < min_size){min_size = current_node->length;*isNeedCutMem = current_cut;ret_node = current_node;if (min_size == size)break;}}current_node = current_node->next;}return ret_node;
}/* 打印当前的内存使用情况 */
void print_mem_info(void)
{MemBlockList *node = g_memoryManager.head;while (node){printf("address = %p, valid = %d, next = %p, size = %d\n", node, node->valid, node->next, node->length);node = node->next;}
}/* 内存检查 */
static void checkMem(void)
{MemBlockList *node = g_memoryManager.head;int size = 0;int cnt = 0;while (node){cnt ++;size += node->length;node = node->next;}if (size < (g_memoryManager.size - cnt * sizeof(MemBlockList))){printf("checkMem err now only have %d block, size = %d!!!\n", cnt, size);print_mem_info();while (1);}return;
}/* 内存操作检查 */
static bool checkMemUse(void *ptr, int size)
{if((ptr < g_memoryManager.start) || (ptr > g_memoryManager.end)){//不在管控范围内的地址不进行校验return true;}MemBlockList *node = g_memoryManager.head;unsigned char *current_p = NULL;while (node){int current_size = node->length;current_p = (unsigned char *)node + sizeof(MemBlockList);if ((ptr >= current_p) && (ptr < (current_p + current_size))){if (node->valid == 0){printf("checkMemUse this address = %p is not active !!\n", ptr);return false;}else{if(size > (node->length - ((unsigned char *)ptr - current_p))){printf("checkMemUse this address = %p size = %d, is size over, source address = %p, len = %d!!\n",ptr, size, current_p, node->length);return false;}}}node = node->next;}return true;
} bool memoryManagerInit(void)
{int size = MEM_MAX_SIZE;g_memoryManager.end = memXbase + size;g_memoryManager.start = memXbase;g_memoryManager.size = size;g_memoryManager.head = (MemBlockList *)g_memoryManager.start;g_memoryManager.head->length = g_memoryManager.size - sizeof(MemBlockList);g_memoryManager.head->valid = 0;g_memoryManager.head->next = NULL;	printf("memoryManagerInit success : %d KB\n", size / 1024);return true;
}void *memoryManager_malloc(int size)
{int free_size = getMaxFreeBlockSize();if (free_size >= size){MemBlockList *ptr = g_memoryManager.head;MemBlockList *free_block = NULL;/* 指针对齐,保证了 currentSize 是指针 sizeof(void*) 的整数倍大小 */int n = size / sizeof(void*);int currentSize = n * sizeof(void*);if (size % sizeof(void*) != 0) {currentSize += sizeof(void*);}bool isNeedCut = false; //是否需要分割MemBlockList *node = getFreeBlock(currentSize, &isNeedCut); if (node == NULL){printf("malloc size = %d faile !!!!!\n", size);print_mem_info();return (void *)(NULL);}/* 标记内存块使用了 */node->valid = 1;unsigned char *p = (unsigned char *)node;if (isNeedCut){p += sizeof(MemBlockList) + currentSize;free_block = (MemBlockList *)(p);free_block->length = node->length - currentSize - sizeof(MemBlockList);free_block->valid = 0;free_block->next = node->next;node->next = free_block;node->length = currentSize;}p = (unsigned char *)node;p += sizeof(MemBlockList);//偏移8个字节为真正使用的malloc地址checkMem();print_mem_info();return (void *)(p);}return NULL;
}void memoryManager_free(void *ptr)
{if (ptr != NULL){if ((ptr > g_memoryManager.start) && (ptr < g_memoryManager.end)){/* 计算地址对应的原始节点 */MemBlockList *source_node = (MemBlockList *)((unsigned char *)ptr - sizeof(MemBlockList));/* 找到node的前一个节点和下一个节点 */MemBlockList *previous_node = g_memoryManager.head;while (previous_node && previous_node->next != source_node){previous_node = previous_node->next;}MemBlockList *next_node = source_node->next;checkMem();source_node->valid = 0;MemBlockList *connect_node;if (previous_node && (previous_node->valid == 0))// 前一个节点是否空闲{connect_node = source_node->next;int size = source_node->length  + sizeof(MemBlockList);if (next_node && (next_node->valid == 0))// 下一个节点是否空闲{connect_node = next_node->next;size += next_node->length + sizeof(MemBlockList);}previous_node->next = connect_node;previous_node->length += size;}else{connect_node = source_node->next;int size =  source_node->length;if (next_node &&  (next_node->valid == 0)){ connect_node = next_node->next;size += next_node->length + sizeof(MemBlockList);}source_node->next = connect_node;source_node->length = size;}checkMem();}else{printf("memoryManager_free not allowd this address = %p(%p --- %p)\n", ptr, g_memoryManager.start,  g_memoryManager.end);}}
}void memoryManager_cpy(void *dest, const void *src, unsigned int n)
{if(checkMemUse(src, n) == false){printf("memoryManager_cpy error check src\n");}if(checkMemUse(dest, n) == false){printf("memoryManager_cpy error check dest\n");}for (unsigned int i = 0; i < n; i++) {dest[i] = src[i];}
}void memoryManager_set(void *ptr, unsigned char value, unsigned int num)
{if(checkMemUse(ptr, num) == false){printf("memoryManager_set error check ptr\n");}for (unsigned int i = 0; i < num; i++) {ptr[i] = value;}
}

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

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

相关文章

SpringBoot使用@Autowired将实现类注入到List或者Map集合中

前言 最近看到RuoYi-Vue-Plus翻译功能 Translation的翻译模块配置类TranslationConfig&#xff0c;其中有一个注入TranslationInterface翻译接口实现类的写法让我感到很新颖&#xff0c;但这种写法在Spring 3.0版本以后就已经支持注入List和Map&#xff0c;平时都没有注意到这…

Jenkins持续集成-快速上手

Jenkins持续集成-快速上手 注&#xff1a;Jenkins一般不单独使用&#xff0c;而是需要依赖代码仓库&#xff0c;构建工具等。 搭配组合&#xff1a;GitGitee&#xff08;GitHub、GitLab&#xff09;MavenJenkins 前置准备 常见安装方式&#xff1a; war包Docker容器实例&…

WEB集群——http、tomcat

1. 简述静态网页和动态网页的区别。 2. 简述 Webl.0 和 Web2.0 的区别。 3. 安装tomcat8&#xff0c;配置服务启动脚本&#xff0c;部署jpress应用。 1. 简述静态网页和动态网页的区别。 1&#xff09;、静态网页 &#xff08;1&#xff09;、什么是静态网页 请求响应信息&…

Java 工具类(列表)

去重 对列表进行保留顺序的去重 List<Integer> list List.of(1, 2, 4, 3, 1); LinkedHashSet<Integer> set new LinkedHashSet<>(list); ArrayList<Integer> integers new ArrayList<>(set); // 1, 2, 4, 3删除 按照单逻辑进行删除 List&…

基于fpga的电子时钟

文章目录 前言实验手册一、实验目的二、实验原理1&#xff0e;理论原理2&#xff0e;硬件原理 三、系统架构设计四、模块说明1&#xff0e;模块端口信号列表按键消抖模块&#xff08;key&#xff09;计数器模块&#xff08;counter&#xff09;蜂鸣器乐谱模块(music)蜂鸣器发声…

leetcode做题笔记57

给你一个 无重叠的 &#xff0c;按照区间起始端点排序的区间列表。 在列表中插入一个新的区间&#xff0c;你需要确保列表中的区间仍然有序且不重叠&#xff08;如果有必要的话&#xff0c;可以合并区间&#xff09;。 思路一&#xff1a;模拟题意 int pushbackInterval(int…

GD32F103VE侵入事件

GD32F103VE的TAMPER引脚(PC13)&#xff0c;当PC13输入低电平时&#xff0c;会产生一个侵入检测事件。它会将所有“数据备份寄存器”内容清除。 这个功能有什么用&#xff1f; 一是防止被人开壳&#xff0c;抄袭。二是自毁功能。 直奔主题&#xff0c;多一句就是浪费时间。测试…

面试之快速学习c++11- 列表初始化和 lambda匿名函数的定义

学习地址&#xff1a; http://c.biancheng.net/view/3730.html 8. C11列表初始化&#xff08;统一了初始化方式&#xff09; 我们知道&#xff0c;在 C98/03 中的对象初始化方法有很多种&#xff0c;请看下面的代码&#xff1a; //初始化列表 int i_arr[3] { 1, 2, 3 }; /…

flutter开发实战-flutter_spinkit实现多种风格进度指示器

flutter开发实战-flutter_spinkit实现多种风格进度指示器 最近开发过程中flutter_spinkit&#xff0c;这个拥有多种种风格加载指示器 一、flutter_spinkit 引入flutter_spinkit # 多种风格的模糊进度指示器flutter_spinkit: ^5.1.0效果示例 const spinkit SpinKitRotatingC…

如何找到死锁的线程?_java都学什么

在Java中&#xff0c;死锁是指两个或多个线程被无限地阻塞&#xff0c;等待彼此持有的资源&#xff0c;从而导致程序无法继续执行的情况。死锁通常是由于线程之间循环等待资源而产生的。要找到死锁的线程&#xff0c;可以采用以下方法&#xff1a; 1.线程转储(Thread Dump) 通过…

6.6 实现卷积神经网络LeNet训练并预测手写体数字

模型架构 代码实现 import torch from torch import nn from d2l import torch as d2lnet nn.Sequential(nn.Conv2d(1,6,kernel_size5,padding2),nn.Sigmoid(),#padding2补偿5x5卷积核导致的特征减少。nn.AvgPool2d(kernel_size2,stride2),nn.Conv2d(6,16,kernel_size5),nn.S…

Node.js-npm包管理工具的介绍

一、概念 包&#xff0c;代表一组特定功能的源码集合。 包管理工具&#xff0c;管理包的应用软件&#xff0c;可以下载安装、更新、删除包等操作&#xff0c;在项目开发中大大提高开发效率。 npm全称&#xff1a;Node Package Manager 二、npm使用 如果安装了 node&#xff0c;…

OpenStreetMap数据转3D场景【Python + PostgreSQL】

很长一段时间以来&#xff0c;我对 GIS 和渲染感兴趣&#xff0c;在分别尝试这两者之后&#xff0c;我决定最终尝试以 3D 方式渲染 OpenStreetMap 中的地理数据&#xff0c;重点关注不超过城市的小规模。 在本文中&#xff0c;我将介绍从建筑形状生成三角形网格、以适合 Blend…

iperf3-性能测试

iperf3-性能测试 安装1.apt安装2.源码安装 使用方法iperf原理测试参考文档性能测试客户端服务端 官方文档&#xff1a;https://iperf.fr/iperf-doc.php 安装 1.apt安装 sudo apt-get install iperf32.源码安装 # 按照官方说明安装 ./configure make sudo make install执行编…

node中使用express+mongodb实现分页查询

文章目录 引言一、分页案例二、查询方法扩展介绍1. find()2. limit()3. skip()4. populate() 总结 引言 在Web应用程序开发中&#xff0c;分页查询是必不可少的功能之一。Node.js提供了许多优秀的工具和框架来实现分页查询&#xff0c;其中最流行的框架之一就是Express。同时&…

8.2一日总结

1.记录更新&#xff1a; untracked&#xff1a; 未追踪&#xff08;新增的文件&#xff09; unmodefied&#xff1a; 未修改 modefied&#xff1a; 已修改 staged&#xff1a; 已暂存 2、添加指定文件到暂存区&#xff1a; git add 文件名 gi…

docker 怎么搭建

Docker是一种容器化平台&#xff0c;可以快速构建、部署和运行应用程序。以下是Docker的搭建流程&#xff1a; 1. 安装Docker 在官方网站上下载并安装Docker&#xff0c;根据官方指引进行安装。 2. 配置Docker环境&#xff1a; 配置Docker环…

GATK ApplyBQSRSpark 过程中因No space left on device终止

Error&#xff1a; GATK ApplyBQSRSpark 过程中因No space left on device终止 执行命令&#xff1a; nohup time ./gatk --java-options "-Xmx128G" ApplyBQSRSpark --spark-master local[20] -R ../../alignment/hg38/hg38.fa -I ../../alignment/bam/P368T.s…

微信小程序nodejs+vue+uniapp高校食堂线上预约点餐系统

本次设计任务是要设计一个食堂线上预约点餐系统&#xff0c;通过这个系统能够满足管理员及学生的食堂线上预约点餐分享功能。系统的主要包括首页、个人中心、学生管理、菜品分类管理、菜品管理、关于我们管理、意见反馈、系统管理、订单管理等功能。 开发语言 node.js 框架&am…

【论文阅读】对抗溯源图主机入侵检测系统的模仿攻击(NDSS-2023)

作者&#xff1a;伊利诺伊大学芝加哥分校-Akul Goyal、Gang Wang、Adam Bates&#xff1b;维克森林大学-Xueyuan Han、 引用&#xff1a;Goyal A, Han X, Wang G, et al. Sometimes, You Aren’t What You Do: Mimicry Attacks against Provenance Graph Host Intrusion Detect…