RTthread中的内存池理解

我们先理解一下单片机中的ROM,RAM,FLASH

ROM:只读闪存器,存放放电不丢失的数据。在早期叫掩膜ROM,一旦刻好,永远无法修改。最后发展成flash,可以用软件代码直接高频修改。写的代码与const只读数据存放在flash里。

RAM:单片机只要一断电,或者按下复位键(Reset),RAM 里的所有数据瞬间清零。存放:全局变量,局部变量,多线程时的栈空间,内存池。

stm32f103c8t6 : 64KB FLASH 20KB RAM

stm32f429igt6 1024kb flash 256kb ram

内存池:在RAM中划分空间。

内存池就是提前把一块大内存,均匀地切成大小完全相同的"标准单间",并统一管理起来。

你可以把它想象成一个**"智能储物柜"**:

  • 整个柜子的大小是你提前定好的(总内存)。

  • 里面每个小格子的尺寸是一模一样的(比如每个格子只能放 8 字节的东西)。

  • 你只能按"整格"来租用,不能租半个格子,也不能把两个格子打通。

在c语言中可以通过:malloc与free申请与释放内存,但是有致命的缺陷

**1:产生内存碎片,**在程序运行时,申请10,20,10字节的内存,然后释放掉中间的20字节内存,现在需要申请25字节的内存,但是空间被之前借出去的内存隔断了,这就产生了内存碎片。

内存池的做法是:每个块的大小一模一样,借出去一个,还回来一个,位置永远完美契合,绝对不会产生内存碎片!

2:分配时间不均匀: malloc 在底层找内存时,需要去遍历空闲空间,如果碎片多,它找的时间就长。比如在机械臂运转过程,需要高频获取位置与电流反馈数据,每次耗时不一样,就会导致机械臂抖动。

内存池的解法: 内存池借出和归还内存的时间是固定且极短的,它能保证系统的绝对"实时性"。

如何计算内存池的大小:

内存池中每一个空闲块的开头,需要带有空闲块的地址,占用4字节。

定义整个大内存要多少空间:总数组大小 = 想要的块数 × (单个块大小 + 4)

对函数的讲解:

复制代码
rt_err_t rt_mp_init(rt_mp_t mp,const char* name,void *start, rt_size_t size,rt_size_t block_size);

内存池的初始化操作

  • rt_mp_t mp (内存池控制块/句柄)

    • 指向 struct rt_mempool 类型的指针。

    • 里面记录了这块内存还有多少空闲块、空闲链表头在哪里等管理信息。

  • const char* name (内存池名称)

    • 一个字符串,例如 "mp1"
  • void *start (内存起始地址)

    • 指向一片连续物理内存空间的指针。通常是事先定义好的一个静大数组的数组名。

    • 这是你要拿来切割的**"整块地皮"的起点**。

  • rt_size_t size (内存总大小)

    • 是什么: 这片连续内存的总字节数。

    • 大白话: 这块地皮的总面积 。比如你定义了一个 1024 字节的数组,这里填的就是 1024。注意它必须和 start 指向的空间大小匹配。

  • rt_size_t block_size (单个内存块大小)

    • 每次分配出来的最小内存单元的字节数。

    • 你打算把总地皮切成多大的单间。比如设为 32,那你以后每次找管家要内存,他就会精准地给你一个 32 字节的单间。

      void* rt_mp_alloc(rt_mp_t mp, rt_int32_t time);

这个函数的作用是从指定的内存池中,申请提取一个空闲的内存块

  • rt_mp_t mp

    • 含义 :你要从哪个内存池里拿内存。传入你之前用 rt_mp_init 初始化好的那个内存池控制块的指针(比如 &memp)。
  • rt_int32_t time

    • 含义:超时时间。RT_WAITING_FOREVER(一直等),0(不等),100(时钟节拍)

      void rt_mp_free(void *block);

这个函数的作用是把用完的内存块,重新挂回内存池的空闲链表上

void *block

  • 你之前通过 rt_mp_alloc 借到的那个内存块的指针。当你把这个指针传进去,系统就会把这块内存标记为"可用",并重新挂回空闲链表。

例子:

初始化邮箱与发送接收线程,初始化120字节的内存池,每个块占sizeof(struct all_data) + 4=12字节,分成10个块。

发送线程:从内存池申请空闲内存块,将这块内存的首地址作为一封邮件,发送到邮箱队列中。

接收线程:一直等待,直到从邮箱中收到发送线程传来的那封邮件(即内存首地址)。读取打印: 顺着这个地址,直接去物理内存中读取温湿度数据并打印。释放内存: 数据读取完毕后,将这块内存调用 free 函数归还给内存池,形成资源循环。

