RT-Thread快速入门(五)之邮箱
2026/7/12 18:44:12 网站建设 项目流程

前言

在上一篇中,我们学习了消息队列——它可以传递任意大小的数据,非常灵活。但它的内部涉及链表操作和memcpy,如果只是传递一些很小的数据(比如状态码、计数值、指针地址),用消息队列就有点"杀鸡用牛刀"了。

RT-Thread 提供了另一种轻量级的线程间通信机制——邮箱(Mailbox)

一句话总结:传递小数据用邮箱,传递大数据用消息队列。


目录

前言

一、邮箱的特性

1.1 邮箱的本质

1.2 核心特性

1.3 邮箱 vs 消息队列

二、邮箱操作 API

2.1 创建/初始化

2.2 删除/脱离

2.3 发送邮件

2.4 接收邮件

三、完整示例

四、邮箱的使用技巧

4.1 通过邮箱传递指针

4.2 邮箱满时的阻塞发送

五、邮箱 vs 消息队列 选型指南

六、总结


一、邮箱的特性

1.1 邮箱的本质

邮箱的底层实现是环形缓冲区(Ring Buffer),而非消息队列的链表结构:

写入端 读出端 ┌──────────┐ ┌────┬────┬────┬────┐ ┌──────────┐ │ 线程A │───→│ │ │ │ │───→│ 线程B │ ├──────────┤ │ ██ │ ██ │ ██ │ ██ │ ├──────────┤ │ 线程B │───→│ │ │ │ │───→│ ISR │ ├──────────┤ └────┴────┴────┴────┘ └──────────┘ │ ISR │───→ 环形缓冲区 └──────────┘ 每格 = 4 字节

1.2 核心特性

