RT-Thread分析-静态内存池管理

目录

1 前言

2 静态内存池管理

3.1 内存池控制块

3.2 接口函数分析

1)rt_mp_init()

2)rt_mp_create()

3)rt_mp_alloc()

4)rt_mp_free()


1 前言

内存管理模块管理系统的内存资源,它是操作系统的核心模块之一。主要包括内存的初始化、分配以及释放。

RT-Thread 的内存管理模块的算法总体上可分为两类:动态内存堆管理和静态内存池管理。其中动态内存堆管理又根据具体设备内存大小划分为三种情况:

  • 针对小内存块的分配管理(小内存管理算法)
  • 针对大内存块的分配管理(slab 管理算法)
  • 针对多内存堆的分配情况(memheap 管理算法)

2 静态内存池管理

  • 内存池在创建时先向系统申请一大块内存,然后分成大小相等的多个小内存块,每个内存块前4字节空间做为单向链表指针,将所有空闲内存块链接起来(已分配出去的内存块的首部指针是指向内存池控制块,后续接口函数分析会详细说明)。
  • 物理内存中允许存在多个不同的内存池,当一个内存池对象被创建时,内存池对象就被分配给了一个内存池控制块
  • rt_mp_alloc(mp,time)每次从指定内存池申请一个内存块,如果申请线程允许挂起(即参数time非0)同时该内存池无可用内存块时,内核会将该申请线程挂起在 suspend_thread 链表上
  • rt_mp_free()会将内存块返回给内存池,并标记为空闲块,并唤醒suspend_thread 链表上挂起的线程

3.1 内存池控制块

struct rt_mempool
{
    struct rt_object parent;            //用于内核对象管理                            
    void            *start_address;     //内存池起始地址
    rt_size_t        size;              //内存池大小
    rt_size_t        block_size;        //每个内存块大小
    rt_uint8_t      *block_list;        //空闲内存块链表指针
    rt_size_t        block_total_count; //内存块的总数量
    rt_size_t        block_free_count;  //空闲内存块的数量
    rt_list_t        suspend_thread;    //因为内存块不可用而挂起的线程链表
};
typedef struct rt_mempool *rt_mp_t;

3.2 接口函数分析

        常规用法如下:

  • 调用 rt_mp_create() 或者rt_mp_init()函数,完成静态内存池创建
  • 调用 rt_mp_alloc() 函数,会从内存池中获取第一个空闲块,并返回该块的地址。
  • 调用 rt_mp_free() 函数,将该内存块返回给内存池,做为空闲内存块

1)rt_mp_init()

rt_err_t rt_mp_init(struct rt_mempool *mp,
                    const char        *name,
                    void              *start,
                    rt_size_t          size,
                    rt_size_t          block_size)
{
    //1 内核对象初始化
    rt_object_init(&(mp->parent), RT_Object_Class_MemPool, name);
    
    //2 初始化内存池控制块
    mp->start_address = start;
    mp->size = RT_ALIGN_DOWN(size, RT_ALIGN_SIZE);
    block_size = RT_ALIGN(block_size, RT_ALIGN_SIZE);
    mp->block_size = block_size;

    //2.1 每个内存块前会空出4字节空间,该空间做为指针用于将内存块链接起来
    mp->block_total_count = mp->size / (mp->block_size + sizeof(rt_uint8_t *));
    mp->block_free_count  = mp->block_total_count;

    //2.2 初始化挂起线程链表
    rt_list_init(&(mp->suspend_thread));

    //2.3 将空闲内存块链接起来
    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 *)));
    }

    //2.4 最后一个内存块的头部指针指向NULL
    *(rt_uint8_t **)(block_ptr + (offset - 1) * (block_size + sizeof(rt_uint8_t *))) =
        RT_NULL;
    mp->block_list = block_ptr;
}
  • 该接口需要外部先静态声明内存池控制块和内存池空间,再将内存空间和控制块地址传递进函数进行初始化
  • 内核会按参数将该内存划分为同样大小的内存块
  • 将所有空闲内存块按下图链接起来,方便管理

2)rt_mp_create()

rt_mp_t rt_mp_create(const char *name,
                     rt_size_t   block_count,
                     rt_size_t   block_size)
{
    //1 创建内存池控制块以及内核对象,会使用到动态内存堆的创建
    mp = (struct rt_mempool *)rt_object_allocate(RT_Object_Class_MemPool, name);
    
    //2 初始化内存池控制块
    block_size     = RT_ALIGN(block_size, RT_ALIGN_SIZE);
    mp->block_size = block_size;
    mp->size       = (block_size + sizeof(rt_uint8_t *)) * block_count;
    
    //3 动态申请内存池的内存空间
    mp->start_address = rt_malloc((block_size + sizeof(rt_uint8_t *)) *
                                  block_count);
       
    //4 后续流程和rt_mp_init类似                           
    ...
}
  • 该接口用户只需关注内存池的内存块大小和内存块数量,其余操作都交给系统完成
  • 会使用动态内存堆来进行内存申请,所以该接口不能在中断上下文调用
  • 其余操作和rt_mp_init一致

3)rt_mp_alloc()

