改进一个实用的、可应用于单片机的内存管理模块

改进一个实用的、可应用于单片机的内存管理模块

一般单片机的内存都比较小,而且没有MMU,malloc 与free的使用容易造成内存碎片。而且可能因为空间不足而分配失败,从而导致系统崩溃,因此应该慎用,或者自己实现内存管理。heap_malloc就是一个不会产生内存碎片的、适合单片机使用的内存管理模块。该模块主要改进于mem_malloc模块,一位大佬写的应用于单片机内存管理模块,原代码仓库:
https://gitee.com/liming2019/mem_malloc
该模块的使用并不会产生内存碎片,可以高效地利用单片机ram空间。但是原模块仅能将一个数组作为内存堆来使用,我尝试对其进行改进,使得可以声明多个数组作为内存堆来使用,且还可以实现堆的嵌套创建使用(还没尝试过,我想这个想法再多进程或多线程、又或者内存回收利用方面估计会有很好的应用)。改进后的模块地址为:
https://gitee.com/liu-guanglin/lgl_malloc
消除内存碎片的原理:
在该模块中,堆内的空间被划分成为三部分,低位地址的堆管理表空间,存储着三种参数:分配给用户的表序号,对应的分配内存的起始地址,还有分配的内存大小;中间的空闲内存空间,用来分配给用户于管理表存储的待分配空间;高位地址则是分配给用户的内存空间。
在这里插入图片描述

该堆空间是处于动态分配的过程中,因而当用户程序对自己申请到的堆空间进行重申请或释放自己申请到的空间时,为保证堆空间内不产生内存碎片,在移动内存空间的过程中会影响到其他无关的申请块。也就是用户申请到的内存空间的起始地址是处于可变化的状态下的,但是用户拿到的表序号是唯一不变的。因而如果用户需要操控自己所申请的对应的内存空间,那么唯一可行且安全的渠道就是先通过拿着的表序号来获取到对于的起始地址,然后再根据地址对自己处于堆空间中的内存地址进行操作。每一次操作都需要重复该过程。
mem_malloc测试验证(改进的模块兼容了原模块,即保留了默认堆空间)
实验过程很简单。准备一份开发板带串口打印的工程,下载mem_malloc,把mem_malloc.c、mem_malloc.h复制到工程目录下,并添加到工程里:
在这里插入图片描述
在这里插入图片描述

然后进行编译,编译过程可能会报错:

..\Src\mem_malloc.c(119): error:  #852: expression must be a pointer to a complete object type

这份代码在不同编译器下编译情况不同。gcc下编译不会报错,在keil下编译报如上错误。
keil编译器更严格些。报错原因是对mem_block结构体的mem_ptr成员进行操作,而mem_ptr成员的类型是void*,而mem_ptr成员参与运算时的增、减偏移量取决于mem_ptr的类型,所以这里我们需要指定类型。
我们把相关报错代码修改如:
在这里插入图片描述

再次编译就正常了。
下面简单看一下mem_malloc的代码。
「mem_malloc.h:」

#ifndef __MEM_MALLOC_H__
#define __MEM_MALLOC_H__

#ifdef __cplusplus
extern "C" {
#endif

#include <stdio.h> 
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <stdarg.h>

//内存堆-对象
typedef struct mem_heap
{
    unsigned int sum;   /*用于记录已使用的长度*/
    unsigned int size;  /*内存堆的长度*/ 
    unsigned char* mem; /*内存堆的首地址*/
}mem_heap;

void print_heap_info(const mem_heap* heap);
void print_heap_hex(const mem_heap* heap);
int heap_malloc(mem_heap* heap,unsigned int msize);
int heap_realloc(mem_heap* heap,int id, unsigned int msize);
void *heap_buffer(const mem_heap* heap,int id);
int heap_free(mem_heap* heap,int id);

/**---------------保留实现默认堆空间--------------------**/
#define MEM_SIZE        128
static char         mem[MEM_SIZE];
static mem_heap MEM={.sum=0,.size=MEM_SIZE,.mem=mem};

#define print_mem_info()  print_heap_info(&MEM)
#define print_mem_hex()   print_heap_hex(&MEM)  
#define mem_malloc(msize)  heap_malloc(&MEM,msize)
#define mem_realloc(id,msize) heap_realloc(&MEM,id,msize)
#define mem_buffer(id)      heap_buffer(&MEM,id)
#define mem_free(id)        heap_free(&MEM,id)

#ifdef __cplusplus
}
#endif

