MCU上的内存管理

正点原子的内存管理代码

malloc.c

#include "malloc.h"

/* 内存池(64字节对齐) */

static __ALIGNED(64) uint8_t mem1base[MEM1_MAX_SIZE];   /* 内部SRAM内存池 */

/* 内存管理表 */

static MT_TYPE mem1mapbase[MEM1_ALLOC_TABLE_SIZE];      /* 内部SRAM内存池MAP */

/* 内存管理参数 */

const uint32_t memtblsize[SRAMBANK] = {MEM1_ALLOC_TABLE_SIZE};  /* 内存表大小 */

const uint32_t memblksize[SRAMBANK] = {MEM1_BLOCK_SIZE};        /* 内存分块大小 */

const uint32_t memsize[SRAMBANK] = {MEM1_MAX_SIZE,};            /* 内存总大小 */

/* 内存管理控制器 */
struct _m_mallco_dev mallco_dev =
{
    my_mem_init,    /* 内存初始化 */

    my_mem_perused, /* 内存使用率 */

    mem1base,       /* 内存池 */

    mem1mapbase,    /* 内存管理状态表 */

    0,              /* 内存管理未就绪 */
};

/**

 * @brief       复制内存

 * @param       *des : 目的地址

 * @param       *src : 源地址

 * @param       n    : 需要复制的内存长度(字节为单位)

 * @retval      无

 */

