RT-Thread系列--内存池MEMPOOL源码分析

一、目的

嵌入式RTOS中最重要也是最容易被忽略的一个组件就是内存管理,像FreeRTOS单单内存管理组件就提供了heap_1/2/3/4/5这五种方案,每种方案都有其特点和应用场景。

一般情况下小系统所运行的芯片平台本身内存就很少,有些时候内存空间还不连续(分散在多个地址区间内)。在一些特定场景下为了保证内存分配的确定性,我们会选择使用内存池的分配方案,也就是固定块内存分配方案,对应于RT-Thread中就是MEMPOOL方案。

本篇我们就从源码层面给大家讲讲RT-Thread的MEMPOOL的实现原理。

内存池分配方案的特点:

  • 每个内存块大小固定,故内存利用率可能不高

  • 不存在内存碎片

  • 分配时间相对固定

  • 实现简单

  • 可以通过实现多个不同大小不同属性的MEMPOOL满足特定场景

二、介绍

RT-Thread的每个MEMPOOL都对应于一个struct rt_mempool数据结构,如下

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 */
};

各字段含义:

parent:每个mempool对象都是一个struct rt_object类型的对象(基类),通过parent字段可以将mempool统一管理;

start_address:内存池对应的内存首地址(对齐)

size:内存池对应的内存总大小(对齐)

block_size:每个内存块可分配的最大内存大小,注意此大小仅仅是外部看到的内存块大小,实际每个内存块的大小稍大(用于存放块信息)

block_list:指向空闲内存块链表中的第一个空闲块

block_total_count:记录此MEMPOOL总共包含的内存块

block_free_count:记录此MEMPOOL当前空闲的内存块个数

suspend_thread:记录由于内存块不足挂起的线程

下面我们来分析下MEMPOOL的初始化函数:

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_uint8_t *block_ptr;
    register rt_size_t offset;

    /* parameter check */
    RT_ASSERT(mp != RT_NULL);
    RT_ASSERT(name != RT_NULL);
    RT_ASSERT(start != RT_NULL);
    RT_ASSERT(size > 0 && block_size > 0);

    /* initialize object */
    rt_object_init(&(mp->parent), RT_Object_Class_MemPool, name);

    /* initialize memory pool */
    mp->start_address = start;
    mp->size = RT_ALIGN_DOWN(size, RT_ALIGN_SIZE);   //对齐

    /* align the block size */
    block_size = RT_ALIGN(block_size, RT_ALIGN_SIZE);    //对齐
    mp->block_size = block_size;

    /* align to align size byte */
    mp->block_total_count = mp->size / (mp->block_size + sizeof(rt_uint8_t *));  //注意此处多出的sizeof(rt_uint8_t *),下面会具体分析原因
    mp->block_free_count  = mp->block_total_count;

    /* initialize suspended thread list */
    rt_list_init(&(mp->suspend_thread));

    // 构建空闲内存块链表
    /* initialize free block list */
    block_ptr = (rt_uint8_t *)mp->start_address;
    for (offset = 0; offset < mp->block_total_count; offset ++)
    {
        *(rt_uint8_t **)(block_ptr + offset * (block_size + sizeof(rt_uint8_t *))) =
            (rt_uint8_t *)(block_ptr + (offset + 1) * (block_size + sizeof(rt_uint8_t *)));
    }

    *(rt_uint8_t **)(block_ptr + (offset - 1) * (block_size + sizeof(rt_uint8_t *))) =
        RT_NULL;

    mp->block_list = block_ptr;

    return RT_EOK;
}

为了方便讲解我这边整理了一张图

注意 struct rt_mempool_item是为了介绍方便虚拟出来的结构,实际上不存在。

如图,每个内存块A/B/C最开头都有一个uint8_t类型的指针,这个指针的作用就是将空闲内存块连接起来;在分配内存时存放已分配内存块所从属的MEMPOOL指针。

mp->block_total_count = mp->size / (mp->block_size + sizeof(rt_uint8_t *));

