在裸机编程中,经常会使用全局变量进行功能间的通信,如某些功能可能由于一些操作而改变全局变量的值,另一个功能对此全局变量进行读取,根据读取到的全局变量值执行相应的动作,达到通信协作的目的。
邮箱服务是实时操作系统中一种典型的线程间通信方法。
有两个线程,线程1检测按键状态并发送,线程2读取按键的状态相应地改变LED的亮灭。
这里就可以使用邮箱的方式进行通信,线程1将按键的状态作为邮件发送到邮箱,线程2在邮箱中读取邮件获得按键状态并对LED执行亮灭操作。
RT-Thread操作系统的邮箱用于线程间通信,特点是开销比较低,效率较高。邮箱中的每一封邮件只能容纳固定的4字节内容(针对32位处理系统,指针的大小即为4个字节,所以一封邮件恰好能够容纳一个指针)。典型的邮箱也称作交换消息,如下图所示,线程或中断服务例程把一封4字节长度的邮件发送到邮箱中,而一个或多个线程可以从邮箱中接收这些邮件并进行处理。
非阻塞方式的邮件发送过程能够安全的应用于中断服务中,是线程、中断服务、定时器向线程发送消息的有效手段。
通常来说,邮件收取过程可能是阻塞的,这取决于邮箱中是否有邮件,以及收取邮件时设置的超时时间。当邮箱中不存在邮件且超时时间不为 0 时,邮件收取过程将变成阻塞方式。在这类情况下,只能由线程进行邮件的收取。
当一个线程向邮箱发送邮件时,如果邮箱没满,将把邮件复制到邮箱中。如果邮箱已经满了,发送线程可以设置超时时间,选择等待挂起或直接返回 - RT_EFULL。如果发送线程选择挂起等待,那么当邮箱中的邮件被收取而空出空间来时,等待挂起的发送线程将被唤醒继续发送。
当一个线程从邮箱中接收邮件时,如果邮箱是空的,接收线程可以选择是否等待挂起直到收到新的邮件而唤醒,或可以设置超时时间。当达到设置的超时时间,邮箱依然未收到邮件时,这个选择超时等待的线程将被唤醒并返回 - RT_ETIMEOUT。如果邮箱中存在邮件,那么接收线程将复制邮箱中的 4 个字节邮件到接收缓存中。
在RT-Thread中,邮箱控制块是操作系统用于管理邮箱的一个数据结构,由结构体struct rt_mailbox表示。
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;
}
rt_mailbox_t rt_mb_create(const char* name, rt_size_t size, rt_uint8_t flag);
创建邮箱对象时会先从对象管理器中分配一个邮箱对象,然后给邮箱动态分配一块内存空间用来存放邮件,这块内存的大小等于邮件大小与邮箱容量的乘积,接着初始化接收邮件数目和发送邮件在邮箱中的偏移量。
线程或中断服务程序可以通过邮箱给其他线程发送邮件,发送邮件函数接口如下:
rt_err_t rt_mb_send(rt_mailbox_t mb, rt_uint32_t value);
发送的邮件可以是 32 位任意格式的数据,一个整型值或者一个指向缓冲区的指针。当邮箱中的邮件已经满时,发送邮件的线程或者中断程序会收到 -RT_EFULL 的返回值。
邮箱是一种简单的线程间消息传递方式,特点是开销比较低,效率较高。
能够一次传递一个4字节大小的邮件,并且邮箱具备一定的存储功能,能够缓存一定数量的邮件数(邮件数由创建、初始化邮箱时指定的容量决定)。
邮箱中一封邮件的最大长度是4字节,所以邮箱能够用于不超过4字节的消息传递。
由于在32位系统上,4字节的内容恰好可以放置一个指针,因此当需要再线程间传递比较大的信息时,可以把指向一个缓冲区的指针作为邮件发送到邮箱中。
struct msg
{
rt_uint8_t *data_ptr;
rt_uint32_t data_size;
}
对于这样一个消息结构体,其中包含了指向数据的指针data_ptr和数据块长度的变量data_size。
消息队列是另一种常用的线程间通讯方式,是邮箱的扩展。
可以应用在多种场合:线程间的消息交换、使用串口接收不定长数据等。
消息队列能够接收来自线程或中断服务例程中不固定长度的消息,并把消息缓存在自己的内存空间中。其它消息也能够从消息队列中读取相应的消息,而当消息队列是空的时候,可以挂起读取线程。
当有新的消息到达时,挂起的线程将被唤醒以接收并处理消息。消息队列是一种异步的通信方式。
如下图所示,线程或中断服务例程可以将一条或多条消息放入消息队列中。同样,一个或多个线程也可以从消息队列中获得消息。当有多个消息发送到消息队列时,通常将先进入消息队列的消息先传给线程,也就是说,线程先得到的是最先进入消息队列的消息,即先进先出原则(FIFO)。
RT-Thread操作系统的消息队列对象由多个元素组成,当消息队列被创建时,它就被分配了消息队列控制块:消息队列名称、内存缓冲区、消息大小以及队列长度等。
同时每个消息队列对象中包含着多个消息框,每个消息框可以存放一条消息;消息队列中的第一个和最后一个消息框被分别称为消息链表头和消息链表尾,对应于消息队列控制块中的msg_queue_head和msg_queue_tail;有些消息框可能是空的,它们可能通过msg_queue_free形成一个空闲消息框链表。
所有消息队列中的消息框总数即是消息队列的长度,这个长度可在消息队列创建时指定。
struct rt_messagequeue
{
struct rt_ipc_object parent;
void* msg_pool;//指向存放消息的缓冲区的指针
rt_uint16_t msg_size;
rt_uint16_t max_msgs;
rt_uint16_t entry;
void* msg_queue_head; /* 消息链表头 */
void* msg_queue_tail; /* 消息链表尾 */
void* msg_queue_free; /* 空闲消息链表 */
rt_list_t suspend_sender_thread; /* 发送线程的挂起等待队列 */
}
线程或中断服务程序都可以给消息队列发送消息。
当发送消息时,消息队列对象先从空闲消息链表上取下一个空闲消息块,把线程或者中断服务程序发送的消息内容复制到消息块上,然后把该消息块挂到消息队列的尾部。
当且仅当空闲消息链表上有可用的空闲消息块时,发送者才能成功发送消息;当空闲消息链表上无可用消息块,说明消息队列已满,此时,发送消息的的线程或者中断程序会收到一个错误码(-RT_EFULL)。发送消息的函数接口如下:
发送消息时,发送至需指定发送的消息队列的对象句柄(即指向消息队列控制块的指针),并且指定发送的消息内容以及消息大小。
在发送一个普通消息之后,空闲消息链表上的队首消息被转移到了消息队列尾。
消息队列可以应用于发送不定长消息的场合,包括线程与线程间的消息交换,以及中断服务例程中给线程发送消息(中断服务例程不能接收消息)。