RT-Thread 邮箱(学习笔记)

本文参考自[野火EmbedFire]《RT-Thread内核实现与应用开发实战——基于STM32》,仅作为个人学习笔记。更详细的内容和步骤请查看原文(可到野火资料下载中心下载)

邮箱的基本概念

邮箱在操作系统中是一种常用的 IPC 通信方式,邮箱可以在线程与线程之间。中断与线程之间进行消息的传递,此外,邮箱相比于信号量与消息队列来说,其开销更低,效率更高,所以常用来做线程与线程、中断与线程间的通信。邮箱中的每一封邮件只能容纳固定的 4 字节内容(STM32是 32 位处理系统,一个指针的大小即为 4 个字节,所以一封邮件恰好能够容纳一个指针),当需要在线程间传递比较大的消息时,可以把指向一个缓冲区的指针作为邮件发送到邮箱中。

——原文

邮箱和消息队列很相似,只不过邮箱的大小只能是固定的4字节,但消息队列可以是任意长度。

邮箱的运作机制

邮箱运行原理如下图所示,下图中,线程或中断服务函数把4字节长度的邮件发送到邮箱中,线程等待队列里有若干个线程正在等待接收邮箱。

图片来源:RT-Thread官方中文手册

在这里插入图片描述

线程能够从邮箱里面读取邮件消息,当邮箱中的邮件是空时,根据用户自定义的阻塞时间决定是否挂起读取线程;当邮箱中有新邮件时,挂起的读取线程被唤醒,邮箱也是一种异步的通信方式。

通过邮箱,线程或中断服务函数可以将一个或多个邮件放入邮箱中。同样,一个或多个线程可以从邮箱中获得邮件消息。当有多个邮件发送到邮箱时,通常应将先进入邮箱的邮件先传给线程,也就是说,线程先得到的是最先进入邮箱的消息,即先进先出原则 (FIFO),同时 RT-Thread 中的邮箱支持优先级,也就是说在所有等待邮件的线程中优先级最高的会先获得邮件。

——原文

邮箱的应用场景

RT-Thread 的邮箱可以存放固定的邮件数量,这个数量在邮箱初始化或创建是确定。虽然一个邮件的大小只能为4个字节,但是可以用一指向缓存区(数组,每个元素4个字节)的指针作为邮件,这样就能发送较大的内容。

与其他通信方式比较,邮箱的特点是开销低,效率高。邮箱发送接收都支持阻塞等待,很适合线程间、中断与线程间通信,但 要注意不能在中断里使用阻塞的方式发送邮件。

由于邮箱邮件大小只能为4个字节,且32位系统的指针正好为4字节,所以邮箱很适合用来传递指针数据。

邮箱的使用技巧

在实际项目开发中,很多信息是结构体变量,可以使用邮箱传递吗?答案是没问题,只需用一个结构体指针。

//定义一个结构体指针(结构体成员自定义)
struct temp *pTemp;
//申请动态堆空间
pTemp = (struct temp*)rt_malloc(sizeof(struct temp));
//发送方在数据处理完成后,将这个消息指针发送给一个邮箱,邮箱名为mb
rt_mb_send(mb, (rt_uint32_t)pTemp);

由于上面的消息指针的内存空间是我们手动申请的,在线程接收到邮箱信息且处理完成后,还需要手动释放消息指针的空间

struct temp *pTemp;
if(rt_mb_recv(mb, (rt_uint32_t *)&pTemp) == RT_EOK)
{
    
    
	//数据处理完成后,释放消息指针内存空间
	rt_free(pTemp);
}

邮箱控制块

struct rt_mailbox
{
    
    
    struct rt_ipc_object parent;                        /**< 继承自ipc_object类 */

    rt_ubase_t          *msg_pool;                      /**< 邮箱缓冲区的开始地址 */

    rt_uint16_t          size;                          /**< 邮箱缓冲区的大小 */

    rt_uint16_t          entry;                         /**< 邮箱中邮件的数目 */
    rt_uint16_t          in_offset;                     /**< 邮箱缓冲的入口指针 */
    rt_uint16_t          out_offset;                    /**< 邮箱缓冲的出口指针 */

    rt_list_t            suspend_sender_thread;         /**< 发送线程的挂起等待队列 */
};

