内存池简介

之前讲的动态内存堆可以分配任意大小的内存块,非常灵活和方便。但其存在明显的缺点:

  1. 分配效率不高,在每次分配时都要进行空闲内存块查找;
  2. 容易产生碎片。

为了提高内存分配效率,并且避免内存碎片,RT-Thread 提供了另外一种内存管理方法:内存池(Memory Pool)

内存池是一种内存分配方式,用于分配大量大小相同的小内存块。使用内存池可以极大地加快内存分配与释放的速度,并且能尽量避免内存碎片化。

RT-Thread 的内存池支持线程挂起功能当内存池中无空闲内存块时,申请线程会被挂起,直到内存池中有新的可用内存块,再将挂起的线程唤醒。基于这个特点内存池非常适合需要通过内存资源进行同步的场景

内存池工作机制

内存池在创建时首先从系统中获取一大块内存(静态或动态),然后分成相同大小的多个小内存块,这些小内存块通过链表连接起来(此链表也称为空闲链表)。

线程每次申请分配内存块的时候,系统从空闲链表中取出链头上第一个内存块,提供给申请者。
RT-Thread 内核学习 >> (十六)内存池的使用

内存池控制块

在RT-Thread 中,内存池控制块是操作系统用于管理内存池的一个数据结构。

struct rt_mempool
{
    struct rt_object parent;                            /**< inherit from rt_object */

    void            *start_address;                     /**< memory pool start */
    rt_size_t        size;                              /**< size of memory pool */

    rt_size_t        block_size;                        /**< size of memory blocks */
    rt_uint8_t      *block_list;                        /**< memory blocks list */

    rt_size_t        block_total_count;                 /**< numbers of memory block */
    rt_size_t        block_free_count;                  /**< numbers of free memory block */

    rt_list_t        suspend_thread;                    /**< threads pended on this resource */
    rt_size_t        suspend_thread_count;              /**< numbers of thread pended on this resource */
};
typedef struct rt_mempool *rt_mp_t;
成员 说明
parent 继承自系统rt_object 对象
start_address 内存池起始地址
size 内存池大小
block_size 内存块大小
block_list 内存块列表
block_total_count 内存块数量
block_free_count 空闲内存块数量
suspend_thread 挂起的线程的列表
suspend_thread_count 挂起的线程的数量

定义静态内存池

struct rt_mempool static_mp;

定义动态内存池

(指针型定义)

rt_mp_t dynamic_mp;

内存池的操作

初始化与脱离

rt_err_t rt_mp_init(struct rt_mempool *mp,
                    const char        *name,
                    void              *start,
                    rt_size_t          size,
                    rt_size_t          block_size);
                    
rt_err_t rt_mp_detach(struct rt_mempool *mp);

rt_mp_init

参数 说明
mp 内存池控制块地址
name 内存池名称
start 系统中的一个地址
size 内存池大小
block_size 内存块大小

block_size 和消息队列中msg_size 一样,都需要为系统设置的对齐方式的整数倍

内存块计算:内存块数量 = 内存池大小 / [内存块大小(符合对齐方式) + 32位指针]


通过rt_mp_initrt_mp_detach 可以将内存池添加进系统管理器中或将其从系统管理器中脱离。

创建与删除

rt_mp_t rt_mp_create(const char *name,
                     rt_size_t   block_count,
                     rt_size_t   block_size);
                     
rt_err_t rt_mp_delete(rt_mp_t mp);

rt_mp_create

若系统当前有可用的内存,即可创建动态内存池。

参数 说明
name 内存池名称
block_count 内存块数量
block_size 内存块大小

通过rt_mp_creatert_mp_delete 可以将内存池添加进系统管理器中或将其从系统管理器中移除。

申请内存块

void *rt_mp_alloc(rt_mp_t mp, rt_int32_t time);
参数 说明
mp 指定需要申请内存块的内存池
time 等待的时间

释放内存块

void rt_mp_free(void *block);
参数 说明
block 要释放的内存块的地址

示例代码

static rt_uint8_t *ptr[50];
static rt_uint8_t mempool[4096];
static struct rt_mempool mp;

#define THREAD_PRIORITY      25
#define THREAD_STACK_SIZE    512
#define THREAD_TIMESLICE     5

/* 指向线程控制块的指针 */
static rt_thread_t tid1 = RT_NULL;
static rt_thread_t tid2 = RT_NULL;

/* 线程1入口 */
static void thread1_mp_alloc(void *parameter)
{
    int i;
    for (i = 0 ; i < 50 ; i++)
    {
        if (ptr[i] == RT_NULL)
        {
            /* 试图申请内存块50次,当申请不到内存块时,
               线程1挂起,转至线程2运行 */
            ptr[i] = rt_mp_alloc(&mp, RT_WAITING_FOREVER);
            if (ptr[i] != RT_NULL)
                rt_kprintf("allocate No.%d\n", i);
        }
    }
}

/* 线程2入口,线程2的优先级比线程1低,应该线程1先获得执行。*/
static void thread2_mp_release(void *parameter)
{
    int i;

    rt_kprintf("thread2 try to release block\n");
    for (i = 0; i < 50 ; i++)
    {
        /* 释放所有分配成功的内存块 */
        if (ptr[i] != RT_NULL)
        {
            rt_kprintf("release block %d\n", i);
            rt_mp_free(ptr[i]);
            ptr[i] = RT_NULL;
        }
    }
}

int mempool_sample(void)
{
    int i;
    for (i = 0; i < 50; i ++) ptr[i] = RT_NULL;

    /* 初始化内存池对象 */
    rt_mp_init(&mp, "mp1", &mempool[0], sizeof(mempool), 80); /* 4096 / (80+4) = 48 */

    /* 创建线程1:申请内存池 */
    tid1 = rt_thread_create("thread1", thread1_mp_alloc, RT_NULL,
                            THREAD_STACK_SIZE,
                            THREAD_PRIORITY, THREAD_TIMESLICE);
    if (tid1 != RT_NULL)
        rt_thread_startup(tid1);

    /* 创建线程2:释放内存池*/
    tid2 = rt_thread_create("thread2", thread2_mp_release, RT_NULL,
                            THREAD_STACK_SIZE,
                            THREAD_PRIORITY + 1, THREAD_TIMESLICE);
    if (tid2 != RT_NULL)
        rt_thread_startup(tid2);

    return 0;
}

/* 导出到 msh 命令列表中 */
MSH_CMD_EXPORT(mempool_sample, mempool sample);

实验现象

由于内存池中最多只有48 块内存块,故当线程1 申请48次后停止挂起;线程2 此时开始释放内存池,释放了一块后线程1 又开始取走一块,接着又挂起,线程2 又释放一块,线程1 又取走,现在就以及取完了50 块内存块,线程1 结束。线程2 接着又释放完48块,共计释放了50 块。

相关文章: