打印

RMP(高效且安全的内存池)

[复制链接]
59|0
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
fanfanluo|  楼主 | 2024-12-21 22:38 | 只看该作者 |只看大图 回帖奖励 |倒序浏览 |阅读模式

RMP(高效且安全的内存池)

RMP(Rice Memory Pool)全称内存池,它是超级高效,并且线程安全的内存池组件。

RMP背景

RMP设计初衷
  1. 动态申请内存的长度相近,并且申请次数频繁,期望有一个高效却合理处理内存分配和释放的软件组件。
  2. 第一点的场景在RTOS环境中,期望有一个满足线程安全的内存分配和释放的软件组件。
RMP特点
  1. 效率性:提供一个高效分配的内存分配算法。
  2. 安全性:针对RTOS环境,提供线程安全的内存分配方式。
  3. 跨平台性:支持任意的RTOS环境,裸机环境。
  4. 资源损耗:几乎可以忽略RAM和ROM的消耗。
  5. 模式支持:支持静态模式和动态模式。
  6. 通信方式:支持阻塞方式和非阻塞方式。
应用场景
  1. 场景1:在一些协议设计场景中,且协议报文长度相近

    • 处理方式1:采用动态内存申请分配协议报文存储空间。存在问题:反复的内存申请释放,会导致内存碎片。
    • 处理方式2:采用一个大内存,用户拆成多个块,自行管理。存在问题:增加代码的复杂度。
  2. 场景2:在RTOS环境中,邮箱的传输过程,邮箱指传输报文的起始地址,它不像消息队列一样内部实现会做数据拷贝。所以需要防护传输报文的内存。

    • 处理方式1:在邮箱发送线程申请报文内存,在邮箱接收线程释放报文内存。存在问题:反复的内存申请释放,会导致内存碎片。
    • 处理方式2:采用一个大内存,用户拆成多个块,自行管理,在邮箱发送线程获取内存块,在邮箱接收线程释放内存块。存在问题:增加代码的复杂度。
    • 处理方式3:如果你使用的系统是RT-RThread,可以利用RT-Thread的内核对象--内存池,大部分系统没有提供该机制。

RMP软件设计

RMP初始化框图

1.png

RMP初始化软件设计说明
  1. RMP的设计是将一个大的内存块平均划分为多个大小相同的内存块。
  2. 通过下一个内存的起始4个字节保存上一个内存块的地址起始地址。
  3. 最后一个内存块的起始地址,通过一个free_list保存。这样就可以形成一个链式存储。
  4. 这种方式可以通过free_list快速的查找到任意的内存块。
RMP内存申请框图

2.png

RMP内存申请软件设计说明
  1. 当内存块申请的时候,通过block4的前4个字节找到block3的起始地址。
  2. free_list保存block3的起始地址。
  3. 返回block3的起始地址。
  4. 这时内存池将减少一内存块。
  5. 以此类推。
RMP内存释放框图

3.png

RMP内存释放软件设计说明
  1. 当block2释放的时候,block2的前四个字节保存free_list指向的地址。
  2. free_list保存block2的起始地址。
  3. 这是内存池将增加一个内存块。
  4. 以此类推。
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接口说明

接口 说明
rmp_create 动态创建内存池
rmp_delete 删除动态创建的内存池
rmp_init 内存池初始化
rmp_deinit 内存池去初始化
rmp_alloc 阻塞方式从内存池申请内存块
rmp_try_alloc 非阻塞方式从内存池申请内存块
rmp_free 释放内存块
rmp_available 内存池剩余内存块个数
  • 动态创建内存池
    • 根据单个内存块的大小和内存块个数,申请内存池。
    • 内存池的空间通过malloc方式申请
mp_t *rmp_create(uint32_t size, uint32_t count);
参数 描述
size --
count --
返回 ——
mp 创建成功,返回内存池句柄
NULL 创建失败
  • 删除动态创建的内存池
    • 内存池的空间通过free方式释放。
void rmp_delete(rmp_t *mp);
参数 描述
mp 内存池句柄
返回 ——
-
  • 内存池初始化
    • 内存池的空间由用户提供。
    • 告知内存池单个内存块的大小和内存块个数
void rmp_init(rmp_t *mp, void *mem, uint32_t size, uint32_t count);
参数 描述
mp 内存池句柄
mem 指向内存池的指针,由用户提供
size 单个内存块的大小
count 内存块个数
返回 ——
-
  • 内存池去初始化
void rmp_deinit(rmp_t *mp);
参数 描述
mp 内存池句柄
返回 ——
-
  • 阻塞方式从内存池申请内存块
    • 在RTOS环境,它是阻塞方式从内存池申请内存块
    • 在非RTOS环境,它是非阻塞方式从内存池申请内存块
void *rmp_alloc(rmp_t *mp);
参数 描述
mp 内存池句柄
返回 ——
mem 申请到内存块
NULL 申请不到内存块
  • 非阻塞方式从内存池申请内存块
    • 在RTOS环境,它是非阻塞方式从内存池申请内存块
    • 在非RTOS环境,它和rmp_alloc功能一样。它是非阻塞方式从内存池申请内存块。
void *rmp_try_alloc(rmp_t *mp);
参数 描述
mp 内存池句柄
返回 ——
mem 申请到内存块
NULL 申请不到内存块
  • 释放内存块
    • 在RTOS环境,它是阻塞方式从内存池申请内存块
    • 在非RTOS环境,它是非阻塞方式从内存池申请内存块
void rmp_free(rmp_t *mp, void *ptr);
参数 描述
mp 内存池句柄
ptr 需要释放的内存块
返回 ——
-
  • 内存池中可用内存块剩余个数
uint32_t rmp_available(rmp_t *mp);
参数 描述
mp 内存池句柄
返回 ——
num 内存块剩余个数

RMP验证

实验说明:分别通过静态和动态定义一个内存池,内存池大小为(16 * 4),每个内存块为16,一共4块。连续申请5块,再释放第4块,再申请一块。

  1. 静态内存池方式:
#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);
  1. 动态内存池方式:
#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);
  1. 实验运行结果:成功申请4次,失败1次,然后释放了1次,又可以申请1次。

5.png

总结:

  1. rmp是一个非常高效且安全的内存池组件。
  2. rmp可以减少对malloc和free的使用,避免内存碎片的产生。
  3. 在设计rmp的同时,也发现RT-Thread内核自带的内存池是很浪费空间并且时间上不是最佳的,我也给RT-Thread社区提了一个对应的issue。

4.png

使用特权

评论回复

相关帖子

发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

1

主题

2

帖子

0

粉丝