邮箱的函数接口

创建邮箱 rt_mb_create()

rt_mailbox_t rt_mb_create (const char* name, rt_size_t size, rt_uint8_t flag);

创建邮箱对象时会先创建一个邮箱对象控制块,然后给邮箱分配一块内存空间用来存放邮件,这块内存的大小等于邮件大小(4字节)与邮箱容量的乘积,接着初始化接收邮件和发送邮件在邮箱中的偏移量。使用 RT_IPC_FLAG_PRIO 优先级 flag 创建的 IPC 对象,在多个线程等待资源时,将由优先级高的线程优先获得资源。而使用 RT_IPC_FLAG_FIFO 先进先出 flag 创建的IPC 对象,在多个线程等待资源时,将按照先来先得的顺序获得资源。

参数 描述
name 邮箱的名称
size 邮箱容量
flag 邮箱的标志

删除邮箱 rt_mb_delete()

rt_err_t rt_mb_delete (rt_mailbox_t mb);

删除邮箱时,如果有线程被挂起在该邮箱对象上,内核先唤醒挂起在该邮箱上的所有线程(线程获得返回值是-RT_ERROR),然后再释放邮箱使用的内存,最后删除邮箱对象。

参数 描述
mb 邮箱对象的句柄

初始化邮箱 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_IPC_FLAG_PRIO 优先级 flag 创建的 IPC 对象,在多个线程等待资源时,将由优先级高的线程优先获得资源。而使用 RT_IPC_FLAG_FIFO 先进先出 flag 创建的IPC 对象,在多个线程等待资源时,将按照先来先得的顺序获得资源。

参数 描述
mb 邮箱对象的句柄
name 邮箱的名称
msgpool 缓冲区指针
size 邮箱容量
flag 邮箱的标志

邮箱接收 rt_mb_recv()

rt_err_t rt_mb_recv (rt_mailbox_t mb, rt_uint32_t* value, rt_int32_t timeout);

接收邮件时,接收者需指定接收邮件的邮箱句柄,并指定接收到的邮件存放位置以及最多能够等待的超时时间。如果接收时设定了超时,当指定的时间内依然未收到邮件时,将返回-RT_ETIMEOUT。

参数 描述
mb 邮箱对象的句柄
value 邮件内容
timeout 指定超时时间

邮箱发送 rt_mb_send()(非阻塞)

rt_err_t rt_mb_send (rt_mailbox_t mb, rt_uint32_t value);

发送的邮件可以是32位任意格式的数据,一个整型值或者一个指向缓冲区的指针。当邮箱中的邮件已经满时,发送邮件的线程或者中断程序会收到-RT_EFULL 的返回值。

参数 描述
mb 邮箱对象的句柄
value 邮件内容

邮箱发送 rt_mb_send_wait()(阻塞)

rt_err_t rt_mb_send_wait (rt_mailbox_t mb, rt_uint32_t value, rt_int32_t timeout);

rt_mb_send_wait与rt_mb_send的区别在于,如果邮箱已经满了,那么发送线程将根据设定的timeout参数等待邮箱中因为收取邮件而空出空间。如果设置的超时时间到达依然没有空出空间,这时发送线程将被唤醒返回错误码。

参数 描述
mb 邮箱对象的句柄
value 邮件内容
timeout 指定超时时间

邮箱实验

要想在RT-Thread中使用邮箱通信,需要先修改rtconfigh配置文件。可以使用下面两种方法:

  1. 取消注释,开启宏定义,

在这里插入图片描述

  1. 或者使用Configuration Wizard向导进行图形化配置,

在这里插入图片描述

此实验参考原文对应实验代码,只包含man()函数,外设初始化相关代码下面未给出。

本实验需要创建两个线程,分别是接收线程和发送线程,接收线程负责接收邮箱信息并打印到串口,发送线程负责扫描按键,检测到按键按下,则向邮箱发送对应的邮箱信息。

#include "board.h"
#include "rtthread.h"

// 定义线程控制块指针
static rt_thread_t recv_thread = RT_NULL;
static rt_thread_t send_thread = RT_NULL;

// 定义邮箱控制块
static rt_mailbox_t test_mb = RT_NULL;


