我们先理解一下单片机中的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;
}

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