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

news2024/11/20 9:11:08

一、目的

嵌入式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来满足业务需要。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/156817.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

libdrm-2.4.112

编译 这个版本使用了meson进行构建、ninja进行编译 &#xff1b; 安装meson 编译 报错如上&#xff0c;查看meson.build文件&#xff0c; 我们的meson版本不正确&#xff0c; 查阅发现apt安装的版本过低&#xff1b; 安装meson sudo apt-get install python3 python3-pip …

LeetCode 111. 二叉树的最小深度

&#x1f308;&#x1f308;&#x1f604;&#x1f604; 欢迎来到茶色岛独家岛屿&#xff0c;本期将为大家揭晓LeetCode 111. 二叉树的最小深度&#xff0c;做好准备了么&#xff0c;那么开始吧。 &#x1f332;&#x1f332;&#x1f434;&#x1f434; 一、题目名称 二、…

程序的编译与链接——ARM可执行文件ELF

读书《嵌入式C语言自我修养》笔记 目录 读书《嵌入式C语言自我修养》笔记 ARM编译工具 使用readelf命令查看ELF Header 使用readelf命令查看ELF section header 程序编译 预处理器 编译器 &#xff08;1&#xff09;词法分析。 &#xff08;2&#xff09;语法分析。 …

班级人员可视化项目

页面分布文件分布index.html(搭建页面)index.css (修饰页面)fonts (放图标)images &#xff08;放图片&#xff09;jsjquery.js &#xff08;调整页面的js&#xff09;flexible.js (尺寸大小的js)echarts.min.js (charts图表的js)chinaMap…

论文投稿指南——中文核心期刊推荐(中国医学 2)

【前言】 &#x1f680; 想发论文怎么办&#xff1f;手把手教你论文如何投稿&#xff01;那么&#xff0c;首先要搞懂投稿目标——论文期刊 &#x1f384; 在期刊论文的分布中&#xff0c;存在一种普遍现象&#xff1a;即对于某一特定的学科或专业来说&#xff0c;少数期刊所含…

普元PAS部署springboot项目访问500

背景 项目需要从东方通部署迁移到普元PAS部署。记录一下遇到的问题 问题一 WebSocket启动异常: Error creating bean with name ‘serverEndpoint’ defined in class path resource 因为SpringBoot默认使用的容器是tomcat 对应的Websocket实现 PAS中直接使用ServerEndpoin…

虹科新品丨什么是光纤微动开关?(下)

HK-Micronor光纤微动开关 HK-MR386光纤微动开关和HK-MR380系列控制器搭配使用&#xff0c;提供了一种全新的创新型信号解决方案&#xff0c;可以长距离部署在困难和危险环境中。该开关传感器采用光中断的方法&#xff0c;通过双工62.5/125μm光纤链路&#xff0c;进行可靠的信号…

虹科方案|使用直接连接的阵列创建 SAN

一、引言通过将直连环境转换为共享存储&#xff0c;用户可以体验到物理主机之间或主机与存储之间更快的数据传输&#xff0c;从而使vMotion 实时迁移等 VMware 功能能 够在更短的时间内完成。二、关于VMWARE VSPHEREvSphere 平台是您的应用程序、云和业务 的最佳基础。 vSphere…

C语言_字符串旋转结果_C语言字符串旋转结果

上一篇博文讲了字符串左旋http://t.csdn.cn/8zbRf 这篇文章将讲解判断一个字符串是不是逆序过。 目录 一、问题描述 二、设计与分析 三、代码实现 一、问题描述 写一个函数&#xff0c;判断一个字符串是否为另外一个字符串旋转之后的字符串。 例如&#xff1a;给定s1 AAB…

【零基础】学python数据结构与算法笔记9

文章目录前言53.栈和队列的应用&#xff1a;迷宫问题54.使用栈解决迷宫问题55.使用队列进行迷宫问题&#xff1a;介绍56.使用队列进行迷宫问题&#xff1a;实现&#xff1a;总结前言 学习python数据结构与算法&#xff0c;学习常用的算法&#xff0c; b站学习链接 53.栈和队列…

