RT-Thread快速入门(五)之邮箱
前言在上一篇中我们学习了消息队列——它可以传递任意大小的数据非常灵活。但它的内部涉及链表操作和memcpy如果只是传递一些很小的数据比如状态码、计数值、指针地址用消息队列就有点杀鸡用牛刀了。RT-Thread 提供了另一种轻量级的线程间通信机制——邮箱Mailbox。一句话总结传递小数据用邮箱传递大数据用消息队列。目录前言一、邮箱的特性1.1 邮箱的本质1.2 核心特性1.3 邮箱 vs 消息队列二、邮箱操作 API2.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()选型口诀传数值用邮箱传结构体用消息队列。二、邮箱操作 API2.1 创建/初始化动态创建 —rt_mb_creatert_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_initrt_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_EFULLrt_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);参数value是rt_uint32_t类型即 32 位无符号整数可以传递整数值如状态码、计数值指针地址如(rt_uint32_t)ptr2.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()适用场景小数据数值/指针用邮箱大数据结构体用消息队列