RMP
RMP(Rice Memory Pool)全称内存池分配算法,它是超级高效,并且线程安全的内存池组件。
开源链接:https://github.com/RiceChen0/rmp
RMP 背景
RMP 设计初衷
- 动态申请内存的长度相近,并且申请次数频繁,期望有一个高效却合理处理内存分配和释放的软件组件。
- 第一点的场景在 RTOS 环境中,期望有一个满足线程安全的内存分配和释放的软件组件。
RMP 特点
- 效率性:提供一个高效分配的内存分配算法。
- 安全性:针对 RTOS 环境,提供线程安全的内存分配方式。
- 跨平台性:支持任意的 RTOS 环境,裸机环境。
- 资源损耗:几乎可以忽略 RAM 和 ROM 的消耗。
- 模式支持:支持静态模式和动态模式。
- 通信方式:支持阻塞方式和非阻塞方式。
应用场景
- 场景 1:在一些协议设计场景中,且协议报文长度相近
- 处理方式 1:采用动态内存申请分配协议报文存储空间。存在问题:反复的内存申请释放,会导致内存碎片。
- 处理方式 2:采用一个大内存,用户拆成多个块,自行管理。存在问题:增加代码的复杂度。
- 场景 2:在 RTOS 环境中,邮箱的传输过程,邮箱指传输报文的起始地址,它不像消息队列一样内部实现会做数据拷贝。所以需要防护传输报文的内存。
- 处理方式 1:在邮箱发送线程申请报文内存,在邮箱接收线程释放报文内存。存在问题:反复的内存申请释放,会导致内存碎片。
- 处理方式 2:采用一个大内存,用户拆成多个块,自行管理,在邮箱发送线程获取内存块,在邮箱接收线程释放内存块。存在问题:增加代码的复杂度。
- 处理方式 3:如果你使用的系统是 RT-RThread,可以利用 RT-Thread 的内核对象--内存池,大部分系统没有提供该机制。
RMP 软件设计
RMP 初始化框图
RMP 初始化软件设计说明
- RMP 的设计是将一个大的内存块平均划分为多个大小相同的内存块。
- 通过下一个内存的起始 4 个字节保存上一个内存块的地址起始地址。
- 最后一个内存块的起始地址,通过一个 free_list 保存。这样就可以形成一个链式存储。
- 这种方式可以通过 free_list 快速的查找到任意的内存块。
RMP 内存申请框图
RMP 内存申请软件设计说明
- 当内存块申请的时候,通过 block4 的前 4 个字节找到 block3 的起始地址。
- free_list 保存 block3 的起始地址。
- 返回 block3 的起始地址。
- 这时内存池将减少一内存块。
- 以此类推。
RMP 内存释放框图
RMP 内存释放软件设计说明
- 当 block2 释放的时候,block2 的前四个字节保存 free_list 指向的地址。
- free_list 保存 block2 的起始地址。
- 这是内存池将增加一个内存块。
- 以此类推。
RMP 目录结构
├─adapter
│ └─rtthread
│ ├─rmp_mutex.c // rtthread mutex适配层
│ └─rmp_sem.c // rtthread sem适配层
├─example
│ └─rmp_rtt_example.c // rtthread 平台实例
├─include
│ ├─rmp_def.h // rmp 通用接口定义
│ └─rmp.h // rmp 对外头文件
└─src
└─rmp.c // rmp 核心代码源文件
RMP 接口说明
- 动态创建内存池
根据单个内存块的大小和内存块个数,申请内存池。
内存池的空间通过 malloc 方式申请
mp_t *rmp_create(uint32_t size, uint32_t count);
- 删除动态创建的内存池
内存池的空间通过 free 方式释放。
voidrmp_delete(rmp_t *mp);
- 内存池初始化
内存池的空间由用户提供。
告知内存池单个内存块的大小和内存块个数
voidrmp_init(rmp_t *mp, void *mem, uint32_t size, uint32_t count);
- 内存池去初始化
voidrmp_deinit(rmp_t *mp);
- 阻塞方式从内存池申请内存块
在 RTOS 环境,它是阻塞方式从内存池申请内存块
在非 RTOS 环境,它是非阻塞方式从内存池申请内存块
void *rmp_alloc(rmp_t *mp);
- 非阻塞方式从内存池申请内存块
在 RTOS 环境,它是非阻塞方式从内存池申请内存块
在非 RTOS 环境,它和 rmp_alloc 功能一样。它是非阻塞方式从内存池申请内存块。
void *rmp_try_alloc(rmp_t *mp);
- 释放内存块
在 RTOS 环境,它是阻塞方式从内存池申请内存块
在非 RTOS 环境,它是非阻塞方式从内存池申请内存块
voidrmp_free(rmp_t *mp, void *ptr);
- 内存池中可用内存块剩余个数
uint32_trmp_available(rmp_t *mp);
RMP 验证
实验说明:分别通过静态和动态定义一个内存池,内存池大小为(16 * 4),每个内存块为 16,一共 4 块。连续申请 5 块,再释放第 4 块,再申请一块。
- 静态内存池方式:
#include "rmp.h"
int rmp_init(void)
{
uint8_t buff[16 * 4];
rmp_t mp;
rmp_init(&mp, buff, 16, 4);
void *mem1 = rmp_try_alloc(&mp);
if (mem1 != NULL)
rt_kprintf("mem: 0x%08X\n", (int)mem1);
else
rt_kprintf("mem: NULL\n");
void *mem2 = rmp_try_alloc(&mp);
if (mem2 != NULL)
rt_kprintf("mem: 0x%08X\n", (int)mem2);
else
rt_kprintf("mem: NULL\n");
void *mem3 = rmp_try_alloc(&mp);
if (mem3 != NULL)
rt_kprintf("mem: 0x%08X\n", (int)mem3);
else
rt_kprintf("mem: NULL\n");
void *mem4 = rmp_try_alloc(&mp);
if (mem4 != NULL)
rt_kprintf("mem: 0x%08X\n", (int)mem4);
else
rt_kprintf("mem: NULL\n");
void *mem5 = rmp_try_alloc(&mp);
if (mem5 != NULL)
rt_kprintf("mem: 0x%08X\n", (int)mem5);
else
rt_kprintf("mem: NULL\n");
rmp_free(&mp, mem4);
void *mem6 = rmp_try_alloc(&mp);
if (mem6 != NULL)
rt_kprintf("mem: 0x%08X\n", (int)mem6);
else
rt_kprintf("mem: NULL\n");
return RT_EOK;
}
INIT_COMPONENT_EXPORT(rmp_init);
- 动态内存池方式:
#include "rmp.h"
int rmp_init(void)
{
rmp *mp = NULL;
mp = rmp_create(16, 4);
if (mp == NULL)
{
rt_kprintf("rmp create failed\n");
return -1;
}
void *mem1 = rmp_try_alloc(mp);
if (mem1 != NULL)
rt_kprintf("mem: 0x%08X\n", (int)mem1);
else
rt_kprintf("mem: NULL\n");
void *mem2 = rmp_try_alloc(mp);
if (mem2 != NULL)
rt_kprintf("mem: 0x%08X\n", (int)mem2);
else
rt_kprintf("mem: NULL\n");
void *mem3 = rmp_try_alloc(mp);
if (mem3 != NULL)
rt_kprintf("mem: 0x%08X\n", (int)mem3);
else
rt_kprintf("mem: NULL\n");
void *mem4 = rmp_try_alloc(mp);
if (mem4 != NULL)
rt_kprintf("mem: 0x%08X\n", (int)mem4);
else
rt_kprintf("mem: NULL\n");
void *mem5 = rmp_try_alloc(mp);
if (mem5 != NULL)
rt_kprintf("mem: 0x%08X\n", (int)mem5);
else
rt_kprintf("mem: NULL\n");
rmp_free(&mp, mem4);
void *mem6 = rmp_try_alloc(mp);
if (mem6 != NULL)
rt_kprintf("mem: 0x%08X\n", (int)mem6);
else
rt_kprintf("mem: NULL\n");
return RT_EOK;
}
INIT_COMPONENT_EXPORT(rmp_init);
- 实验运行结果:成功申请 4 次,失败 1 次,然后释放了 1 次,又可以申请 1 次。
总结:
1. rmp 是一个非常高效且安全的内存池组件。
2. rmp 可以减少对 malloc 和 free 的使用,避免内存碎片的产生。
3. rmp 已经集成到 RT-Thread 的软件包中,可在 RT-Thread 的软件包下载
4. 开源链接:https://github.com/RiceChen0/rmp
5. 在设计 rmp 的同时,也发现 RT-Thread 内核自带的内存池是很浪费空间并且时间上不是最佳的,我也给 RT-Thread 社区提了一个对应的 issue。
END
作者:RiceChen
原文:Rice嵌入式
推荐阅读
欢迎关注Rice 嵌入式开发技术分享极术专栏,欢迎添加极术小姐姐微信(id:aijishu20)加入技术交流群,请备注研究方向。