#endif

「mem_malloc.c:」
下面对mem_malloc进行测试验证。(与上面的不同,我使用的是Linux编译)在这里插入图片描述

测试代码作者也有给出,这里我们简单测试即可,进行了一些删减及增加了一些注释:

#include "mem_malloc.h"

void main(void){
    print_mem_info(); //打印内存信息
    printf("------test_malloc-------\n");
    int size=30;
    printf("未使用的堆内存空间:\r\n");
    print_mem_hex();

    //1、使用 mem_malloc 或 mem_realloc 函数申请指定大小的内存块(返回内存块的表序号)
    printf("使用mem_malloc()来申请堆内存空间\r\n");
    unsigned int memId=mem_malloc(size);
    if (memId==0)
    {
        printf("申请堆内存失败!\r\n");
        return -1;
    }
    printf("得到的表序号为:memId:%d\r\n",memId);
    print_mem_hex();
    //2、当需要使用该内存块,使用表序号获取内存块的起始地址
    char *p = mem_buffer(memId);
    printf("得到第%d块堆空间的起始地址为:%o\r\n",memId,p);
    print_mem_hex();
    printf("往该块堆内存块填充数据0xFF\r\n");
    memset(p, 0xFF, size);
    print_mem_hex();
    printf("重申请堆内存大小\r\n");
    size=10;
    if(mem_realloc(memId, size)==0){
        printf("申请失败");
        mem_free(memId);
        return -1;
    }
    p = mem_buffer(memId);
    printf("得到第%d块堆空间的起始地址为:%o\r\n",memId,p);
    print_mem_hex();
    printf("往该块堆内存块填充数据0xAA\r\n");
    memset(p, 0xAA, size);
    print_mem_hex();
    mem_free(memId);
} 

这里设定一个128字节的数组作为堆空间使用。其中数组前面存放的是申请到的内存块的信息,包括内存块地址、大小、索引信息,这三个数据各占4个字节,共12个字节。这里有设计到一个大小端模式的问题,STM32平台为小端模式,即数据的低位存储在内存的低地址中。
申请的内存块从128字节的尾部开始分配,再次申请的内存块依次往前移,释放的内存,则整体内存块往后移动,内存块之前不留空隙,即不产生内存碎片。

运行结果及解析:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

测试2:使用新的数组作为堆空间(推荐静态数组)

#define SIZE 128
static char memer[128];

int main2(){
    //1、声明静态内存堆对象
    static mem_heap heap={.size=SIZE,.sum=0,.mem=&memer[0]};
    printf("测试内存堆信息打印\r\n");
    print_heap_info(&heap);
    print_heap_hex(&heap);

    int heapId=heap_malloc(&heap,50);
    printf("申请堆内存的表序号:%d\r\n",heapId);
    print_heap_hex(&heap);
    //2、通过表序号获取堆空间的起始地址
    printf("往申请到的内存空间打印0xAA数据\r\n");
    char* p=heap_buffer(&heap,heapId);
    if(p==NULL){
        printf("获取地址错误\r\n");
        return -1;
    }
    memset(p,0xAA,50);
    print_heap_hex(&heap);
    printf("重新申请内存空间\r\n");
    if(heap_realloc(&heap,heapId,20)==0){
        printf("重新申请内存空间失败\r\n");
    }
    print_heap_hex(&heap);
    printf("往申请到的内存空间打印0xFF数据\r\n");
    p=heap_buffer(&heap,heapId);
    if(p==NULL){
        printf("获取地址错误\r\n");
        return -1;
    }
    memset(p,0xFF,50);
    print_heap_hex(&heap);
    printf("释放堆内存\r\n");
    heap_free(&heap,heapId);
    print_heap_hex(&heap);
}=