此行代码分母部分要多出sizeof(rt_uint8_t *)字节的原因就是为了存放此指针。

    block_ptr = (rt_uint8_t *)mp->start_address;
    for (offset = 0; offset < mp->block_total_count; offset ++)
    {
        *(rt_uint8_t **)(block_ptr + offset * (block_size + sizeof(rt_uint8_t *))) =
            (rt_uint8_t *)(block_ptr + (offset + 1) * (block_size + sizeof(rt_uint8_t *)));
    }

    *(rt_uint8_t **)(block_ptr + (offset - 1) * (block_size + sizeof(rt_uint8_t *))) =
        RT_NULL;

上面的代码行就是将空闲块通过指针的方式连接起来,最后一个空闲块头部指针设置为NULL。

分析完初始化代码,我们来看一下内存分配接口

void *rt_mp_alloc(rt_mp_t mp, rt_int32_t time)
{
    rt_uint8_t *block_ptr;
    register rt_base_t level;
    struct rt_thread *thread;
    rt_uint32_t before_sleep = 0;

    /* parameter check */
    RT_ASSERT(mp != RT_NULL);

    /* get current thread */
    thread = rt_thread_self();

    /* disable interrupt */
    level = rt_hw_interrupt_disable();

    while (mp->block_free_count == 0)
    {
        /* memory block is unavailable. */
        if (time == 0)
        {
            /* enable interrupt */
            rt_hw_interrupt_enable(level);

            rt_set_errno(-RT_ETIMEOUT);

            return RT_NULL;
        }

        RT_DEBUG_NOT_IN_INTERRUPT;

        thread->error = RT_EOK;

        /* need suspend thread */
        rt_thread_suspend(thread);
        rt_list_insert_after(&(mp->suspend_thread), &(thread->tlist));

        if (time > 0)
        {
            /* get the start tick of timer */
            before_sleep = rt_tick_get();

            /* init thread timer and start it */
            rt_timer_control(&(thread->thread_timer),
                             RT_TIMER_CTRL_SET_TIME,
                             &time);
            rt_timer_start(&(thread->thread_timer));
        }

        /* enable interrupt */
        rt_hw_interrupt_enable(level);

        /* do a schedule */
        rt_schedule();

        if (thread->error != RT_EOK)
            return RT_NULL;

        if (time > 0)
        {
            time -= rt_tick_get() - before_sleep;
            if (time < 0)
                time = 0;
        }
        /* disable interrupt */
        level = rt_hw_interrupt_disable();
    }

    /* memory block is available. decrease the free block counter */
    mp->block_free_count--;

    /* get block from block list */
    block_ptr = mp->block_list;
    RT_ASSERT(block_ptr != RT_NULL);

    /* Setup the next free node. */
    mp->block_list = *(rt_uint8_t **)block_ptr;

    /* point to memory pool */
    *(rt_uint8_t **)block_ptr = (rt_uint8_t *)mp;

    /* enable interrupt */
    rt_hw_interrupt_enable(level);

    RT_OBJECT_HOOK_CALL(rt_mp_alloc_hook,
                        (mp, (rt_uint8_t *)(block_ptr + sizeof(rt_uint8_t *))));

    return (rt_uint8_t *)(block_ptr + sizeof(rt_uint8_t *)); //返回给调用者的内存地址需要往后偏移sizeof(rt_uint8_t *)字节
}

代码中通过判断mp->block_free_count的值来检查是否有空闲块可用,如果没有空闲块可用就根据time的设置将当前线程进行挂起并设置超时时间;当有空闲块时,通过mp->block_list指针获取到一个空闲块,并且此块头部的指针区域赋值为mp,通过此指针这个分配出来的块就和mempool进行了绑定,这样只要找到内存块就能找到MEMPOOL对象。

下图是分配一个内存块后的内存布局如下

图中内存块A被分配出来后,block_list指向了下一个内存块B

注意此时内存块A的next_ptr指针指向mempool对象

最后我们再分析一下内存回收接口

