55. 内存管理

一、什么是内存管理

  内存管理,是指软件运行时对计算机内存资源的分配和使用的技术。其最主要的目的是如何高效、快速的分配,并且在适当的时候释放和回收内存资源。内存管理的实现方法有很多种,其实最终都是要实现两个函数:malloc() 和 free()。malloc() 函数用来内存申请,free() 函数用于内存释放。

  标准的 C 库也提供了 malloc() 函数和 free() 函数来实现申请和释放内存,但是它存在以下问题:

  • 占用大量的代码空间,不适合在资源紧缺的嵌入式系统。
  • 没有线程安全的相关机制。
  • 运行有不确定性,每次调用这些函数时花费的时间可能都不相同。
  • 内存碎片化。

二、分块式内存管理

分块式内存管理原理

  从上图可以看出,分块式内存管理由内存池和内存管理表两部分组成。内存池被等分为了 n 块,对应的内存管理表,大小也为 n,内存管理表的每一个项对应内存池的一块内存。

  内存管理表的项值代表的意义为:当该项值为 0 的时候,代表对应的内存块未被占用,当该项值非零的时候,代表该项对应的内存块已经被占用,其数值则代表被连续占用的内存块数。比如某项值为 10,那么说明包括本项对应的内存块在内,总共分配了 10 个内存块给外部的某个指针。

  内存分配方向如上图所示,是从顶 → 底的分配方向。即首先从最末端开始找空内存。当内存管理刚初始化的时候,内存表全部清零,表示没有任何内存块被占用。

【1】、分配原理:指针 p 调用 malloc() 申请内存。

  1. 先判断 p 要分配的内存块数(m)。
  2. 从第 n 开始,向下查找,直到找到 m 块连续的空内存块(即对应内存管理表项为 0)。
  3. 将这 m 个内存管理表项的值都设置为 m(标记被占用)。
  4. 把最后的这个空内存块的地址返回指针 p,完成一次分配。

注意:如果当内存不够的时候(找到最后也没有找到连续 m 块空闲内存),则返回 NULL 给 p,表示分配失败。

【2】、释放原理:当 p 申请的内存用完,需要释放的时候,调用 free() 函数实现。

  1. 先判断 p 指向的内存地址所对应的内存块。
  2. 找到对应的内存管理表项目,得到 p 所占用的内存块数目 m(内存管理表项目的值就是所分配内存块的数目)。
  3. 将这 m 个内存管理表项目的值都清零,标记释放,完成一次内存释放。

  当我们使用内存管理时,占用的总空间大小为 内存池大小和内存管理表大小之和。 例如,当我们管理内存池大小为 10K,每个单独的内存块大小为 32B 大小时,这是内存池中会有 320 个内存块。由于每个内存块都会对应内存管理表中的一项,我们还需要一个内存空间为 320 * 2 = 640B 的内存管理表。

三、内存管理的使用

3.1、初始化内存

  当我们烧入二进制文件到单片机时会将文件烧入到 FLASH 中。FLASH 存储器主要用于存放用户程序和常量数据。程序启动时,MCU 会从 FLASH 的起始地址(0x0800 0000)开始读取代码和常量,初始化过的全局变量和静态变量会在程序启动时被复制到 RAM 中。RAM是STM32中的易失性存储器,用于存放程序运行时的数据,包括栈、堆以及动态分配的内存和已初始化的全局变量、静态变量。

  不同类型的 STM32 单片机的 SRAM 大小是不一样的,但 起始地址 都是 0x2000 0000终止地址都是 0x2000 0000 + 其固定的容量大小。STM32F103C8T6 的 SRAM(数据存储器)大小为 20KB,它的范围是 0x2000 0000 ~ 0x2000 4FFF

#define SRAM_MEMORY_POOL_SIZE           10 * 1024
#define SRAM_MEMORY_POOL_BLOCK_SIZE     32
#define SRAM_MEMORY_POOL_BLOCK_COUNT    SRAM_MEMORY_POOL_SIZE / SRAM_MEMORY_POOL_BLOCK_SIZE

typedef struct Memory_t
{
    uint8_t *pool;
    uint16_t *table;
    uint32_t pool_size;
    uint32_t block_size;
    uint32_t block_count;
    uint32_t used_block_count;
} memory_t;
memory_t g_sram_memory;
__attribute((aligned (64))) uint8_t g_sram_memory_pool[SRAM_MEMORY_POOL_SIZE];
uint16_t g_srammemory_table[SRAM_MEMORY_POOL_BLOCK_COUNT];

/**
 * @brief 内存管理初始化
 * 
 * @param memory 要管理的内存
 * @param pool 内存池的地址
 * @param table 内存表的地址
 * @param pool_size 内存池的大小
 * @param block_size 单个内存块的大小
 */
