单片机实现动态内存管理

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直接使用静态变量

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

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

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

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

4.3 数据结构设计

cpp 复制代码
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 动态内存的初始化

cpp 复制代码
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 动态内存的申请

cpp 复制代码
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 动态内存的释放

cpp 复制代码
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. 实现代码总览

cpp 复制代码
#ifndef NULL
#define NULL ((void *)0)
#endif

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;

/* 获取当前内存中空闲的最大块大小 */
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;
    }
}
相关推荐
鸿喵小仙女33 分钟前
C# WPF读写STM32/GD32单片机Flash数据
stm32·单片机·c#·wpf
lucy153027510791 小时前
MCU 功耗基准测试
科技·单片机·嵌入式硬件·智能家居·信号处理·工控主板
m0_748240912 小时前
OpenMV与STM32通信全面指南
stm32·单片机·嵌入式硬件
Cchengzu4 小时前
阿里巴巴2017实习生笔试题(二)
stm32·单片机·嵌入式硬件
重生之我是数学王子7 小时前
单片机 STM32入门
stm32·单片机·嵌入式硬件
qq_4597300311 小时前
4-3 MCU中ARM存储器的作用
arm开发·单片机·嵌入式硬件
重生之我是数学王子14 小时前
点亮核心板小灯 STM32U575
stm32·单片机·嵌入式硬件
end_SJ14 小时前
初学stm32 --- 定时器中断
stm32·单片机·嵌入式硬件
南城花随雪。14 小时前
单片机:实现数码管动态显示(0~99999999)74hc138驱动(附带源码)
单片机·嵌入式硬件
南城花随雪。16 小时前
单片机:实现信号发生器(附带源码)
单片机·嵌入式硬件