void my_mem_copy(void *des, void *src, uint32_t n)
{
    uint8_t *xdes = des;

    uint8_t *xsrc = src;

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

/**

 * @brief       设置内存值

 * @param       *s    : 内存首地址

 * @param       c     : 要设置的值

 * @param       count : 需要设置的内存大小(字节为单位)

 * @retval      无

 */

void my_mem_set(void *s, uint8_t c, uint32_t count)
{
    uint8_t *xs = s;
    
    while (count--) *xs++ = c;
}

/**

 * @brief       内存管理初始化

 * @param       memx : 所属内存块

 * @retval      无

 */

void my_mem_init(uint8_t memx)
{
    uint8_t mttsize = sizeof(MT_TYPE);  /* 获取memmap数组的类型长度(uint16_t /uint32_t)*/

    my_mem_set(mallco_dev.memmap[memx], 0, memtblsize[memx] * mttsize); /* 内存状态表数据清零 */

    mallco_dev.memrdy[memx] = 1;        /* 内存管理初始化OK */
}

/**

 * @brief       获取内存使用率

 * @param       memx : 所属内存块

 * @retval      使用率(扩大了10倍,0~1000,代表0.0%~100.0%)

 */

uint16_t my_mem_perused(uint8_t memx)
{
    uint32_t used = 0;

    uint32_t i;

    for (i = 0; i < memtblsize[memx]; i++)
    {
        if (mallco_dev.memmap[memx][i]) used++;
    }

    return (used * 1000) / (memtblsize[memx]);
}

  

/**

 * @brief       内存分配(内部调用)

 * @param       memx : 所属内存块

 * @param       size : 要分配的内存大小(字节)

 * @retval      内存偏移地址

 *   @arg       0 ~ 0XFFFFFFFE : 有效的内存偏移地址

 *   @arg       0XFFFFFFFF     : 无效的内存偏移地址

 */

static uint32_t my_mem_malloc(uint8_t memx, uint32_t size)
{
    signed long offset = 0;

    uint32_t nmemb;         /* 需要的内存块数 */

    uint32_t cmemb = 0;     /* 连续空内存块数 */

    uint32_t i;
    
    if (!mallco_dev.memrdy[memx])
    {
        mallco_dev.init(memx);          /* 未初始化,先执行初始化 */
    }

    if (size == 0) return 0XFFFFFFFF;   /* 不需要分配 */
    
    nmemb = size / memblksize[memx] + (!!(size % memblksize[memx]));

    for (offset = memtblsize[memx] - 1; offset >= 0; offset--)  /* 搜索整个内存控制区 */
    {
        if (!mallco_dev.memmap[memx][offset])
        {
            cmemb++;            /* 连续空内存块数增加 */
        }
        else
        {
            cmemb = 0;          /* 连续内存块清零 */
        }
        
        if (cmemb == nmemb)     /* 找到了连续nmemb个空内存块 */
        {
            for (i = 0; i < nmemb; i++)         /* 标注内存块非空 */
            {
                mallco_dev.memmap[memx][offset + i] = nmemb;
            }
            
            return (offset * memblksize[memx]); /* 返回偏移地址 */
        }
    }

    return 0XFFFFFFFF;  /* 未找到符合分配条件的内存块 */
}

  

/**

 * @brief       释放内存(内部调用)

 * @param       memx   : 所属内存块

 * @param       offset : 内存地址偏移

 * @retval      释放结果

 *   @arg       0, 释放成功;

 *   @arg       1, 释放失败;

 *   @arg       2, 超区域了(失败);

 */

static uint8_t my_mem_free(uint8_t memx, uint32_t offset)
{

    int i;

    if (!mallco_dev.memrdy[memx])   /* 未初始化,先执行初始化 */
    {
        mallco_dev.init(memx);

        return 1;                   /* 未初始化 */
    }

    if (offset < memsize[memx])     /* 偏移在内存池内. */
    {
        int index = offset / memblksize[memx];      /* 偏移所在内存块号码 */

        int nmemb = mallco_dev.memmap[memx][index]; /* 内存块数量 */

        for (i = 0; i < nmemb; i++)                 /* 内存块清零 */
        {
            mallco_dev.memmap[memx][index + i] = 0;
        }
        
        return 0;
    }
    else
    {
        return 2;   /* 偏移超区了. */
    }
}

/**

 * @brief       释放内存(外部调用)

 * @param       memx : 所属内存块

 * @param       ptr  : 内存首地址

 * @retval      无

 */
void myfree(uint8_t memx, void *ptr)
{
    uint32_t offset;
    
    if (ptr == NULL) return;    /* 地址为0. */
    
    offset = (uint32_t)ptr - (uint32_t)mallco_dev.membase[memx];

    my_mem_free(memx, offset);  /* 释放内存 */
}

  

/**

 * @brief       分配内存(外部调用)

 * @param       memx : 所属内存块

 * @param       size : 要分配的内存大小(字节)

 * @retval      分配到的内存首地址.

 */

void *mymalloc(uint8_t memx, uint32_t size)
{
    uint32_t offset;

    offset = my_mem_malloc(memx, size);
    
    if (offset == 0XFFFFFFFF)   /* 申请出错 */
    {
        return NULL;            /* 返回空(0) */
    }
    else    /* 申请没问题, 返回首地址 */
    {
        return (void *)((uint32_t)mallco_dev.membase[memx] + offset);
    }
}

  

/**

 * @brief       重新分配内存(外部调用)

 * @param       memx : 所属内存块

 * @param       *ptr : 旧内存首地址

 * @param       size : 要分配的内存大小(字节)

 * @retval      新分配到的内存首地址.

 */

void *myrealloc(uint8_t memx, void *ptr, uint32_t size)
{
    if(NULL == ptr)
    {
        return mymalloc(memx, size);    /* 旧地址为NULL, 相当于分配新内存 */
    }
    
    if(0 == size)
    {
        myfree(memx, ptr);             /* 释放内存 */
        return NULL;                   /* 返回空(0) */
    }
    
    uint32_t offset = my_mem_malloc(memx, size); /* 分配新内存 */

    if (offset == 0XFFFFFFFF)   /* 申请出错 */
    {
        return NULL;            /* 返回空(0) */
    }

    uint32_t old_offset = (uint32_t)ptr - (uint32_t)mallco_dev.membase[memx];   /* 偏移地址 */

    uint32_t old_index = old_offset / memblksize[memx];                         /* 偏移所在内存块号码 */

    uint32_t old_size = mallco_dev.memmap[memx][old_index] * memblksize[memx];  /* 旧内存大小 */

    // 使用小内存的大小进行拷贝

    uint32_t copy_size = old_size < size ? old_size : size;

    my_mem_copy((void *)((uint32_t)mallco_dev.membase[memx] + offset), ptr, copy_size); /* 拷贝旧内存内容到新内存 */

    myfree(memx, ptr);      /* 释放旧内存 */

    return (void *)((uint32_t)mallco_dev.membase[memx] + offset);   /* 返回新内存首地址 */

}

malloc.h

#ifndef __MALLOC_H
#define __MALLOC_H

#include <stdlib.h>

/* 定义1个内存池 */
#define SRAMIN      0       /* 内部SRAM, 共64KB */

#define SRAMBANK    1       /* 定义支持的SRAM块数. */

#define _USHORT_MAX_    65535    /* 定义unsigned short 最大值 */

#define _UCHAR_MAX_      255    /* 定义unsigned char 最大值 */


/* 单块内存,内存管理所占用的全部空间大小计算公式如下:

 * size = MEM1_MAX_SIZE + (MEM1_MAX_SIZE / MEM1_BLOCK_SIZE) * sizeof(MT_TYPE)

  

 * 已知总内存容量(size),最大内存池的计算公式如下:

 * MEM1_MAX_SIZE = (MEM1_BLOCK_SIZE * size) / (MEM1_BLOCK_SIZE + sizeof(MT_TYPE))

 * 以SRAMIN为例,MEM1_MAX_SIZE = (32 * 64) / (32 + 2) = 60.24KB ≈ 60KB

 * 但是我们为了给其他全局变量 / 数组等预留内存空间, 这里设置最大管理为 40KB

 */

/* mem1内存参数设定.mem1是F103内部的SRAM. */

#define MEM1_BLOCK_SIZE         32                              /* 内存块大小为32字节 */

#define MEM1_MAX_SIZE           40 * 1024                       /* 最大管理内存 40K, F103RC内部SRAM总共48KB */

#define MEM1_ALLOC_TABLE_SIZE   MEM1_MAX_SIZE/MEM1_BLOCK_SIZE   /* 内存表大小 */

  
/* 定义内存管理表类型,当外扩SDRAM的时候,必须使用uint32_t类型,否则可以定义成uint16_t,以节省内存占用 */
#if MEM1_ALLOC_TABLE_SIZE > _USHORT_MAX_
#define MT_TYPE     uint32_t
#elif MEM1_ALLOC_TABLE_SIZE > _UCHAR_MAX_
#define MT_TYPE     uint16_t
#else 
#define MT_TYPE     uint8_t
#endif
  

/* 如果没有定义NULL, 定义NULL */
#ifndef NULL

#define NULL 0

#endif

  

/* 内存管理控制器 */
struct _m_mallco_dev
{
    void (*init)(uint8_t);          /* 初始化 */
    uint16_t (*perused)(uint8_t);   /* 内存使用率 */
    uint8_t *membase[SRAMBANK];     /* 内存池 管理SRAMBANK个区域的内存 */
    MT_TYPE *memmap[SRAMBANK];      /* 内存管理状态表 */
    uint8_t  memrdy[SRAMBANK];      /* 内存管理是否就绪 */
};

 
extern struct _m_mallco_dev mallco_dev; /* 在mallco.c里面定义 */

/* 函数声明 */

void my_mem_init(uint8_t memx);                             /* 内存管理初始化函数(外/内部调用) */

uint16_t my_mem_perused(uint8_t memx) ;                     /* 获得内存使用率(外/内部调用) */

void my_mem_set(void *s, uint8_t c, uint32_t count);        /* 内存设置函数 */

void my_mem_copy(void *des, void *src, uint32_t n);         /* 内存拷贝函数 */

void myfree(uint8_t memx, void *ptr);                       /* 内存释放(外部调用) */

void *mymalloc(uint8_t memx, uint32_t size);                /* 内存分配(外部调用) */

void *myrealloc(uint8_t memx, void *ptr, uint32_t size);    /* 重新分配内存(外部调用) */

#endif

在这里插入图片描述
使用的是分块式内存管理办法

从上图可以看出,分块式内存管理由内存池和内存管理表两部分组成。内存池被等分为了

n 块,对应的内存管理表,大小也为 n,内存管理表的每一个项对应内存池的一块内存。

内存管理表的项值代表的意义为:当该项值为 0 的时候,代表对应的内存块未被占用,当

该项值非零的时候,代表该项对应的内存块已经被占用,其数值则代表被连续占用的内存块数。

比如某项值为 10,那么说明包括本项对应的内存块在内,总共分配了 10 个内存块给外部的某

个指针。

内存分配方向如上图所示,是从顶→底的分配方向。即首先从最末端开始找空内存。当内

存管理刚初始化的时候,内存表全部清零,表示没有任何内存块被占用。

分配原理:
当指针 p 调用 malloc 申请内存的时候,先判断 p 要分配的内存块数(m),然后从第 n 开
始,向下查找,直到找到 m 块连续的空内存块(即对应内存管理表项为 0),然后将这 m 个内
存管理表项的值都设置为 m(标记被占用),最后,把最后的这个空内存块的地址返回指针 p,
完成一次分配。注意:如果当内存不够的时候(找到最后也没有找到连续 m 块空闲内存),则
返回 NULL 给 p,表示分配失败。

释放原理:
当 p 申请的内存用完,需要释放的时候,调用 free 函数实现。free 函数先判断 p 指向的内
存地址所对应的内存块,然后找到对应的内存管理表项目,得到 p 所占用的内存块数目 m(内
存管理表项目的值就是所分配内存块的数目),将这 m 个内存管理表项目的值都清零,标记释
放,完成一次内存释放。

代码讲解

  1. 将内存池分块,先定义每个块的字节数本内存池的总字节数,用总字节数除以每个块的字节数得到块数
#define MEM1_BLOCK_SIZE         32                              /* 内存块大小为32字节 */

#define MEM1_MAX_SIZE           40 * 1024                       /* 最大管理内存 40K, F103RC内部SRAM总共48KB */

#define MEM1_ALLOC_TABLE_SIZE   MEM1_MAX_SIZE/MEM1_BLOCK_SIZE   /* 内存表大小 */
  1. 内存池实际上是一块数组
/* 内存池(64字节对齐) */

static __ALIGNED(64) uint8_t mem1base[MEM1_MAX_SIZE];   /* 内部SRAM内存池 */
  1. 内存管理表,实际也是一块数组,总元素个数为内存块个数,每个元素对应一块内存块,该元素非零时代表此内存块已被占用,管理表的数据类型是根据内存块最大个数来定义的。当内存块个数小于255个时,此时管理表的类型为uint8_t类型,当内存块个数小于65535个时,此时管理表的类型为uint16_t类型,当内存块数大于65535个时,使用uin32_t类型
/* 内存管理表 */

static MT_TYPE mem1mapbase[MEM1_ALLOC_TABLE_SIZE];      /* 内部SRAM内存池MAP */
  1. Coder可以定义不同的内存池,不同的内存管理表的大小内存块大小内存总大小等参数由以下几个数组管理定义
/* 内存管理参数 */

const uint32_t memtblsize[SRAMBANK] = {MEM1_ALLOC_TABLE_SIZE};  /* 内存表大小 */

const uint32_t memblksize[SRAMBANK] = {MEM1_BLOCK_SIZE};        /* 内存分块大小 */

const uint32_t memsize[SRAMBANK] = {MEM1_MAX_SIZE,};            /* 内存总大小 */
  1. 内存管理控制器,属性成员有内存管理的初始化方法、内存使用率评估函数、内存池的地址、内存管理表的地址、内存管理状态
/* 内存管理控制器 */

struct _m_mallco_dev

{

    void (*init)(uint8_t);          /* 初始化 */

    uint16_t (*perused)(uint8_t);   /* 内存使用率 */

    uint8_t *membase[SRAMBANK];     /* 内存池 管理SRAMBANK个区域的内存 */

    MT_TYPE *memmap[SRAMBANK];      /* 内存管理表 */

    uint8_t  memrdy[SRAMBANK];      /* 内存管理是否就绪 */

};
  1. 内存管理控制器实例静态初始化
/* 内存管理控制器 */

struct _m_mallco_dev mallco_dev =

{

    my_mem_init,    /* 内存初始化 */

    my_mem_perused, /* 内存使用率 */

    mem1base,       /* 内存池 */

    mem1mapbase,    /* 内存管理状态表 */

    0,              /* 内存管理未就绪 */

};
  1. 内存拷贝函数和填充函数,其实就是memcpy和memset函数

void my_mem_copy(void *des, void *src, uint32_t n)

{

    uint8_t *xdes = des;

    uint8_t *xsrc = src;

  

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

}

void my_mem_set(void *s, uint8_t c, uint32_t count)

{

    uint8_t *xs = s;

  

    while (count--) *xs++ = c;

}
  1. 初始化函数,将指定内存管理表的内容全部用0填充,内存就绪状态置位
void my_mem_init(uint8_t memx)

{

    uint8_t mttsize = sizeof(MT_TYPE);  /* 获取memmap数组的类型长度(uint16_t /uint32_t)*/

    my_mem_set(mallco_dev.memmap[memx], 0, memtblsize[memx] * mttsize); /* 内存状态表数据清零 */

    mallco_dev.memrdy[memx] = 1;        /* 内存管理初始化OK */

}
  1. 计算内存使用率函数,计算的是内存块使用的比例,而不是字节的比例
uint16_t my_mem_perused(uint8_t memx)
{
    uint32_t used = 0;

    uint32_t i;

    for (i = 0; i < memtblsize[memx]; i++)
    {
        if (mallco_dev.memmap[memx][i]) used++;
    }

    return (used * 1000) / (memtblsize[memx]);
}
  1. 分配内存函数(static)内部调用
    1. 检查指定的内存池是否已初始化,若未初始化,先初始化
    2. 检查输入参数(需要申请的字节数),若为0,则表示不需要分配内存,直接退出
    3. 计算需要的内存块数,向上取整
    4. 从内存块的末尾开始往前遍历,寻找所需的连续内存块
    5. 将即将用到的内存块对应的内存管理表中的元素置为所需的连续块数
    6. 返回的偏移地址为所需连续块的起始块相对于内存池的偏移地址
static uint32_t my_mem_malloc(uint8_t memx, uint32_t size)
{

    signed long offset = 0;

    uint32_t nmemb;         /* 需要的内存块数 */
    
    uint32_t cmemb = 0;     /* 连续空内存块数 */

    uint32_t i;

    if (!mallco_dev.memrdy[memx])
    {
        mallco_dev.init(memx);          /* 未初始化,先执行初始化 */
    }

    if (size == 0) return 0XFFFFFFFF;   /* 不需要分配 */

    nmemb = size / memblksize[memx] + (!!(size % memblksize[memx]));

    for (offset = memtblsize[memx] - 1; offset >= 0; offset--)  /* 搜索整个内存控制区 */
    {
        if (!mallco_dev.memmap[memx][offset])
        {
            cmemb++;            /* 连续空内存块数增加 */
        }
        else
        {
            cmemb = 0;          /* 连续内存块清零 */
        }

        if (cmemb == nmemb)     /* 找到了连续nmemb个空内存块 */
        {
            for (i = 0; i < nmemb; i++)         /* 标注内存块非空 */
            {
                mallco_dev.memmap[memx][offset + i] = nmemb;
            }
            return (offset * memblksize[memx]); /* 返回偏移地址 */
        }
    }
    
    return 0XFFFFFFFF;  /* 未找到符合分配条件的内存块 */
}
  1. 释放内存函数(static)内部调用
    1. 检查内存池是否已经初始化,若未初始化,则初始化后直接返回,没有初始化,自然不需要释放内存
    2. 检查输入参数偏移量是否在内存池地址内,若不在,返回错误码
    3. 用地址偏移除以内存块的大小,得到偏移索引,利用偏移索引得到申请的内存块的数量
    4. 从偏移索引开始,清零内存块,数量为申请的内存块的数量
static uint8_t my_mem_free(uint8_t memx, uint32_t offset)
{

    int i;

    if (!mallco_dev.memrdy[memx])   /* 未初始化,先执行初始化 */
    {
        mallco_dev.init(memx);

        return 1;                   /* 未初始化 */
    }

    if (offset < memsize[memx])     /* 偏移在内存池内. */
    {
        int index = offset / memblksize[memx];      /* 偏移所在内存块号码 */

        int nmemb = mallco_dev.memmap[memx][index]; /* 内存块数量 */

        for (i = 0; i < nmemb; i++)                 /* 内存块清零 */
        {
            mallco_dev.memmap[memx][index + i] = 0;
        }
        
        return 0;
    }
    else
    {
        return 2;   /* 偏移超区了. */
    }
}
  1. 内存释放函数,供外部调用,利用传入的指针计算基于内存池基地址开始的偏移,再调用上述函数释放内存
void myfree(uint8_t memx, void *ptr)
{
    uint32_t offset;
    
    if (ptr == NULL) return;    /* 地址为0. */
    
    offset = (uint32_t)ptr - (uint32_t)mallco_dev.membase[memx];

    my_mem_free(memx, offset);  /* 释放内存 */
}
  1. 内存分配函数,供外部调用。首先调用内部的分配函数得到偏移地址,检查偏移地址的正确性,再返回(偏移地址+内存池基地址)
void *mymalloc(uint8_t memx, uint32_t size)
{
    uint32_t offset;

    offset = my_mem_malloc(memx, size);
    
    if (offset == 0XFFFFFFFF)   /* 申请出错 */
    {
        return NULL;            /* 返回空(0) */
    }
    else    /* 申请没问题, 返回首地址 */
    {
        return (void *)((uint32_t)mallco_dev.membase[memx] + offset);
    }
}
  1. 扩大分配内存函数,之前的正点原子的代码有些问题。已修改
    1. 当输入的旧地址指针为空时,直接调用mymalloc分配新的内存,符合标准的realloc规范
    2. 当size为0时,释放原有内存,并返回NULL,这也是标准realloc规范
    3. 分配新的内存成功后,才释放旧的内存,保证数据不会丢失
    4. 计算旧内存的大小,与新内存进行比较,使用二者中的较小值进行数据拷贝,避免数组越界
void *myrealloc(uint8_t memx, void *ptr, uint32_t size)
{
    if(NULL == ptr)
    {
        return mymalloc(memx, size);    /* 旧地址为NULL, 相当于分配新内存 */
    }
    
    if(0 == size)
    {
        myfree(memx, ptr);             /* 释放内存 */
        return NULL;                   /* 返回空(0) */
    }
    
    uint32_t offset = my_mem_malloc(memx, size); /* 分配新内存 */

    if (offset == 0XFFFFFFFF)   /* 申请出错 */
    {
        return NULL;            /* 返回空(0) */
    }

    uint32_t old_offset = (uint32_t)ptr - (uint32_t)mallco_dev.membase[memx];   /* 偏移地址 */

    uint32_t old_index = old_offset / memblksize[memx];                         /* 偏移所在内存块号码 */

    uint32_t old_size = mallco_dev.memmap[memx][old_index] * memblksize[memx];  /* 旧内存大小 */

    // 使用小内存的大小进行拷贝

    uint32_t copy_size = old_size < size ? old_size : size;

    my_mem_copy((void *)((uint32_t)mallco_dev.membase[memx] + offset), ptr, copy_size); /* 拷贝旧内存内容到新内存 */

    myfree(memx, ptr);      /* 释放旧内存 */

    return (void *)((uint32_t)mallco_dev.membase[memx] + offset);   /* 返回新内存首地址 */

}