在实时操作系统中,如果需要在线程间或线程与中断间传送数据,就需要采用消息队列作为同步与通信手段。本节主要介绍消息队列的基本知识及应用场合、消息队列常用函数及消息队列的编程举例。对于消息队列所采用的结构体,以及存放消息函数、获取消息函数和内存池分配函数将在11.2节进行深入剖析。
消息(Message)是一种线程间数据传送的单位,它可以是只包含文本的字符串或数字,也可以更复杂,如结构体类型等,所以相比使用事件时传递的少量数据(1位或1个字),消息可以传递更多、更复杂的数据,这是通过消息队列来实现的。
消息队列(Message Queue)是在消息传输过程中保存消息的一种容器,是将消息从它的源头发送到目的地的中转站,它是能够实现线程之间同步和大量数据交换的一种队列机制。在该机制下,消息发送方在消息队列未满时将消息发往消息队列,接收方则在消息队列非空时将消息队列中的首个消息取出;而在消息队列满或者空时,消息发送方及接收方既可以等待消息队列满足条件,也可以不等待,直接进行后续操作。这样只要消息的平均发送速度小于消息的平均接收速度,就可以实现线程间的同步数据交换,即使偶尔产生消息堆积,也可以在消息队列中获得缓冲。
消息队列作为具有行为同步和缓冲功能的数据通信手段,主要适用于以下两个场合:第一,消息的产生周期较短,消息的处理周期较长;第二,消息的产生是随机的,消息的处理速度与消息内容有关,某些消息的处理时间有可能较长。这两种情况均可把产生与处理分在两个程序主体进行编程,它们之间通过消息队列通信。
在Mbed OS中,消息队列的常用函数有内存池分配函数(alloc)、消息存放函数(put)、消息获取函数(get)、释放内存块函数(free)和获取队列中消息数函数(count)。
在发送消息前,需要调用内存池分配函数从内存池中得到一块分配好的内存空间,之后才能填写消息并将其发送。函数原型如下:
此函数将消息放入消息队列中,若消息阻塞列表中有等待消息的线程,则将消息直接给线程,并不进入消息队列,否则给消息分配内存,并放入消息队列中。若无可分配内存,则返回等待超时或资源不可用。在GEC架构下,消息存放函数put被封装成mq_send函数。
此函数从消息队列中获取消息,若消息队列非空,则将消息队列中首个消息出队,此消息变为该线程的资源;若消息队列为空,则线程阻塞,直到消息队列获取到消息。在GEC架构下,消息获取函数get被封装成mq_recv函数。
当从消息队列中获取消息之后,需要调用释放内存块函数将消息所占用的内存块还给内存池,以便下次继续存放消息。函数原型如下:
调用该函数可以获知某一消息队列中消息的数量。
利用消息队列进行编程时,除了调用以上常用的函数,还需要创建一个消息队列及消息池,其具体语句如下。
1)创建消息队列
其中,Message_Type表示消息的类型;Queue_sz表示队列中消息的数量;QueueName表示队列的名称。
2)创建内存池
其中,Message_Type表示消息的类型;Pool_sz表示内存块的数量,一般与消息的数量相同;MemoryPoolName表示内存池的名称。
下面将举例说明如何通过消息队列实现线程间消息的传递。基于3.4节的样例工程,每当串口接收一个字节,就将一条完整的消息放入消息队列中,消息成功放入队列后,消息队列接收线程(run_messagerecv)会通过串口(波特率设置为115200)打印消息,以及消息队列中消息的数量。具体代码可参见“...\04-Softwareware\CH05\CH5.3.3-MessageQueue_mbedOS_STM32L431”文件。
使用消息队列的编程一般分为创建消息队列变量、发送消息和接收消息三个步骤,具体操作如下。
1)准备阶段
创建消息队列控制块,初始化消息队列,设置每个消息的最大值及消息队列最大可存放消息数:通过mq_create函数初始化消息队列结构体指针变量,设置每个消息的最大值及消息队列最大可存放消息数。例如,在本节样例程序中,在threadauto_appinit.c文件的app_init函数中初始化消息队列结构体指针变量,设置每个消息最大为8×4字节(rt_size_t类型的大小为4字节),消息队列最大可存放消息数为5,代码如下:
2)应用阶段
(1)将消息放入消息队列中:通过mq_send函数将消息放入消息队列中,若消息队列中存放的消息数已满,则会直接舍弃该条消息。例如,在本节样例程序中,串口接收中断处理程序将收到的消息放入消息队列中,代码如下:
(2)获取消息队列中的消息:通过mq_recv函数获取消息队列中存放的消息。例如,在本节样例程序中,在thread_messagerecv线程中获取消息,代码如下:
1)串口接收中断服务程序
当串口中断成功接收到一个字节数据时,将数据组成一条完整的消息,并放入消息队列中。
2)消息接收线程
当消息队列中有消息时,可获取队列中消息的地址,并输出消息,其具体代码如下:
3)程序执行流程分析
消息队列的执行流程需要等待串口接收一个完整的数据帧(帧头3A+8位数据+帧尾0D 0A)之后发送8位数据。每当串口接收一个完整的数据帧时,中断服务程序会将接收到的消息放入消息队列中。消息成功放入后,消息队列中的消息数量增1。若消息数量不足5个,则消息可以继续放入消息队列中;若消息数量超过5个,则消息溢出,溢出的消息会被舍去;若消息发送的速度快于消息处理的速度且消息数量超过5,则会产生消息堆积,而堆积的消息将被舍弃。
消息接收线程每隔1s从消息队列中获取消息,收到消息后输出消息内容,同时消息数量减1。若无消息可获取,则消息接收线程会被放入消息阻塞列表和等待列表中,直到有新的消息到来,才会从消息阻塞列表和等待列表中移出,放入就绪列表中。
(1)发送1个消息的串口收发数据结果如图5-3所示。
图5-3 发送1个消息的串口收发数据结果
(2)当连续发送不超过5个消息时,串口每隔1s便输出一个消息的内容。图5-4所示为发送3个消息的串口收发数据结果。
(3)当发送多于5个消息时,溢出的部分会被舍弃。如图5-5所示,同时发送6个消息时,只会输出5个消息。
图5-4 发送3个消息的串口收发数据结果
图5-5 发送6个消息的串口收发数据结果