消息队列的工作机制
1. 理解消息队列
线程或中断服务例程可以将一条或多条消息放入消息队列中。
一个或多个线程也可以从消息队列中获得消息。当有多个消息发送到消息队列时,通常将先进入消息队列的消息先传给线程,也就是说,线程先得到的是最先进入消息队列的消息,即先进先出原则 (FIFO)。
如下图所示
2. 消息队列控制块
消息队列控制块是 RT-Thread 系统管理消息队列的一种数据结构,由结构体 struct rt_messagequeue 表示。另外 rt_mq_t 表示消息队列的句柄,即指向消息队列控制块的指针。
消息队列控制块的数据结构定义如下:
结构体定义中,继承关系一目了然,不再赘述。rt_messagequeue 对象从 rt_ipc_object 中派生,由 IPC 容器所管理。
消息队列的操作函数
RT-Thread 提供了多种管理消息队列的接口函数。包括:创建消息队列 - 发送消息 - 接收消息 - 删除消息队列。 如下图所示:
对于初学者来说,掌握其中常用的函数即可。本文重点介绍消息队列常用的函数接口。
实际项目中,使用消息队列的流程为:创建消息队列 - 发送消息 - 接收消息。我们就重点介绍一下对应的操作函数。
1. 创建消息队列
在 RT-Thread 中,同其他内核对象一样。创建消息队列也有两种方式:(1)动态创建(2)静态初始化。
动态创建一个消息队列的函数接口如下,调用此函数时,内核动态创建一个消息队列控制块。然后再分配一块内存空间,用于存放消息,这块内存的大小为:消息队列个数* [消息大小 + 消息头大小]。最后初始化消息队列以及消息队列控制块。
参数 name 为消息队列名称;msg_size 为队列中一条消息的长度,单位为字节;
max_msgs 为消息队列的最大个数;flag 为消息队列的等待方式。
创建成功,返回消息队列的句柄;创建失败,则返回 RT_NULL。
静态方式创建消息队列需要两步:
定义一个消息队列控制块以及一段存放消息的缓冲区
初始化消息队列控制块
消息队列控制块初始化函数如下:
函数的参数解释如下表:
初始化消息队列函数返回 RT_EOK。
创建或初始化完成消息队列后,所有消息块都挂在空闲消息链表上,消息队列为空。
创建消息队列的标志变量取值有两种:
RT_IPC_FLAG_FIFO,等待消息队列的线程按照先进先出的方式进行排列。
RT_IPC_FLAG_PRIO,等待消息队列的线程按照优先级的方式进行排列。
2. 发送消息
RT-Thread 提供的发送消息接口函数有两种:一种是无等待超时接口,一种是有等待超时。
线程或者中断服务程序都可以给消息队列发送消息,发送消息的函数接口如下,此函数没有等待超时参数。
参数 mq 为消息队列对象的句柄;buffer 为存放消息缓冲区的指针;size 为消息大小。
发送成功,函数返回 RT_EOK;消息队列已满,返回 -RT_EFULL;
发送的消息长度大于消息队列中消息块的最大长度,则返回 -RT_ERROR。
等待方式发送消息的函数接口如下,这个函数有等待超时参数:
此函数的参数 timeout 为发送等待超时时间,单位为系统时钟节拍。其他参数与 rt_mq_send() 相同。
如果消息队列已经满了,发送线程会根据设定的 timeout 参数等待消息队列中因为收取消息而空出空间。若超时时间到达依然没有空出空间,则发送线程将会被唤醒并返回错误码。
返回 RT_EOK 表示发送成功;返回 -RT_ETIMEOUT 表示超时;返回 -RT_ERROR 表示发送失败。
注意:在中断服务例程中发送邮件时,应该采用无等待延时的方式发送,直接使用 rt_mq_send() 或者等待超时设定为 0 的函数rt_mq_send_wait()。
3. 接收消息
线程接收消息的函数接口如下,
参数 mq 为消息队列对象的句柄;buffer 为消息内容;size 为消息大小;timeout 为超时时间。
接收消息时,需要指定消息队列的句柄,以及一块用于存储消息的缓冲区,接收到的消息内容将被复制到该缓冲区里。还需指定等待消息的超时时间。
当消息队列中为空时,接收消息的线程会根据设定的超时时间,挂起在消息队列的等待线程队列上,或直接返回。
实战演练
多说无益,实践出真知。我们来举个例子,学习一下如何使用消息队列。
动态创建两个线程和一个消息队列,一个线程往消息队列中发送消息,一个线程从消息队列中接收消息。
代码如下:
编译执行结果如下
该例程演示了消息队列如何使用。线程 1 从消息队列中收取消息;线程 2 定时给消息队列发送消息,一共发送了 20 条消息。
### 其他操作函数
对于 RT-Thread 消息队列操作来说,还有几个函数没有介绍。可以简单了解一下。
1. 删除动态创建的消息队列
删除由 rt_mq_create() 函数创建的消息队列,可以调用如下函数:
调用此函数,可以释放消息队列控制块占用的内存资源以及消息缓冲区占用的内存。
在删除一个消息队列对象时,应该确保该消息队列不再被使用。
在删除前会唤醒所有挂起在该消息队列上的线程,然后释放消息队列对象占用的内存块。
2. 脱离静态创建的消息队列
删除 rt_mq_init() 初始化的消息队列,可以用如下函数:
调用此函数时,首先会唤醒所有挂起在该消息队列中,线程等待队列上的线程,然后将该消息队列从内核对象管理器中脱离。
3.发送紧急消息
RT-Thread 中,提供了一种发送紧急消息的函数接口,其过程与发送消息几乎一样。其函数接口如下:
在发送紧急消息时,从空闲消息链表上取下来的消息块不是挂到消息队列的队尾,而是挂到队首,这样,接收者就能够优先接收到紧急消息,从而及时进行消息处理。