特性说明
邮件大小每封邮件固定4 字节(32 位系统上为一个rt_uint32_t
底层结构环形缓冲区(数组),比链表的消息队列更高效
存储能力可缓存一定数量的邮件
发送内容任意 32 位数据,可以是整数值,也可以是指针
阻塞访问空时读可阻塞,满时写可阻塞
超时机制支持超时等待

1.3 邮箱 vs 消息队列

对比项邮箱(Mailbox)消息队列(Message Queue)
数据大小固定 4 字节任意大小
内部实现环形缓冲区(数组)链表+ memcpy
效率⭐⭐⭐⭐⭐更高⭐⭐⭐ 中等
内存开销小(每个邮件 4 字节)较大(消息块 + 链表指针)
适用场景传递小数值 / 指针传递大数据 / 结构体
紧急消息不支持rt_mb_urgent()支持rt_mq_urgent()

选型口诀:传数值用邮箱,传结构体用消息队列。


二、邮箱操作 API

2.1 创建/初始化

动态创建 —rt_mb_create

rt_mailbox_t rt_mb_create(const char* name, rt_size_t size, // 邮箱容量(邮件个数) rt_uint8_t flag); // RT_IPC_FLAG_FIFO / RT_IPC_FLAG_PRIO
  • 返回值:成功返回邮箱句柄,失败返回RT_NULL

  • 邮件缓冲区由函数内部动态分配

// 示例:创建可容纳 5 封邮件的邮箱 rt_mailbox_t mb = rt_mb_create("mbox", 5, RT_IPC_FLAG_FIFO); if (mb == RT_NULL) rt_kprintf("Failed to create mailbox!\n");

静态初始化 —rt_mb_init

rt_err_t rt_mb_init(rt_mailbox_t mb, // 邮箱句柄 const char* name, void* msgpool, // 预分配的缓冲区 rt_size_t size, // 邮箱容量(邮件个数) rt_uint8_t flag);
  • 返回值:成功返回RT_EOK

  • 注意:size是邮件个数,不是字节数。每个邮件占 4 字节,所以size = 缓冲区总字节 / 4

// 示例:静态初始化,缓冲区 20 字节 → 可容纳 5 封邮件 static rt_uint32_t mb_pool[5]; static struct rt_mailbox mb; ​ rt_mb_init(&mb, "mbt", &mb_pool[0], sizeof(mb_pool) / 4, RT_IPC_FLAG_FIFO);

2.2 删除/脱离

// 删除动态创建的邮箱 rt_err_t rt_mb_delete(rt_mailbox_t mb); ​ // 脱离静态初始化的邮箱 rt_err_t rt_mb_detach(rt_mailbox_t mb);

2.3 发送邮件

RT-Thread 提供三个发送函数:

函数说明阻塞?
rt_mb_send(mb, value)发送普通邮件(放到队尾)❌ 满时返回-RT_EFULL
rt_mb_send_wait(mb, value, timeout)等待发送(满时阻塞)✅ 满时阻塞等待
/* 普通发送 */ rt_err_t rt_mb_send(rt_mailbox_t mb, rt_uint32_t value); ​ /* 等待发送 */ rt_err_t rt_mb_send_wait(rt_mailbox_t mb, rt_uint32_t value, rt_int32_t timeout);

参数valuert_uint32_t类型(即 32 位无符号整数),可以传递:

  • 整数值:如状态码、计数值

  • 指针地址:如(rt_uint32_t)ptr

2.4 接收邮件

rt_err_t rt_mb_recv(rt_mailbox_t mb, rt_uint32_t *value, // 输出参数:接收到的邮件值 rt_int32_t timeout);
  • 返回值:RT_EOK成功 /-RT_ETIMEOUT超时 /-RT_ERROR错误


三、完整示例

本程序创建一个邮箱、1 个发送线程、1 个接收线程:

#include <rtthread.h> ​ /* 邮箱控制块和缓冲区(静态方式) */ static struct rt_mailbox mb; static rt_uint32_t mb_pool[10]; // 可容纳 10 封邮件 ​ /* 线程句柄 */ static rt_thread_t thread1 = RT_NULL; static rt_thread_t thread2 = RT_NULL; ​ #define THREAD_PRIORITY 15 #define THREAD_STACK_SIZE 512 #define THREAD_TIMESLICE 10 ​ /* 发送线程入口函数 */ static void thread1_entry(void *parameter) { rt_err_t result; int count = 0; ​ while(1) { count++; ​ /* 发送邮件:数值自增 */ result = rt_mb_send(&mb, (rt_ubase_t)count); if (result != RT_EOK) { rt_kprintf("rt_mb_send ERR\n"); } else { rt_kprintf("rt_mb_send:%d\n\r", count); } ​ rt_thread_mdelay(10); } } ​ /* 接收线程入口函数 */ static void thread2_entry(void *parameter) { int val; ​ while(1) { /* 接收邮件(永久等待) */ if (rt_mb_recv(&mb, (rt_ubase_t *)&val, RT_WAITING_FOREVER) == RT_EOK) { rt_kprintf("rt_mb_recv:%d\n\r", val); } ​ rt_thread_mdelay(5); } } ​ int main(void) { rt_err_t result; ​ /* 1. 初始化邮箱 * mb_pool[10] 共 40 字节,每个邮件 4 字节 * 容量 = 40 / 4 = 10 */ result = rt_mb_init(&mb, "mbt", &mb_pool[0], sizeof(mb_pool) / 4, // 邮箱容量 = 10 RT_IPC_FLAG_FIFO); // 先来先得 if (result != RT_EOK) { rt_kprintf("rt_mb_init ERR\n"); return -1; } ​ /* 2. 创建发送线程(优先级 14,较高) */ thread1 = rt_thread_create("thread1", thread1_entry, RT_NULL, THREAD_STACK_SIZE, THREAD_PRIORITY - 1, THREAD_TIMESLICE); if (thread1 != RT_NULL) rt_thread_startup(thread1); ​ /* 3. 创建接收线程(优先级 15) */ thread2 = rt_thread_create("thread2", thread2_entry, RT_NULL, THREAD_STACK_SIZE, THREAD_PRIORITY, THREAD_TIMESLICE); if (thread2 != RT_NULL) rt_thread_startup(thread2); ​ return 0; }

运行结果:

注意:发送线程优先级更高(14),所以它先运行写入邮箱;当邮箱满或调用mdelay(10)后挂起,接收线程(优先级 15)才有机会读出。


四、邮箱的使用技巧

4.1 通过邮箱传递指针

由于邮箱可以传递任意 32 位值,因此也可以用来传递指针:

/* 发送端 */ char *message = "Hello RT-Thread!"; rt_mb_send(&mb, (rt_uint32_t)message); // 把指针地址当作邮件发送 ​ /* 接收端 */ char *msg; rt_mb_recv(&mb, (rt_uint32_t *)&msg, RT_WAITING_FOREVER); rt_kprintf("Received: %s\n", msg);

⚠️ 确保指针指向的内存在线程生命周期内有效,不要在接收方拿到地址后原始内存已被释放。

4.2 邮箱满时的阻塞发送

使用rt_mb_send_wait()可以在邮箱满时阻塞等待:

/* 如果邮箱满了,等待 100 Tick(约 100ms),超时则返回 */ result = rt_mb_send_wait(&mb, 42, 100); if (result == RT_EOK) rt_kprintf("Send OK\n"); else if (result == -RT_ETIMEOUT) rt_kprintf("Send timeout, mailbox full\n");

五、邮箱 vs 消息队列 选型指南

场景推荐使用原因
传递数值(状态码、计数值)邮箱效率高,内存开销小
传递指针(大块数据)邮箱直接传 4 字节地址
传递结构体(中等大小)消息队列消息队列支持任意大小数据
传递不定长数据消息队列每个消息大小可不同
需要多个数据缓冲消息队列链表结构更灵活
实时性要求极高邮箱数组操作,无动态内存分配

六、总结

知识点要点
邮箱本质环形缓冲区(数组),每封邮件 4 字节
动态创建rt_mb_create()— 内存由内核分配
静态初始化rt_mb_init()— 需用户提供缓冲区,容量 = 总字节 / 4
发送邮件rt_mb_send()队尾 /rt_mb_urgent()队首紧急
等待发送rt_mb_send_wait()满时阻塞
接收邮件rt_mb_recv()— 支持超时等待
传递内容32 位整数值 或 指针地址
唤醒策略RT_IPC_FLAG_PRIO(按优先级)/RT_IPC_FLAG_FIFO(按等待时间)
删除/脱离动态用rt_mb_delete(),静态用rt_mb_detach()
适用场景小数据(数值/指针)用邮箱,大数据(结构体)用消息队列

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询