RT thread邮箱知识点讲解
RT-Thread邮箱机制在嵌入式系统中发挥重要作用:1)高效传递指针,避免大数据拷贝;2)自带缓冲队列,解决生产消费速度不匹配问题;3)自动线程调度,空闲时挂起接收线程,有数据时唤醒;4)实现中断与线程的安全通信。通过温湿度数据传输示例,展示了使用malloc动态分配内存避免数据覆盖,以及指针传递的实现方法。关键点包括:动态内存管理确保数据独立性,类型转换实现指针传递,以及必须的内存释放操作。该
底层物理结构:固定大小的 32 位数组
在系统内核中,一个邮箱对象在物理内存里表现为一个固定长度的环形缓冲区。 最核心的物理限制是:这个缓冲区里的每一个数据格(一封邮件),大小被严格写死为 4 个字节。
0.邮箱在rt thread中的作用:
1:传递指针:如果有1000份的数据需要传输,直接把对应数据的内存地址传给对方,让对方从地址上读取,方便快捷
2:缓冲作用:邮箱本身自带一个缓冲队列。发送线程生产得快,可以先把“信件”排队塞进邮箱,然后继续干活;接收线程处理得慢也。关系,只要邮箱没满,数据就不会丢。
3:自动休眠与唤醒: 当邮箱里没有信件时,调用 rt_mb_recv 的接收线程会自动挂起,把宝贵的 CPU 资源让给其他线程。一旦发送线程投递了新邮件,操作系统会瞬间把接收线程唤醒。
4:中断与线程之间的通信:在嵌入式底层开发中,硬件中断(ISR,比如按键按下、串口收到数据)要求“快进快出”,绝对不能在中断里执行耗时的操作或死等。可以瞬间把状态码放入邮箱中。其他线程去读取该数据。
主要函数:
1. 创建邮箱:rt_mb_create
-
作用: 在系统动态堆内存(Heap)中分配邮箱控制块,并根据你指定的大小,开辟一块连续的物理内存作为环形缓冲区。
-
rt_mailbox_t rt_mb_create(const char *name, rt_size_t size, rt_uint8_t flag); -
参数输入:
-
name:邮箱的名称(字符串,如"mail_A")。 -
size:邮箱的容量(能装多少封邮件)。如果你填10,系统会在底层分配10 * 4 = 40个字节的缓冲区。 -
flag:阻塞唤醒规则。-
RT_IPC_FLAG_FIFO:先进先出。多个线程等邮件时,谁先来排队,谁先拿到。 -
RT_IPC_FLAG_PRIO:优先级唤醒。优先级最高的线程优先拿到邮件。
-
-
-
返回值:
-
成功:返回邮箱控制块的句柄(
rt_mailbox_t类型的指针)。 -
失败:返回
RT_NULL(通常是因为动态内存不足)。
-
2. 发送邮件:rt_mb_send
-
作用: 将一个 32 位的数据(或地址)写入邮箱的环形缓冲区。如果此时有线程正在阻塞等待该邮箱,内核会立即触发调度将其唤醒。
-
rt_err_t rt_mb_send(rt_mailbox_t mb, rt_ubase_t value); -
参数输入:
-
mb:你要操作的邮箱句柄。 -
value:你要发送的数据。由于参数类型被强制限定为rt_ubase_t(32 位整数),如果你要发送的是指针,必须进行强制类型转换,例如(rt_ubase_t)data_ptr,否则编译器会报错。
-
-
返回值:
-
RT_EOK:发送成功。 -
-RT_EFULL:邮箱已经满了。
-
-
物理规则与使用场景: 该函数执行极快,不包含任何阻塞逻辑,因此它是完全中断安全的,被大量应用于硬件中断服务函数(ISR)中向线程传递底层数据。
3. 接收邮件:rt_mb_recv
-
作用: 从邮箱的环形缓冲区中读取最早进入的一封邮件(4 个字节)。如果邮箱为空,当前线程将被挂起进入阻塞状态。
-
rt_err_t rt_mb_recv(rt_mailbox_t mb, rt_ubase_t *value, rt_int32_t timeout); -
参数输入:
-
mb:邮箱句柄。 -
value:需要提供一个存放数据的内存物理地址。内核在底层会执行写指令,把邮箱里的 4 字节数据直接写进这个地址里。(这就是为什么如果你的局部变量本身是指针struct all_data *data,你还必须用&data对它再次取地址,把它所在的栈地址提供给内核)。 -
timeout:超时等待时间(单位:Tick)。-
RT_WAITING_FOREVER:永久死等,直到拿到邮件。 -
0:非阻塞读取。有邮件就读,没有立刻返回报错。 -
正整数:最多挂起等待该数值的节拍数。
-
-
-
返回值:
-
RT_EOK:成功接收到一封邮件,数据已经被写入你提供的value地址中。 -
-RT_ETIMEOUT:超时时间已到,但邮箱依然为空。 -
-RT_ERROR:发生其他内核级错误。
-
例子:假设有8个字节的数据要进行传输,邮箱的邮件最大是4字节,这时候我们可以传入数据的指针。其他接收线程可以读取地址去找到数据的值。
假设现在有温湿度数据:8个字节,通过把结构体的指针传入到邮箱中,其他线程通过rt_mb_recv读取。
#include <rtthread.h>
#include <rtdevice.h>
#include <board.h>
#include <stdlib.h> // 提供 malloc 和 free 函数的声明支持
// 定义全局的线程控制块指针和邮箱控制块指针
rt_thread_t send_data_t;
rt_thread_t rec_data_t;
rt_mailbox_t box_c;
// 定义用于传递数据的结构体模板
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)
{
// 1. 在堆(Heap)区动态申请一块独立内存,大小为一个结构体的尺寸。
// 使用 malloc 保证了每次循环都有新的内存地址,防止发送端覆写尚未被接收的数据。
struct all_data *data=(struct all_data *)malloc(sizeof(struct all_data));
// 2. 调用写入函数,将具体数值填入这块刚刚申请到的内存中。
all_data_write(data);
// 3. 发送邮件:将存放数据的内存首地址作为邮件内容发送。
// (rt_base_t) 强转是为了告诉编译器:把这个指针当成一个系统字长(32位系统下为4字节)的数值塞进邮箱。
rt_mb_send_wait(box_c, (rt_base_t)data,RT_WAITING_FOREVER);
// 4. 线程主动休眠 1000 毫秒(1秒),期间系统会将 CPU 交给其他有需要的线程执行。
rt_thread_mdelay(1000);
}
}
// 接收线程的入口函数(充当“消费者”角色)
void rec_data(void *param)
{
while(1)
{
// 1. 准备一个空白的本地指针变量,用于承接发送端传来的地址(门牌号)。
struct all_data *data;
// 2. 阻塞接收邮件:线程在这里原地等待,直到邮箱里有数据。
// (rt_ubase_t *)&data 将本地指针变量的地址交出去,让底层的接收函数直接把取到的地址数值写进 data 变量里。
rt_mb_recv(box_c,(rt_ubase_t *)&data, RT_WAITING_FOREVER);
// 3. 此时 data 已经成功指向了发送端申请的那块堆内存,通过指针箭头直接读取并打印数据。
rt_kprintf("temp: %d, hum: %d\n", data->wendu, data->shidu);
// 4. 【关键】使用完毕后,必须手动释放这块由 malloc 申请的堆内存,否则会导致系统内存泄漏!
free(data);
}
}
int main(void)
{
// 1. 动态创建一个名为 "box_chen" 的邮箱组件。
// 容量为 10(最多可缓存 10 个数据地址),等待该邮箱的线程按优先级大小来唤醒 (RT_IPC_FLAG_PRIO)。
box_c=rt_mb_create("box_chen", 10,RT_IPC_FLAG_PRIO);
// 2. 动态创建并启动“发送线程”。栈大小 4096 字节,优先级为 10。
send_data_t=rt_thread_create("send_data", send_data,RT_NULL, 4096, 10, 10);
rt_thread_startup(send_data_t);
// 3. 动态创建并启动“接收线程”。栈大小 4096 字节,优先级为 10。
rec_data_t=rt_thread_create("rec_data", rec_data,RT_NULL, 4096, 10, 10);
rt_thread_startup(rec_data_t);
return 0;
}
1:(struct all_data *)malloc(sizeof(struct all_data))如何理解
malloc 的工作是在堆区划出一块指定大小(比如 8 字节)的内存,然后把这块内存的首地址返回。但是申请这块内存是用来存整数、存字符,还是存你自定义的结构体 未知。因此,malloc 的返回值类型是 void *(无类型指针,也叫万能指针)。
(struct all_data *):malloc 返回的地址(4字节)是:温湿度结构体指针
注意:
a:如果使用全局变量来存储传感器数据:发送线程在第二次循环时更新了数据,但接收线程可能还没来得及处理第一次的数据。这会导致接收线程读到的是被覆盖后的数据。
b:没用while的情况下:如果使用局部变量,在函数结束时,内存被系统回收,这时候时候发过去的地址,读取时会出现乱码情况。
如果在while循环中,数据过快读取,也会造成重复覆盖数据。
c:使用 malloc 的优势: 每次循环都会在堆区申请一块全新且独立的内存。发送线程把数据写进新内存后,把指针扔进邮箱。这样发送线程就可以继续去申请下一块内存,绝对不会覆盖之前还未被接收的数据。
2: rt_mb_recv(box_c,(rt_ubase_t *)&data, RT_WAITING_FOREVER);如何理解
rt_mb_send_wait(, (rt_base_t)data,):发送过来的是温湿度结构体的指针,接收的时候要用一个变量去接收这个指针
struct all_data *data:定义结构体指针变量,在内存中分配地址存这个指针变量
rt_mb_recv(,,)中间的参数要传入的是地址
&data把存放data指针的地址取出来,这时候data等于发送过来的温湿度结构体的指针
(rt_ubase_t *):类型强制转换
更多推荐



所有评论(0)