【学习笔记之Linux】工具之vim配置

配置文件的位置&#xff1a; 在目录/etc/下面有一个名为vimrc的文件&#xff0c;这是系统中公共的vim配置文件&#xff0c;对所有用户都有效&#xff1b;   每个用户可以在自己的主目录下创建一个私有的配置文件&#xff0c;命名为“.vimrc”&#xff0c;这个配置只对自己有效…

如何利用MOS管实现双向电平转换

前面讲过的三极管和MOS管电平转换电路都是单向的&#xff0c;就是信号只能是从A输出到B输入。其实单个MOS管也能实现双向电平转换&#xff0c;即信号即能从A输出到B输入&#xff0c;也能从B输出到A输入。实际电路就是这个&#xff0c;包含一个MOS管和两个电阻&#xff0c;芯片1…

自定义启动器

&#x1f341;博客主页&#xff1a;&#x1f449;不会压弯的小飞侠 ✨欢迎关注&#xff1a;&#x1f449;点赞&#x1f44d;收藏⭐留言✒ ✨系列专栏&#xff1a;&#x1f449;SpringBoot专栏 &#x1f525;欢迎大佬指正&#xff0c;一起学习&#xff01;一起加油&#xff01; …

D. Meta-set(组合数学)

Problem - D - Codeforces 你喜欢纸牌棋盘游戏“集合”。每张牌包含k个特征&#xff0c;每个特征都等于集合{0,1,2}中的一个值。这副牌包含所有可能的纸牌变体&#xff0c;也就是说&#xff0c;总共有3k张不同的纸牌。 如果三张牌的某个特征与这三张牌相同或两两不同&#xff0…

[LeetCode算法->双指针]

在算法中&#xff0c;双指针的问题较为常见&#xff0c;应用也比较广泛&#xff0c;双指针问题能够降低时间复杂度和空间复杂度&#xff0c;有必要掌握这一内容。下面通过LeetCode的题目来说明双指针。1.给你一个按 非递减顺序 排序的整数数组 nums&#xff0c;返回 每个数字的…

Minecraft 1.19.2 Fabric模组开发 03.动画生物实体

我们本次尝试在1.19.2 Fabric中添加一个能够具有各种动画效果动作的生物实体。 效果展示效果展示效果展示 1.首先&#xff0c;为了实现这些动画效果&#xff0c;我们需要首先使用到一个模组:geckolib(下载地址) 找到项目的build.gradle文件&#xff0c;在repositories和depen…

vue实现网页端企业微信扫码登录功能(前端部分)

时至今日&#xff0c;企业微信在企业日常工作中的使用越来越频繁也越来越重要&#xff0c;不少企业已使用企业微信进行着日常的工作安排管理。在这种背景下&#xff0c;各类系统和企业微信对接的需求也不断增加&#xff0c;今天要说的就是一个比较常见的需求&#xff1a;在网页…

第08讲:Docker中的网络类型bridge和host

Docker 中的网络模式早先是 3 种&#xff0c;后来又加了 1 种&#xff0c;一共是 4 种。这里&#xff0c;我们只涉及、介绍使用其中 2 种网络模式&#xff1a;bridge 模式和 host 模式。 bridge 是驱动&#xff08; Driver &#xff09;类型为 bridge 的默认网络&#xff1b;h…

『 MySQL篇 』:库操作、数据类型

目录 目录 一、初识数据库 数据库 数据库管理系统 SQL语言 二、详解MySQL MySQL 存储引擎 三、简单的库操作 设置数据库的编码字符集 创建数据库 显示数据库 使用数据库 删除数据库 四、MySQL数据类型 数值类型 字符串类型 日期类型 一、初识数据库 学习 MyS…

SpringBoot整合ELK教程

SpringBoot整合ELK教程 1 基础概念 ELK 即 Elasticsearch、Logstash、Kibana&#xff0c;组合起来可以搭建线上日志系统&#xff0c;本文主要讲解使用 ELK 来收集测试框架产生的日志。 Elasticsearch&#xff1a;用于存储收集到的日志信息&#xff1b;Logstash&#xff1a;用于…