cs 复制代码
#include <rtthread.h>

// 1. 定义全局的线程控制块指针和邮箱控制块指针
rt_thread_t send_data_t;
rt_thread_t rec_data_t;
rt_mailbox_t box_c;

// 2. 内存池相关定义
struct rt_mempool memp;
// 【修正1】使用 rt_uint8_t 定义,大小与 rt_mp_init 保持一致,并强制 4 字节对齐
ALIGN(RT_ALIGN_SIZE)
rt_uint8_t men_pool[120];

// 定义用于传递数据的结构体模板 (大小为 8 字节)
struct all_data
{
    int wendu; // 温度
    int shidu; // 湿度
};

// 数据写入函数
void all_data_write(struct all_data *data)
{
    data->shidu = 10;
    data->wendu = 20;
}

// 发送线程(生产者)
void send_data(void *param)
{
    while(1)
    {

        // 【修正2】去除了末尾多余的括号
        struct all_data *data = (struct all_data *)rt_mp_alloc(&memp, RT_WAITING_FOREVER);

        if (data != RT_NULL) // 加上判空是个好习惯
        {
            rt_kprintf("【内存监控】总房间: %d, 剩余空房: %d\n",
                   memp.block_total_count,
                   memp.block_free_count);
            // 写入数据
            all_data_write(data);

            // 发送邮件 (发送内存地址)
            rt_mb_send_wait(box_c, (rt_base_t)data, RT_WAITING_FOREVER);
        }

        // 延时 1000 毫秒
        rt_thread_mdelay(1000);
    }
}

// 接收线程(消费者)
void rec_data(void *param)
{
    while(1)
    {
        struct all_data *data;

        // 阻塞接收邮件
        if (rt_mb_recv(box_c, (rt_ubase_t *)&data, RT_WAITING_FOREVER) == RT_EOK)
        {
            // 读取并打印数据
            rt_kprintf("temp: %d, hum: %d\n", data->wendu, data->shidu);

            // 【关键】释放内存池的内存块
            rt_mp_free(data);
        }
    }
}

int main(void)
{
    // 1. 初始化内存池:总大小120,每个块 8 字节(正好容纳 all_data 结构体)
    rt_mp_init(&memp, "mem_c", men_pool, sizeof(men_pool), sizeof(struct all_data));

    // 2. 动态创建邮箱
    box_c = rt_mb_create("box_chen", 10, RT_IPC_FLAG_PRIO);

    // 3. 创建并启动发送线程
    send_data_t = rt_thread_create("send_data", send_data, RT_NULL, 1024, 10, 10);
    if (send_data_t != RT_NULL)
        rt_thread_startup(send_data_t);

    // 4. 创建并启动接收线程
    rec_data_t = rt_thread_create("rec_data", rec_data, RT_NULL, 1024, 10, 10);
    if (rec_data_t != RT_NULL)
        rt_thread_startup(rec_data_t);

    return 0;
}

从打印的结果清晰看出来,申请了一块内存,内存池中空闲的内存块数减一。

相关推荐
玛卡巴卡ldf2 小时前
【LeetCode 手撕算法】(矩阵)73-矩阵置零、54-螺旋矩阵(贪吃蛇)、48-旋转图像
java·数据结构·算法·leetcode·力扣
深藏功yu名2 小时前
Day25(高阶篇):RAG检索与重排序算法精研|从原理到参数调优,彻底攻克检索瓶颈
人工智能·算法·ai·自然语言处理·排序算法·agent
fobwebs2 小时前
wordpress 网站安装了Yoast SEO,并且做了内容的优化后,如果想重置Yoast SEO,并且删除所有的优化内容,应该如何操作?
数据库·yoast seo·重置yoast seo·清空yoast seo内容
司南-70492 小时前
claude初探- 国内镜像安装linux版claude
linux·运维·服务器·人工智能·后端
郝学胜-神的一滴2 小时前
深入解析:生成器在UserList中的应用与Python可迭代对象实现原理
开发语言·python·程序人生·算法
雪木木2 小时前
刷题:力扣热题100--滑动窗口(Day03)
算法·leetcode
为美好的生活献上中指2 小时前
*Java 沉淀重走长征路*之——《Linux 从入门到企业实战:一套六步法,带你打通运维与开发的任督二脉》
java·linux·运维·开发语言·阿里云·华为云·linux命令
lcj25112 小时前
蓝桥杯C++:数据结构(功能导向速查)
数据结构·c++·蓝桥杯
Yzzz-F2 小时前
Problem - 2157D - Codeforces
算法