55. 内存管理
一、什么是内存管理
内存管理,是指软件运行时对计算机内存资源的分配和使用的技术。其最主要的目的是如何高效、快速的分配,并且在适当的时候释放和回收内存资源。内存管理的实现方法有很多种,其实最终都是要实现两个函数:malloc() 和 free()。malloc() 函数用来内存申请,free() 函数用于内存释放。
标准的 C 库也提供了 malloc() 函数和 free() 函数来实现申请和释放内存,但是它存在以下问题:
- 占用大量的代码空间,不适合在资源紧缺的嵌入式系统。
- 没有线程安全的相关机制。
- 运行有不确定性,每次调用这些函数时花费的时间可能都不相同。
- 内存碎片化。
二、分块式内存管理

从上图可以看出,分块式内存管理由内存池和内存管理表两部分组成。内存池被等分为了 n 块,对应的内存管理表,大小也为 n,内存管理表的每一个项对应内存池的一块内存。
内存管理表的项值代表的意义为:当该项值为 0 的时候,代表对应的内存块未被占用,当该项值非零的时候,代表该项对应的内存块已经被占用,其数值则代表被连续占用的内存块数。比如某项值为 10,那么说明包括本项对应的内存块在内,总共分配了 10 个内存块给外部的某个指针。
内存分配方向如上图所示,是从顶 → 底的分配方向。即首先从最末端开始找空内存。当内存管理刚初始化的时候,内存表全部清零,表示没有任何内存块被占用。
【1】、分配原理:指针 p 调用 malloc() 申请内存。
- 先判断 p 要分配的内存块数(m)。
- 从第 n 开始,向下查找,直到找到 m 块连续的空内存块(即对应内存管理表项为 0)。
- 将这 m 个内存管理表项的值都设置为 m(标记被占用)。
- 把最后的这个空内存块的地址返回指针 p,完成一次分配。
注意:如果当内存不够的时候(找到最后也没有找到连续 m 块空闲内存),则返回 NULL 给 p,表示分配失败。
【2】、释放原理:当 p 申请的内存用完,需要释放的时候,调用 free() 函数实现。
- 先判断 p 指向的内存地址所对应的内存块。
- 找到对应的内存管理表项目,得到 p 所占用的内存块数目 m(内存管理表项目的值就是所分配内存块的数目)。
- 将这 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;
}

浙公网安备 33010602011771号