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

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

相关推荐
数厘2 分钟前
2.20 sql基础聚合函数(COUNT、SUM、AVG、MAX、MIN)
数据库·sql·oracle
苏渡苇12 分钟前
Redis 版本演进、新特性与协议那些事儿
数据库·redis·缓存·开源协议·redis版本·redis新特性
鬼蛟17 分钟前
Nacos
数据库·redis·缓存
倔强的石头_23 分钟前
NFS网络文件系统下企业级数据库安装排障实战:环境变量失效与权限问题的深度解析
数据库
Linux运维技术栈24 分钟前
Cloudflare Argo Smart Routing全球加速:优化跨境回源链路,提升跨区域访问体验
大数据·前端·数据库
2402_8548083728 分钟前
CSS如何实现元素在容器内居中_利用margin-auto技巧
jvm·数据库·python
weixin_5806140030 分钟前
html标签怎么表示用户输入_kbd标签键盘快捷键标注【介绍】
jvm·数据库·python
楼田莉子30 分钟前
Linux网络:IP协议
linux·服务器·网络·c++·学习·tcp/ip
m0_7164300730 分钟前
如何监控集群 interconnect_ping与traceroute验证心跳通畅.txt
jvm·数据库·python
m0_6784854531 分钟前
如何通过 curl 调用 Go 标准库 RPC 服务(JSON-RPC 协议)
jvm·数据库·python