RT-Thread快速入门-消息邮箱

首发,公众号【一起学嵌入式

前面几篇文章介绍了线程(任务)间的同步机制:信号量、互斥量、事件集。接下来我们学习线程(任务)之间的通信机制。

一般来说,RTOS 均会提供两种线程间通信的机制:消息邮箱和消息队列。RT-Thread 同样如此。

本篇文章介绍 RT-Thread 消息邮箱相关的内容。

邮箱的工作机制

1. 理解消息邮箱

邮箱是一种简单的线程间消息传递的方式,其特点是开销较低,效率较高。邮箱中的每一封邮件可以容纳固定大小的内容(针对 32 位处理器,可容纳 4 字节内容,所以一封邮件恰好可以容纳一个指针)。

邮箱的工作示意图如下,中断服务例程或者线程把一封 4 字节长度的邮件发送到邮箱中,一个或多个线程可以从邮箱中读取这些邮件并进行处理。

在这里插入图片描述

在中断服务例程中,只能用非阻塞的方式发送邮件。线程中可以设定发送超时时间,以阻塞的方式发送邮件。

当一个线程向邮箱发送邮件时,如果邮箱没满,将把邮件复制到邮箱中。如果邮箱已经满了,发送线程可以设置超时时间,选择等待挂起或直接返回 -RT_EFULL

接收邮件过程中,当邮箱中不存在邮件且超时时间不为 0 时,邮件收取过程将变成阻塞方式。 此时,只能由线程进行邮件的收取。

2. 邮箱控制块

RT-Thread 中管理邮箱的数据结构为邮箱控制块,有结构体 struct rt_mailbox 表示。另外,rt_mailbox_t 表示的是邮箱的句柄,即指向邮箱控制块的指针。 邮箱控制块结构体定义如下:

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;  /* 发送线程的挂起等待队列 */
};
typedef struct rt_mailbox *rt_mailbox_t;

rt_mailbox 对象从 rt_ipc_object 中派生,由 IPC 容器管理。结构体 rt_ipc_object 定义如下:

struct rt_object
{
    char       name[RT_NAME_MAX]; /* 内核对象名称 */
    rt_uint8_t type;              /* 内核对象类型 */
    rt_uint8_t flag;              /* 内核对象的参数 */

#ifdef RT_USING_MODULE
    void      *module_id;  /* 应用程序模块 ID */
#endif
    rt_list_t  list;       /* 内核对象管理链表 */
};

struct rt_ipc_object
{
    struct rt_object parent;          /* 继承自 rt_object */
    rt_list_t        suspend_thread;  /* 挂起的线程链表 */
};

结构体定义中,继承关系一目了然,不再赘述。

管理邮箱

RT-Thread 邮箱相关的操作函数如下所示,包含:创建 / 初始化邮箱、发送邮件、接收邮件、删除 / 脱离邮
箱。

在这里插入图片描述

本文只重点介绍几种常用的接口函数。

1. 创建邮箱

RT-Thread 创建一个邮箱有两种方式:动态创建、静态初始化。

动态创建一个邮箱的系统函数如下,调用这个函数创建一个邮箱时,内核会先从对象管理器中分配一个邮箱对象,然后创建一个邮箱控制块,接着对邮箱控制块进行初始化,包括邮箱缓冲区地址、邮件数目、发送邮件在邮箱中的偏移等。

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

在对邮箱控制块初始化期间,内核会动态分配一块内存空间用来存放消息邮件,这块内存的大小等于邮件大小(4 字节)与邮箱容量的乘积。

rt_mb_create()函数的参数,name 为邮箱名称;size 表示邮箱容量;flag 为邮箱的标志,取值为 RT_IPC_FLAG_FIFORT_IPC_FLAG_PRIO

邮箱创建成功,则返回邮箱控制块指针;创建失败,则返回 RT_NULL