测试内存堆信息打印
------------mem_info--------------
sizeof(mem_block)=16
mem_start = -1305308960(0xb23290e0)
MEM_END = -1305308832(0xb2329160)
MEM_SIZE = 128(0x80)

00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00
申请堆内存的表序号:1
2e 91 32 b2 59 55 00 00 32 00 00 00
01 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00
往申请到的内存空间打印0xAA数据
2e 91 32 b2 59 55 00 00 32 00 00 00
01 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 aa aa aa aa aa aa
aa aa aa aa aa aa aa aa aa aa aa aa
aa aa aa aa aa aa aa aa aa aa aa aa
aa aa aa aa aa aa aa aa aa aa aa aa
aa aa aa aa aa aa aa aa
重新申请内存空间
4c 91 32 b2 59 55 00 00 14 00 00 00
01 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00
aa aa aa aa aa aa aa aa aa aa aa aa
aa aa aa aa aa aa aa aa
往申请到的内存空间打印0xFF数据
4c 91 32 b2 59 55 00 00 14 00 00 00
01 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00
ff ff ff ff ff ff ff ff ff ff ff ff
ff ff ff ff ff ff ff ff
释放堆内存
00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
单片机内存管理框架主要是为了有效地利用有限的内存资源,避免内存碎片化和浪费。以下是一个简单的单片机内存管理框架的实现思路: 1. 定义内存池:首先需要定义一个内存池,用于存储可用的内存块,内存池可以是一个数组或链表,每个内存块大小相同。 2. 初始化内存池:在系统启动时,需要将整个内存池初始化为一系列可用的内存块,这些内存块可以用于分配给任务或其他部分使用。 3. 分配内存:当需要分配内存时,内存管理框架会从内存池中找到一个可用的内存块,将其标记为已分配,并返回该内存块的地址。 4. 释放内存:当任务完成了对内存块的使用,需要将其释放回内存池中,以便其他任务可以重新使用该内存块。 5. 管理内存池:内存管理框架还需要实现一些管理内存池的函数,例如检查内存池的状态、打印内存池的使用情况等。 下面是一个简单的单片机内存管理框架的代码示例: ```c #define MEM_POOL_SIZE 1024 // 内存池大小 typedef struct mem_block { uint8_t data[MEM_BLOCK_SIZE]; // 内存块数据 struct mem_block *next; // 指向下一个内存块 uint8_t used; // 是否被使用 } mem_block_t; static mem_block_t mem_pool[MEM_POOL_SIZE]; // 内存池 void mem_init(void) { // 初始化内存池 for (int i = 0; i < MEM_POOL_SIZE; i++) { mem_pool[i].used = 0; mem_pool[i].next = &mem_pool[i+1]; } mem_pool[MEM_POOL_SIZE-1].next = NULL; } void *mem_alloc(size_t size) { // 分配内存 if (size > MEM_BLOCK_SIZE) { return NULL; } mem_block_t *block = mem_pool; while (block != NULL) { if (!block->used) { block->used = 1; return block->data; } block = block->next; } return NULL; } void mem_free(void *ptr) { // 释放内存 if (ptr == NULL) { return; } mem_block_t *block = (mem_block_t *)((uint8_t *)ptr - offsetof(mem_block_t, data)); block->used = 0; } void mem_print(void) { // 打印内存池状态 int free_blocks = 0; int used_blocks = 0; mem_block_t *block = mem_pool; while (block != NULL) { if (block->used) { used_blocks++; } else { free_blocks++; } block = block->next; } printf("free blocks: %d, used blocks: %d\n", free_blocks, used_blocks); } ``` 这个简单的内存管理框架采用了一个静态数组作为内存池,并实现了分配、释放和打印内存池状态等基本功能。在实际应用中,可以根据需求修改和扩展该框架。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值