void memory_init(memory_t *memory, uint8_t *pool, uint16_t *table, uint32_t pool_size, uint32_t block_size)
{
    uint32_t block_count = pool_size / block_size;

    memory_set_value(pool, 0, pool_size);
    memory_set_value(table, 0, block_count * 2);

    memory->pool = pool;
    memory->table = table;
    memory->pool_size = pool_size;
    memory->block_size = block_size;
    memory->block_count = block_count;
    memory->used_block_count = 0;
}

/**
 * @brief 设置内存的值
 * 
 * @param ptr 要设置的内存的地址
 * @param value 要设置的值
 * @param size 要设置的内存的字节数
 */
void memory_set_value(void *ptr, uint8_t value, uint32_t size)
{
    uint8_t *p = ptr;

    while (size--)
    {
        *p++ = value;
    }
}

/**
 * @brief 复制内存
 * 
 * @param des 目的地址
 * @param src 源地址
 * @param n 复制的字节数
 */
void memory_copy(void *des, void *src, uint32_t n)
{
    uint8_t *xdes = des;
    uint8_t *xsrc = src;

    while (n--) 
    {
        *xdes++ = *xsrc++;
    }
}

__attribute((aligned (64))) 定义内存池为 64 字节对齐,如果不加上这个限制,在某些情况下(比如分配内存给结构体指针),可能出现错误。

3.2、申请内存

/**
 * @brief 申请内存
 * 
 * @param memory 要管理的内存
 * @param size 要申请的字节数
 * @return void* 申请的内存块的地址
 */
void * memory_malloc(memory_t *memory, uint32_t size)
{
    uint32_t connected_memory_block = 0;

    if (size <= 0)
    {
        return NULL;
    }
  
    // 需要的内存块数
    uint32_t block_count = size % memory->block_size ? size / memory->block_size + 1 : size / memory->block_size;
  
    // 从表尾往头寻找空闲块
    for (uint32_t offset = memory->block_count - 1; offset >= 0; offset--)
    {
        // 找到空闲块,则相连的内存块数加一,否则清零
        connected_memory_block = (memory->table[offset]) ? 0 : connected_memory_block + 1;
   
        if (connected_memory_block == block_count)
        {
            // 连续块数满足,则标记为已使用
            for (uint32_t i = 0; i < block_count; i++)
            {
                memory->table[offset + i] = connected_memory_block;
            }

            // 返回内存块的地址
            return (void *)((uint32_t)memory->pool + offset * memory->block_size);
        }
    }

    return NULL;
}

3.3、释放内存

/**
 * @brief 释放内存
 * 
 * @param memory 要管理的内存
 * @param ptr 要释放的内存块的地址
 */
void memory_free(memory_t *memory, void *ptr)
{
    if ((uint32_t) ptr < (uint32_t)memory->pool || (uint32_t)ptr >= (uint32_t)memory->pool + memory->pool_size)
    {
        return;
    }

    uint32_t offset = (uint32_t)ptr - (uint32_t)memory->pool;                   // 获取内存块地址偏移量
    uint32_t index = offset / memory->block_size;                               // 计算内存块索引
    uint16_t block_count = memory->table[index];                                // 获取占用的内存块数

    // 释放内存块
    for (uint32_t i = 0; i < block_count; i++)
    {
        memory->table[index + i] = 0;
    }
  
    // 清零内存块
    memory_set_value(ptr, 0, block_count * memory->block_size);
}

3.4、重新分配内存大小

/**
 * @brief 重新分配内存函数
 * 
 * @param memory 要管理的内存
 * @param ptr 旧内存首地址
 * @param size 要分配的内存大小(字节)
 * @return void* 新分配到的内存首地址
 */
void * memory_realloc(memory_t *memory, void *ptr, uint32_t size)
{
    uint32_t *offset = 0;
  
    offset = memory_malloc(memory, size);

    if (offset == NULL)                                                         // 申请出错,返回NULL
    {
        return NULL;
    }
    else                                                                        // 申请没问题, 返回首地址
    {
        memory_copy((void *)((uint32_t)memory->pool + offset), ptr, size);      // 拷贝旧内存内容到新内存
        memory_free(memory, ptr);                                               // 释放旧内存
        return (void *)((uint32_t)memory->pool + offset);                       // 返回新内存首地址
    }
}

3.5、获取内存使用率

/**
 * @brief 获取内存使用率
 * 
 * @param memory 要管理的内存
 * @return uint8_t 内存的使用率
 */
uint8_t memory_get_usage_rate(memory_t *memory)
{
    memory->used_block_count = 0;

    for (uint32_t i = 0; i < memory->block_count; i++)
    {
        if (memory->table[i])
        {
            memory->used_block_count++;
        }
    }

    return (memory->used_block_count * 100) / memory->block_count;
}
posted @ 2025-03-28 20:34  星光映梦  阅读(183)  评论(0)    收藏  举报