静态方式创建邮箱需要两步:(1)定义一个邮箱控制块和一段存放邮件的缓冲区(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_mb_create() 相同。

这里的 size 参数指定的是邮箱的容量,即如果 msgpool 指向的缓冲区的字节数是 N,那么邮箱容量
应该是 N/4

函数rt_mb_init() 的返回值为 RT_EOK

创建邮箱的标志变量取值有两种:

  • RT_IPC_FLAG_FIFO,等待邮箱的线程按照先进先出的方式进行排列。
  • RT_IPC_FLAG_PRIO,等待邮箱的线程按照优先级的方式进行排列。

2. 发送邮件

RT-Thread 提供的发送邮件接口函数有两种:一种是无等待超时接口,一种是有等待超时。

线程或者中断服务程序可以通过邮箱给其他线程发送消息,发送邮件的函数接口如下,此函数没有等待超时参数。

rt_err_t rt_mb_send (rt_mailbox_t mb, rt_ubase_t value)

参数 mb 为邮箱对象的句柄;value 为邮件内容。

发送成功,函数返回 RT_EOK;发送失败,返回 -RT_EFULL,表示邮箱已经满了。

等待方式发送邮件的函数接口如下,这个函数有等待超时参数:

rt_err_t rt_mb_send_wait (rt_mailbox_t mb,
                          rt_ubase_t value,
                          rt_int32_t timeout)

此函数的参数 timeout 为发送等待超时时间,单位为系统时钟节拍。其他参数与 rt_mb_send() 相同。

如果邮箱已经满了,发送线程会根据设定的 timeout 参数等待邮箱中因为收取邮件而空出空间。若超时时间到达依然没有空出空间,则发送线程将会被唤醒并返回错误码。

返回 RT_EOK 表示发送成功;返回 -RT_ETIMEOUT 表示超时;返回 -RT_ERROR 表示发送失败。

邮件的内容可以是 32 位任意格式的数据,一个整型值或一个指向某个缓冲区的指针。可以根据自己的实际应用进行设定。

注意:在中断服务例程中发送邮件时,应该采用无等待延时的方式发送,直接使用 rt_mb_send() 或者等待超时设定为 0 的函数rt_mb_send_wait()

3.接收邮件

线程接收邮件的函数接口如下,线程接收邮件时,需要指定接收邮件的邮箱句柄、邮件存放位置以及等待的超时时间。

rt_err_t rt_mb_recv (rt_mailbox_t mb, rt_ubase_t *value, rt_int32_t timeout)

参数 mb 为邮箱的句柄;value 为邮箱消息存储地址;timeout 为等待超时时间。

接收成功,则返回 RT_EOK;接收超时,则返回 -RT_ETIMEOUT;接收失败,返回 -RT_ERROR

只有当邮箱中有邮件时,接收者才能立即取到邮件并返回 RT_EOK;否则接收线程会根据设定的超时时间,挂起在等待线程队列或者立即返回(超时时间设定为 0)。

实战演练

举例来说明邮箱操作函数的用法,代码如下。动态创建两个线程,一个线程往邮箱中发送邮件,一个线程从邮箱中收取邮件。

#include <rtthread.h>

#define THREAD_PRIORITY 8
#define THREAD_TIMESLICE 5

/* 邮 箱 控 制 块 */
rt_mailbox_t mb_handle;

static char mb_str1[] = "I'm a mail!";
static char mb_str2[] = "this is another mail!";
static char mb_str3[] = "over";

/* 线程 1 入口 */
static void thread1_entry(void *parameter)
{
	char *str;

	while (1)
	{
		rt_kprintf("thread1: try to recv a mail\n");
		
		/* 从邮箱中收取邮件 */
		if (rt_mb_recv(mb_handle, (rt_ubase_t *)&str, RT_WAITING_FOREVER) == RT_EOK)
		{
			rt_kprintf("thread1: get a mail from mailbox, the content:%s\n", str);
			if (str == mb_str3)
			{
				break;
			}
			/* 延时 100ms */
			rt_thread_mdelay(100);
		}	
	}
}

/* 线程 2 入口 */
static void thread2_entry(void *parameter)
{
	rt_uint8_t count = 0;
	
	while (count < 10)
	{
		count ++;
		if (count & 0x1)
		{
			/* 发送 mb_str1 地址到邮箱中 */
			rt_mb_send(mb_handle, (rt_uint32_t)&mb_str1);
		}
		else
		{
			/* 发送 mb_str2 地址到邮箱中 */
			rt_mb_send(mb_handle, (rt_uint32_t)&mb_str2);
		}
		/* 延 时 200ms */
		rt_thread_mdelay(200);
	}
	/* 发送邮件告诉线程 1, 线程 2 已经运行结束 */
	rt_mb_send(mb_handle, (rt_uint32_t)&mb_str3);
}

int main()
{
	/* 线程控制块指针 */
	rt_thread_t thread1 = RT_NULL;
	rt_thread_t thread2 = RT_NULL;	

	/* 创建一个邮箱 */
	mb_handle = rt_mb_create("mt", 32, RT_IPC_FLAG_FIFO);
	if (mb_handle == RT_NULL)
	{
		rt_kprintf("create mailbox failed.\n");
		return -1;
	}

	/* 动态创建线程1 */
	thread1 = rt_thread_create("thread1", thread1_entry, RT_NULL,
					1024, THREAD_PRIORITY - 1, THREAD_TIMESLICE);
	
	if(thread1 != RT_NULL)
	{
		/* 启动线程 */
		rt_thread_startup(thread1);
	}

	/* 动态创建线程2 */
	thread2 = rt_thread_create("thread2", thread2_entry, RT_NULL,
					1024, THREAD_PRIORITY, THREAD_TIMESLICE);
	if(thread2 != RT_NULL)
	{
		/* 启动线程 */
		rt_thread_startup(thread2);
	}	
}

编译,运行结果如下:

在这里插入图片描述

该例程中,线程 2 发送邮件,共发送了 11 次;线程 1 接收邮件,接收到了 11 封邮件,并将邮件内容打印出来,并根据判断结束运行。

其他操作函数

对于 RT-Thread 邮箱操作来说,还有删除邮箱的函数没有介绍。可以简单了解一下。

1. 删除动态创建的邮箱

删除由 rt_mb_create() 函数创建的邮箱,可以调用如下函数:

rt_err_t rt_mb_delete (rt_mailbox_t mb)

调用此函数,可以释放邮箱控制块占用的内存资源以及邮箱缓冲区占用的内存。在删除一个邮箱对象时,应该确保该邮箱不再被使用。

在删除前会唤醒所有挂起在该邮箱上的线程,然后释放邮箱对象占用的内存块。

2. 脱离静态创建的邮箱

删除 rt_mb_init() 初始化的邮箱,可以用如下函数:

rt_err_t rt_mb_detach(rt_mailbox_t mb)

调用此函数时,首先会唤醒所有挂起在该邮箱等待队列上的线程,然后将该邮箱从内核对象管理器中脱离。

OK,今天先到这,下次继续。加油~


公众号【一起学嵌入式】,精彩首先送达

  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
RT-Thread诞生于2006年,是一款以开源、中立、社区化发展起来的物联网操作系统。 RT-Thread主要采用 C 语言编写,浅显易懂,且具有方便移植的特性(可快速移植到多种主流 MCU 及模组芯片上)。RT-Thread把面向对象的设计方法应用到实时系统设计中,使得代码风格优雅、架构清晰、系统模块化并且可裁剪性非常好。 RT-Thread有完整版和Nano版,对于资源受限的微控制器(MCU)系统,可通过简单易用的工具,裁剪出仅需要 3KB Flash、1.2KB RAM 内存资源的 NANO 内核版本;而相对资源丰富的物联网设备,可使用RT-Thread完整版,通过在线的软件包管理工具,配合系统配置工具实现直观快速的模块化裁剪,并且可以无缝地导入丰富的软件功能包,实现类似 Android 的图形界面及触摸滑动效果、智能语音交互效果等复杂功能。 RT-Thread架构 RT-Thread是一个集实时操作系统(RTOS)内核、中间件组件的物联网操作系统,架构如下: 内核层:RT-Thread内核,是 RT-Thread的核心部分,包括了内核系统中对象的实现,例如多线程及其调度、信号量、邮箱消息队列、内存管理、定时器等;libcpu/BSP(芯片移植相关文件 / 板级支持包)与硬件密切相关,由外设驱动和 CPU 移植构成。 组件与服务层:组件是基于 RT-Thread内核之上的上层软件,例如虚拟文件系统、FinSH命令行界面、网络框架、设备框架等。采用模块化设计,做到组件内部高内聚,组件之间低耦合。 RT-Thread软件包:运行于 RT-Thread物联网操作系统平台上,面向不同应用领域的通用软件组件,由描述信息、源代码或库文件组成。RT-Thread提供了开放的软件包平台,这里存放了官方提供或开发者提供的软件包,该平台为开发者提供了众多可重用软件包的选择,这也是 RT-Thread生态的重要组成部分。软件包生态对于一个操作系统的选择至关重要,因为这些软件包具有很强的可重用性,模块化程度很高,极大的方便应用开发者在最短时间内,打造出自己想要的系统。RT-Thread已经支持的软件包数量已经达到 180+。 RT-Thread的特点: 资源占用极低,超低功耗设计,最小内核(Nano版本)仅需1.2KB RAM,3KB Flash。 组件丰富,繁荣发展的软件包生态 。 简单易用 ,优雅的代码风格,易于阅读、掌握。 高度可伸缩,优质的可伸缩的软件架构,松耦合,模块化,易于裁剪和扩展。 强大,支持高性能应用。 跨平台、芯片支持广泛。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

zsky_01

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值