void rt_mp_free(void *block)
{
    rt_uint8_t **block_ptr;
    struct rt_mempool *mp;
    struct rt_thread *thread;
    register rt_base_t level;

    /* parameter check */
    if (block == RT_NULL) return;

    /* get the control block of pool which the block belongs to */
    block_ptr = (rt_uint8_t **)((rt_uint8_t *)block - sizeof(rt_uint8_t *));  //block是偏移后的数值,此处需要找到内存块真正的首地址
    mp        = (struct rt_mempool *)*block_ptr;   //已分配内存块的头部存放的是mempool对象指针

    RT_OBJECT_HOOK_CALL(rt_mp_free_hook, (mp, block));

    /* disable interrupt */
    level = rt_hw_interrupt_disable();

    /* increase the free block count */
    mp->block_free_count ++;

    /* link the block into the block list */
    *block_ptr = mp->block_list;        // 将需要的释放的内存块放到空闲内存块的首部
    mp->block_list = (rt_uint8_t *)block_ptr;

    if (!rt_list_isempty(&(mp->suspend_thread)))
    {
        /* get the suspended thread */
        thread = rt_list_entry(mp->suspend_thread.next,
                               struct rt_thread,
                               tlist);

        /* set error */
        thread->error = RT_EOK;

        /* resume thread */
        rt_thread_resume(thread);

        /* enable interrupt */
        rt_hw_interrupt_enable(level);

        /* do a schedule */
        rt_schedule();

        return;
    }

    /* enable interrupt */
    rt_hw_interrupt_enable(level);
}
    /* get the control block of pool which the block belongs to */
    block_ptr = (rt_uint8_t **)((rt_uint8_t *)block - sizeof(rt_uint8_t *));
    mp        = (struct rt_mempool *)*block_ptr;

通过入参block的值找到内存块的首地址,此时内存块的最开始处存放的是当前mempool的指针值,通过这个指针值我们就可以找到当前这个内存块从属于的mempool。

    *block_ptr = mp->block_list;
    mp->block_list = (rt_uint8_t *)block_ptr;

将当前mempool的block_list的值赋值给当前内存块的开始位置,然后再将mp->block_list设置为当前块的地址值,也就是将当前free的内存块重新挂到mempool空闲链表上(mp->block_list总是存放空闲块链表的第一个内存块,如果为NULL则代表无空闲内存块)。

以上,我们就基本分析完了RT-Thread的mempool代码,总体来看实现上清晰明了。

在具体使用上,我们可以通过创建多个不同粒度的mempool来满足业务需要。

  • 0
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
RT-Thread 中,可以使用内存池mempool)来统一管理多个内存块。内存池是一种预先分配好一定数量的内存块,并把这些内存块组成一个链表进行统一管理的数据结构。当需要使用内存时,可以从内存池中申请内存块,使用完后再将内存块还回内存池中,以便下次重复使用,这样可以减少内存的频繁申请和释放,提高内存的利用效率。 下面是一个简单的示例代码,演示如何使用内存池来管理多个内存块: ```c #include <rtthread.h> /* 定义内存池控制块 */ static struct rt_mempool mempool; /* 定义内存块结构体 */ struct mem_block { rt_uint8_t data[64]; }; /* 定义内存块数组 */ static struct mem_block mem_array[16]; int main(void) { /* 初始化内存池 */ rt_mempool_init(&mempool, sizeof(struct mem_block), &mem_array[0], 16); /* 从内存池中申请内存块 */ struct mem_block *block1 = rt_mempool_alloc(&mempool); struct mem_block *block2 = rt_mempool_alloc(&mempool); /* 使用内存块 */ rt_memcpy(block1->data, "Hello", 5); rt_memcpy(block2->data, "World", 5); /* 打印内存块内容 */ rt_kprintf("block1: %s\n", block1->data); rt_kprintf("block2: %s\n", block2->data); /* 还回内存池 */ rt_mempool_free(&mempool, block1); rt_mempool_free(&mempool, block2); return 0; } ``` 在上面的代码中,我们定义了一个内存池控制块 `mempool` 和一个内存块结构体 `mem_block`,并使用一个大小为 16 的内存块数组 `mem_array` 来初始化内存池。然后我们从内存池中申请两个内存块 `block1` 和 `block2`,使用 `rt_memcpy()` 函数将数据写入内存块中,并打印出来。最后我们将内存块还回内存池中,释放内存块资源。 使用内存池来统一管理多个内存块,可以减少内存碎片的产生,提高内存的利用效率。同时,内存池也是 RT-Thread 中常用的内存管理方式之一,适用于各种嵌入式应用场景。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值