void *rt_mp_alloc(rt_mp_t mp, rt_int32_t time)
{
    //1 获取申请内存的线程,用于后续线程挂起操作
    thread = rt_thread_self();
    
    //2 关闭中断
    level = rt_hw_interrupt_disable();
    
    //3 内存池无空闲内存块时
    while (mp->block_free_count == 0)
    {
        //3.1 返回申请失败
        if (time == 0)
        {
            rt_hw_interrupt_enable(level);
            rt_set_errno(-RT_ETIMEOUT);
            return RT_NULL;
        }
        
        //3.2 主动挂起线程,实际挂起是在后续任务调度操作中
        rt_thread_suspend(thread);
        
        //3.3 将线程插入内存池控制块挂起链表中
        rt_list_insert_after(&(mp->suspend_thread), &(thread->tlist));
        
        //3.4 设定线程定时器来操作该线程挂起时间
        if (time > 0)
        {
            before_sleep = rt_tick_get();
            rt_timer_control(&(thread->thread_timer),
                             RT_TIMER_CTRL_SET_TIME,
                             &time);
            rt_timer_start(&(thread->thread_timer));
        }
        
        //3.5 执行内核调度,此时该线程会挂起,等待唤醒
        rt_hw_interrupt_enable(level);
        rt_schedule();
        
        //3.6 线程唤醒后,判断自身挂起时长
        if (time > 0)
        {
            // 无非两种情况: 
            // 1)线程定时器超时后唤醒,此时time = 0,内核再次判断一下是否有空闲内存块,如果没有则执行3.1返回失败
            // 2) 别处主动唤醒该线程(比如释放内存块时会主动唤醒挂起线程),此时time > 0,内核再次判断一下是否有空闲内存块,如果没有则再次挂起
            time -= rt_tick_get() - before_sleep;
            if (time < 0)
                time = 0;
        }
        level = rt_hw_interrupt_disable();
    }
    
    //4 空闲内存块数量减1
    mp->block_free_count--;
    
    //5 获取空闲内存块链表指针
    block_ptr = mp->block_list;
    
    //5 将空闲内存块链表指针指向下一个内存块
    mp->block_list = *(rt_uint8_t **)block_ptr;
    
    //6 将申请出去的内存块首部指针指向内存控制块指针,目的是释放该内存块时能找到内存池控制块
    *(rt_uint8_t **)block_ptr = (rt_uint8_t *)mp;
    
    rt_hw_interrupt_enable(level);
    
    //7 跳过首部指针,返回实际内存块数据区
    return (rt_uint8_t *)(block_ptr + sizeof(rt_uint8_t *));
}
  • 该接口是向指定内存池申请一个内存块,参数time用于表示该线程未申请到内存块时可挂起的时间
  • 在指向过程中是关闭中断的
  • 下图是内存池第一次对外分配内存块的过程,block_list指向下一个内存块,被分配出去的内存块指向内存控制块(释放该内存块时能找到内存池控制块)。

4)rt_mp_free()

void rt_mp_free(void *block)
{
    //1 从内存块的头部指针获取到内存池控制块(rt_mp_alloc()已分析,分配出去的内存块首部指针会指向内存池控制块)
    block_ptr = (rt_uint8_t **)((rt_uint8_t *)block - sizeof(rt_uint8_t *));
    mp        = (struct rt_mempool *)*block_ptr;
    
    //2 关闭中断
    level = rt_hw_interrupt_disable();
    
    //3 空闲内存块数量加1
    mp->block_free_count ++;
    
    //4 将释放内存块的首部指针指向下一个内存块首部(见下图分析标记)
    *block_ptr = mp->block_list;
    
    //5 将block_list指向释放内存块首部(见下图分析标记)
    mp->block_list = (rt_uint8_t *)block_ptr;
    
    //6 如果控制块中挂起线程链表非空,则唤醒其中一个线程
    if (!rt_list_isempty(&(mp->suspend_thread)))
    {
        thread = rt_list_entry(mp->suspend_thread.next,
                               struct rt_thread,
                               tlist);
        thread->error = RT_EOK;
        rt_thread_resume(thread);
        rt_hw_interrupt_enable(level);
        rt_schedule();
        return;
    }
    
    rt_hw_interrupt_enable(level);
}
  • 释放指定内存块,内核释放内存块1的过程,其中标记4,5即代码中的步骤4,5

  • 如果释放内存块时,suspend_thread链表非空,说明有线程挂起在等待空闲内存块,此时唤醒之

 

  • 2
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
下载时请看下面说明,对写一个动态的内存池很有帮助。 这是一个用C++语言链表的方法实现的一个静态内存池代源码。原理就是先向系统申请一块大内存,然后把这一大块分隔成相等的很多小块,然后在这这些小块的首地址部份放一个结构体,结构体中有一个值是用来标明这一小块是否使用中。在这里存放你要放存的数据就是用结构体首地址加结构体自身长度就得到要放数据的首地址了.具体看代码的实现吧。我说一下动态内存池的写法。那是我给公司写的就不能上传了。结构体和静态内存池的这个差不多一样,只是增加了一个成员用来记录每一节点到大块内存的首地址在到本节点的一个尺寸长度值,做法也是先申请一块大内存。我先从释放说起吧,释放本节点时看自己的相邻节点是不是有释放掉的,如果有则合并掉他们成为一个块,如果碰到相邻的节点是另外的一个大块的话就不用合并了,原因他和自己所在的这一个大块内存上物理地址不是连续,这里一定要记住,释放过程算法怎么去写就看你的了。下面是分配写法要考虑的。在分配一小块内存给高层使用时,如果是分配在尾节点去分配的情况,那好办啊,尾节点如果不够分配了就直接从系统去申请一块大内存,节点连起来在分配,这里有可能会浪费掉一小块以结构体大小的一块内存,如果够分配就直接分配了。如果是在中间节点去分配,这里就要将释放时合并的如果大于现在要分配的就拆开来用,如果拆开剩余的那一部份只有结构体大小就不用在拆开了。这些都是要考虑的东西,优化加快速度就看你自己了.可能看时不些不明白,看静态内存的写法后你就明白了.有时我也要下载其他人共享的东西,所以就一分吧.哈哈~~~~

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值