提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
目录
前言
接下来我们将学习内核中的线程间的通信方式之一,邮箱,我们实现创建两个线程,线程1按键按下发送邮件,线程二接收邮件灭灯。
一、邮箱
1.1 工作机制
RT-Thread 操作系统的邮箱用于线程间通信,特点是开销比较低,效率较高。邮箱中的每一封邮件只能容纳固定的 4 字节内容(针对 32 位处理系统,指针的大小即为 4 个字节,所以一封邮件恰好能够容纳一个指针)。典型的邮箱也称作交换消息,如下图所示,线程或中断服务例程把一封 4 字节长度的邮件发送到邮箱中,而一个或多个线程可以从邮箱中接收这些邮件并进行处理。
1.2 控制块
邮箱控制块结构的详细定义请见以下代码:
struct rt_mailbox
{
struct rt_ipc_object parent;
rt_uint32_t* msg_pool; /* 邮箱缓冲区的开始地址 */
rt_uint16_t size; /* 邮箱缓冲区的大小 */
rt_uint16_t entry; /* 邮箱中邮件的数目 */
rt_uint16_t in_offset, out_offset; /* 邮箱缓冲的进出指针 */
rt_list_t suspend_sender_thread; /* 发送线程的挂起等待队列 */
};
typedef struct rt_mailbox* rt_mailbox_t;
1.3 相关API
邮箱的相关接口如下图所示,对一个邮箱的操作包含:创建 / 初始化邮箱、发送邮件、接收邮件、删除 / 脱离邮箱。
1.3.1 创建和删除邮箱
rt_mailbox_t rt_mb_create(const char *name, rt_size_t size, rt_uint8_t flag)
创建邮箱
调用该函数创建邮箱对象时会先从对象管理器中分配一个邮箱对象,然后给邮箱动态分配一块内存空间用来存放邮件,这块内存的大小等于邮件大小(4 字节)与邮箱容量的乘积,接着初始化接收邮件数目和发送邮件在邮箱中的偏移量。
参数
name 邮箱的名称
size 邮箱容量
flag 邮箱标志,它可以取值:RT_IPC_FLAG_FIFO 或 RT_IPC_FLAG_PRIO
返回
返回创建的邮箱对象,创建失败时返回 RT_NULL
rt_err_t rt_mb_delete(rt_mailbox_t mb)
删除邮箱
当用rt_mb_create()创建的邮箱不再被使用时,应该调用该函数删除它来释放相应的系统资源,一旦操作完成,邮箱将被永久性的删除。删除邮箱时,如果有线程被挂起在该邮箱对象上,内核先唤醒挂起在该邮箱上的所有线程(线程返回值是- RT_ERROR),然后再释放邮箱使用的 内存,最后删除邮箱对象。
参数
mb 邮箱对象的句柄
返回
错误代码
1.3.2 初始化和脱离邮箱
rt_err_t rt_mb_init (rt_mailbox_t mb, const char *name, void *msgpool, rt_size_t size, rt_uint8_t flag)
初始化邮箱
该函数将初始化邮箱并将其置于内核管理器的控制之下。
参数
mb 邮箱对象的句柄
name 邮箱的名称
msgpool 缓冲区指针
size 邮箱容量
flag 邮箱标志,它可以取数值:RT_IPC_FLAG_FIFO或RT_IPC_FLAG_PRIO
返回
RT_EOK
rt_err_t rt_mb_detach(rt_mailbox_t mb)
脱离邮箱
该函数将将把静态初始化的邮箱对象从内核对象管理器中删除。使用该函数接口后, 内核先唤醒所有挂在该邮箱上的线程(线程获得返回值是 - RT_ERROR ),然后将 该邮箱对象从内核对象管理器中删除。
参数
mb 邮箱对象的句柄
返回
RT_EOK
1.3.3 发送邮件
rt_err_t rt_mb_send(rt_mailbox_t mb, rt_uint32_t value)
发送邮件
线程或者中断服务程序可以通过调用该函数给其他线程发送邮件。发送的邮件可以是32位任意格式的数据,一个整型值或者一个指向缓冲区的指针。当邮箱中的邮件已经满时,发送邮件的线程或者中断程序会收到- RT_EFULL 的返回值。
参数
mb 邮箱对象的句柄
value 邮件内容
返回
RT_EOK 发送成功;-RT_EFULL 邮箱已经满了
1.3.4 等待方式发送邮件
rt_err_t rt_mb_send_wait(rt_mailbox_t mb, rt_uint32_t value, rt_int32_t timeout)
等待方式发送邮件
用户也可以通过调用该函数接口向指定邮箱发送邮件。它与rt_mb_send()的区别在于,如果邮箱已经满了,那么发送线程将根据设定的timeout参数等待邮箱中因为收取邮件而空出空间。如果设置的超时时间到达依然没有空出空间,这时发送线程将被唤醒返回错误码。
参数
mb 邮箱对象的句柄
value 邮件内容
timeout 超时时间
1.3.5 发送紧急邮件
rt_err_t rt_mb_urgent(rt_mailbox_t mb, rt_ubase_t value)
发送紧急邮件的过程与发送邮件几乎一样,唯一的不同是,当发送紧急邮件时,邮件被直接插队放入了邮件队首,这样,接收者就能够优先接收到紧急邮件,从而及时进行处理。
参数
mb 邮箱对象的句柄
value 邮件内容
返回
RT_EOK 发送成功
-RT_EFULL 邮箱已满
1.3.6 接收邮件
rt_err_t rt_mb_recv(rt_mailbox_t mb, rt_uint32_t *value, rt_int32_t timeout)
接收邮件
只有当接收者接收的邮箱中有邮件时,接收者才能立即取到邮件并返回 RT_EOK 的返回值, 否则接收线程会根据超时时间设置,或挂起在邮箱的等待线程队列上,或直接返回。
参数
mb 邮箱对象的句柄
value 邮件内容
timeout 超时时间
返回
RT_EOK 发送成功;-RT_ETIMEOUT 超时;-RT_ERROR 失败,返回错误号
二、代码实现
#include <rtthread.h>
#include <drv_common.h>
#include <rtdevice.h>
#include <rtdbg.h>
#define RED_LED GET_PIN(B,5)
#define GREEN_LED GET_PIN(E,5)
#define KEY_0 GET_PIN(E,4)
#define KEY_1 GET_PIN(E,3)
#define STACK_SIZE 512
#define PRIORITY 5
#define TICK 10
static rt_thread_t key_tid = RT_NULL;
static rt_mailbox_t mb = RT_NULL;
static rt_thread_t dect = RT_NULL;
char test[] = "press";
static void KEY_thread_entry(void *paramenter)
{
while(1)
{
if(rt_pin_read(KEY_0) == PIN_LOW)
{
rt_thread_mdelay(100);
if(rt_pin_read(KEY_0) == PIN_LOW)
{
rt_mb_send(mb, (rt_uint32_t)&test);
rt_kprintf("send sucessfully\n");
}
}
}
}
static void DECT_thread_entry(void *paramenter)
{
rt_err_t result = RT_NULL;
char *str = RT_NULL;
while(1)
{
result = rt_mb_recv(mb, (rt_uint32_t)&str, RT_WAITING_FOREVER);
if(result == RT_EOK)
{
rt_kprintf("recv sucessfully\n");
rt_kprintf("%s\n",str);
rt_pin_write(RED_LED, PIN_HIGH);
}
}
rt_mb_detach(mb);
}
int main(void)
{
rt_kprintf("start\n");
rt_pin_mode(RED_LED, PIN_MODE_OUTPUT);
rt_pin_write(RED_LED, PIN_LOW);
rt_pin_mode(KEY_0, PIN_MODE_INPUT_PULLUP);
mb = rt_mb_create("send", STACK_SIZE, RT_IPC_FLAG_PRIO);
if(mb != RT_NULL)
{
rt_kprintf("create mb successfully\n");
}
key_tid = rt_thread_create("key", KEY_thread_entry, RT_NULL, STACK_SIZE, PRIORITY, TICK);
dect = rt_thread_create("detection", DECT_thread_entry, RT_NULL, STACK_SIZE, PRIORITY + 1, TICK);
rt_thread_startup(key_tid);
rt_thread_startup(dect);
while (1)
{
rt_thread_mdelay(1000);
}
return RT_EOK;
}
三、总结
邮箱是一种简单的线程间消息传递方式,特点是开销比较低,效率较高。在 RT-Thread 操作系统的实现中能够一次传递一个 4 字节大小的邮件,并且邮箱具备一定的存储功能,能够缓存一定数量的邮件数 (邮件数由创建、初始化邮箱时指定的容量决定)。邮箱中一封邮件的最大长度是 4 字节,所以邮箱能够用于不超过 4 字节的消息传递。由于在 32 系统上 4 字节的内容恰好可以放置一个指针,因此当需要在线程间传递比较大的消息时,可以把指向一个缓冲区的指针作为邮件发送到邮箱中,即邮箱也可以传递指针。