// 实验需要用到的全局变量
char test_str1[] = "this is a mail test 1";
char test_str2[] = "this is a mail test 2";



/******************************************************************************
* @ 函数名  : recv_thread_entry
* @ 功  能  : 接收线程入口函数
* @ 参  数  : parameter 外部传入的参数
* @ 返回值  : 无
******************************************************************************/
static void recv_thread_entry(void *parameter)
{
    
    
	rt_err_t uwRet = RT_EOK;
	char *r_str;
	while(1)
	{
    
    
		// 等待接收邮箱信息
		uwRet = rt_mb_recv(test_mb,                          // 邮箱对象句柄
					(rt_uint32_t*)&r_str,                    // 接收邮箱信息
					RT_WAITING_FOREVER);                     // 超时一直等
		
		if(uwRet == RT_EOK)
		{
    
    
			rt_kprintf("邮箱接收到的内容:%s\n\n", r_str);
			LED0_TOGGLE;    // LED0 反转
		}
		else
		{
    
    
			rt_kprintf("邮箱接收错误!\n");
		}
	}	
}

/******************************************************************************
* @ 函数名  : send_thread_entry
* @ 功  能  : 发送线程入口函数
* @ 参  数  : parameter 外部传入的参数
* @ 返回值  : 无
******************************************************************************/
static void send_thread_entry(void *parameter)
{
    
    
	rt_err_t uwRet = RT_EOK;
	while(1)
	{
    
    
		
		// KEY0 被按下
		if(Key_Scan(KEY0_GPIO_PORT, KEY0_GPIO_PIN) == KEY_ON)
		{
    
    
			rt_kprintf("send:KEY0被单击\n");
			// 发送邮箱信息1
			uwRet = rt_mb_send(test_mb, (rt_uint32_t)&test_str1);
			if(uwRet == RT_EOK)
				rt_kprintf("邮箱信息发送成功!\n\n");
			else
				rt_kprintf("邮箱信息发送失败!\n\n");
		}
		
		// WK_UP 被按下
		if(Key_Scan(WK_UP_GPIO_PORT, WK_UP_GPIO_PIN) == KEY_ON)
		{
    
    
			rt_kprintf("send:WK_UP被单击\n");
			// 发送邮箱2
			uwRet = rt_mb_send(test_mb, (rt_uint32_t)&test_str2);
			if(uwRet == RT_EOK)
				rt_kprintf("邮箱信息发送成功!\n\n");
			else
				rt_kprintf("邮箱信息发送失败!\n\n");
		}
		rt_thread_delay(20);     //每20ms扫描一次
	}
}

int main(void)
{
    
    
	// 硬件初始化和RTT的初始化已经在component.c中的rtthread_startup()完成
	
	// 创建一个邮箱
	test_mb =                                     // 邮箱控制块指针
	rt_mb_create("test_mb",                       // 邮箱初始值
				    10,                           // 邮箱大小
	                RT_IPC_FLAG_FIFO);            // FIFO队列模式(先进先出)
	
	if(test_mb != RT_NULL)
		rt_kprintf("邮箱创建成功!\n");

	// 创建一个动态线程
	recv_thread =                                 // 线程控制块指针
	rt_thread_create("recv",                      // 线程名字
	                recv_thread_entry,            // 线程入口函数
	                RT_NULL,                      // 入口函数参数
	                255,                          // 线程栈大小
				    5,                            // 线程优先级
					10);                          // 线程时间片
	
	
	// 开启线程调度
	if(recv_thread != RT_NULL)
		rt_thread_startup(recv_thread);
	else
		return -1;
							
	// 创建一个动态线程
	send_thread =                                 // 线程控制块指针
	rt_thread_create("send",                      // 线程名字
	                send_thread_entry,            // 线程入口函数
	                RT_NULL,                      // 入口函数参数
	                255,                          // 线程栈大小
				    5,                            // 线程优先级
					10);                          // 线程时间片
	// 开启线程调度
	if(send_thread != RT_NULL)
		rt_thread_startup(send_thread);
	else
		return -1;
}

实验现象

按下KEY0时,send线程向recv线程发送邮箱信息1,按下WK_UP时,send线程向recv线程发送邮箱信息2;recv线程接收到邮箱信息时,打印接收到的内容:

在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/weixin_43772810/article/details/123981624