RT-Thread record (7. Mailbox and message queue of IPC mechanism)

讲完了线程同步的机制,我们要开始线程通讯的学习,
线程通讯中的邮箱消息队列也属于 RT-Thread 的IPC机制。

foreword

Similar to the semaphore, mutex, and event set introduced in the previous article, mailboxes and message queues are also RT-Thread IPC mechanisms. However, semaphores belong to the thread synchronization mechanism and cannot transmit messages between threads. The mailboxes and message queues introduced in this article are the mechanisms for realizing message transmission between threads.

Compared with the content of the previous article, the learning of thread communication will be relatively complicated. Because it involves the transmission of messages, there may be many different situations in the actual project, so the usage scenarios and methods of mailboxes and message queues are: key, especially the message queue. Basically all message types in the actual project can use the way of message queue. I will use a separate blog post to explain the application of message queues to serial communication. This article will first give a basic introduction and basic examples.

The development environment recorded in this RT-Thread column:

RT-Thread recording (1. RT-Thread version, RT-Thread Studio development environment and quick start with CubeMX development)

1. Mailbox

Mail in RT-Thread is an effective means for threads, interrupt services, and timers to send messages to threads (interrupts and timers require non-blocking methods, which cannot wait to be sent or received).

Each message in the mailbox can only hold a fixed 4 bytes of content (a 32-bit kernel can pass exactly one pointer).

Mailbox features Less RAM space and higher efficiency.

RT-Thread is somewhat similar to FreeRTOS's task notification, and it can only pass 4 bytes of content.
However, the task notification of FreeRTOS belongs to the task itself, and each task has one and only one notification,
while the mailbox of RT-Thread is managed by the mailbox control block. A new mailbox can contain multiple emails (each 4 bytes) .

1.1 Mailbox Control Block

The old rules use the source code, explain and see the comments (it is also easy to use and copy ~ ~!)

#ifdef RT_USING_MAILBOX
/**
 * mailbox structure
 */
struct rt_mailbox
{
    
    
    struct rt_ipc_object parent;             /**< inherit from 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;
#endif

1.2 Mailbox operation

1.2.1 Create and delete

Like the previous threads, in a dynamic way, first define a pointer variable of a mailbox structure and receive the created handle.

Create mailbox:

/**
参数的含义:
1、name 		邮箱名称
2、size			邮箱容量(就是多少封邮件,4的倍数)
3、flag 		邮箱标志,它可以取如下数值: RT_IPC_FLAG_FIFO 或 RT_IPC_FLAG_PRIO
返回值:
RT_NULL 		创建失败
邮箱对象的句柄 	创建成功 
 */
rt_mailbox_t rt_mb_create(const char *name, rt_size_t size, rt_uint8_t flag)

The last flag is the same as the semaphore suggestion RT_IPC_FLAG_PRIO:
insert image description here
delete the mailbox:

/**
参数的含义:
mb 	邮箱对象的句柄
返回
RT_EOK 	成功
 */
rt_err_t rt_mb_delete(rt_mailbox_t mb)

1.2.2 Initialization and disengagement

In a static way, first define a mailbox structure and then initialize it.

It should be noted here that an array is also defined, which is used for the memory space of the mailbox, which is the same as the static initialization thread.

Initialize mailbox:

/**
参数含义:
1、mb	 	邮箱对象的句柄,需要取自定义的结构体地址
2、name	 	邮箱名称
3、msgpool 	缓冲区指针(用户自定义的数组的地址,第一个数组元素的地址)
4、size 	邮箱容量(就是数组的大小/4)
5、flag 	邮箱标志,它可以取如下数值: RT_IPC_FLAG_FIFO 或 RT_IPC_FLAG_PRIO
返回
RT_EOK 	成功
 */
rt_err_t rt_mb_init(rt_mailbox_t mb,
                    const char  *name,
                    void        *msgpool,
                    rt_size_t    size,
                    rt_uint8_t   flag)

Get out of the mailbox:

/**
参数的含义:
mb 	邮箱对象的句柄
返回
RT_EOK 	成功
 */
rt_err_t rt_mb_detach(rt_mailbox_t mb)

1.2.3 Send Email

Sending emails in RT-Thread is divided into sending emails with or without waiting, and sending urgent emails.

In the version of the project I built, there is no function for sending emergency emails. Here, according to the project source code, the function for sending emergency emails will not be introduced. In general STM32 applications, I personally think that it does not matter whether emergency emails are present or not!

No wait mode applies to all threads and interrupts, wait mode cannot be used in interrupts!

No waiting to send mail:

/**
参数:
1、mb	 	邮箱对象的句柄
2、value 	邮件内容
返回
RT_EOK 		发送成功
-RT_EFULL 	邮箱已经满了
看函数原型,其实就是把等待方式发送的时间改成了0
 */
rt_err_t rt_mb_send(rt_mailbox_t mb, rt_ubase_t value)
{
    
    
    return rt_mb_send_wait(mb, value, 0);
}

Sending without waiting is actually using the waiting method to send the mail, and the waiting time is 0:.

Waiting to send mail:

/**
参数:
1、mb 		邮箱对象的句柄
2、value 	邮件内容
3、timeout 	超时时间
返回:
RT_EOK 			发送成功
-RT_ETIMEOUT 	超时
-RT_ERROR 		失败,返回错误
 */
rt_err_t rt_mb_send_wait(rt_mailbox_t mb,
                         rt_ubase_t   value,
                         rt_int32_t   timeout)

1.2.4 Receive emails

When receiving mail, in addition to specifying the mailbox handle of the receiving mail, and specifying the storage location of the received mail (a variable is required to save the received data).

/**
参数含义:
1、mb 		邮箱对象的句柄,从哪个邮件控制块取邮件
2、value 	邮件内容,需要用一个变量保存
3、timeout 	超时时间
返回值:
RT_EOK 	接收成功
-RT_ETIMEOUT 	超时
-RT_ERROR 	失败,返回错误
 */
rt_err_t rt_mb_recv(rt_mailbox_t mb, rt_ubase_t *value, rt_int32_t timeout)

1.3 Example (pointer passing)

2 examples, the first is normal messaging and the second is a bootstrap example related to the number of mailboxes created.

1.3.1 Mailbox Messaging

As mentioned earlier, each email in the mailbox can only hold a fixed 4 bytes of content, but 4 bytes can pass pointers, we will make a simple demonstration.

In the example, we use two different keys to send emails, receive emails through an event, and print the content of the received emails.

Press key3, send 4 bytes of content, press Key2, send a string pointer:
insert image description here

Email creation:
insert image description here

In the receiving thread, we print out the received value: the
insert image description here
test result, two buttons are pressed, the thread can not only receive the 4-byte data directly passed, but also send a string through the passed pointer:
insert image description here

1.3.2 Example of the number of mailboxes

In the above example, the mailbox size we started to create is only one size. Let's test, if there is no thread to receive, will it print the message that the mailbox is full? We comment out the thread receiving mailbox code, and the rest is the same as the previous test:
insert image description here
Let's change it again, use a button to test whether the size is in bytes, or is it directly the number of emails, just look at the picture description:

insert image description here
When statically initializing the mail, we need to pay attention to the size of the space we open up, which needs to be a multiple of 4. We generally use the array divided by 4 to directly represent the sizesize of the mailbox, as follows:
insert image description here

RT-Thread manages these IPC mechanisms through control blocks. In the actual test, in order to deepen the understanding of an object, such as the mailbox here, you can directly print out the parameters of the mailbox to view the current mailbox status. Learn to test! ! !
insert image description here

Second, the message queue

The message queue can receive messages of variable length from threads or interrupt service routines, and buffer the messages in its own memory space.

The difference between a message queue and a mailbox is that the length is not limited to 4 bytes, but if the maximum byte of each message in the message queue is specified within 4 bytes, then the message queue is the same as a mailbox.

A typical application is to use the serial port to receive variable-length data (there will be a separate blog post later to introduce the application of message queues in serial port reception).

2.1 Message Queue Control Block

These properties of the message queue control block, we will use an example to print out, and deepen our understanding of these properties.

#ifdef RT_USING_MESSAGEQUEUE
/**
 * message queue structure
 */
struct rt_messagequeue
{
    
    
    struct rt_ipc_object parent;                        /**< inherit from ipc_object */

    void                *msg_pool;                      /**< 消息队列的开始地址 */

    rt_uint16_t          msg_size;                      /**< 每个消息长度 */
    rt_uint16_t          max_msgs;                      /**< 最大的消息数量 */

    rt_uint16_t          entry;                         /**< 已经有的消息数 */

    void                *msg_queue_head;                /**< list head 链表头 */
    void                *msg_queue_tail;                /**< list tail 链表尾*/
    void                *msg_queue_free;                /**< 空闲消息链表 */

    rt_list_t            suspend_sender_thread;         /**< 挂起的发送线程 */
};
typedef struct rt_messagequeue *rt_mq_t;
#endif

2.2 Message Queue Operations

2.2.1 Create and delete

First define a pointer variable of the mailbox structure, and receive the created handle.

Create a message queue:

/**
参数:
1、name 		消息队列的名称
2、msg_size 	消息队列中一条消息的最大长度,单位字节
3、max_msgs 	消息队列的最大个数
4、flag 		消息队列采用的等待方式,它可以取如下数值: RT_IPC_FLAG_FIFO 或 RT_IPC_FLAG_PRIO
返回:
RT_EOK 				发送成功
消息队列对象的句柄 	成功
RT_NULL 			失败
 */
rt_mq_t rt_mq_create(const char *name,
                     rt_size_t   msg_size,
                     rt_size_t   max_msgs,
                     rt_uint8_t  flag)

Notice! msg_sizeThe unit is bytes. RT-Thread is the default in 32-bit systems #define RT_ALIGN_SIZE 4, so if it msg_sizeis not aligned with 4 bytes, the system will automatically complete it.

For example, if the user defines it as 9, then the system will automatically set the message queue size to 12, define it as 1, and set it to 4.

For other flaguses, you still have to pay attention. Like the mailbox semaphore, pay attention to real-time issues.

Delete message queue:

/**
参数
mq 		消息队列对象的句柄
返回
RT_EOK 	成功
 */
rt_err_t rt_mq_delete(rt_mq_t mq)

2.2.2 Initialization and disengagement

In a static way, first define a message queue structure, and then initialize it.

Initialize the message queue:

/**
参数:
1、mq 			消息队列对象的句柄,需要取自定义的结构体地址
2、name 		名称
3、msgpool 		存放消息的地址
4、msg_size 	消息队列中一条消息的最大长度,单位字节
5、pool_size 	存放消息的缓冲区大小
6、flag 		消息队列采用的等待方式,
返回:
RT_EOK 	成功
 */
rt_err_t rt_mq_init(rt_mq_t     mq,
                    const char *name,
                    void       *msgpool,
                    rt_size_t   msg_size,
                    rt_size_t   pool_size,
                    rt_uint8_t  flag)

Get off the message queue:

/**
参数:
mq 		消息队列对象的句柄
返回:
RT_EOK 	成功
 */
rt_err_t rt_mq_detach(rt_mq_t mq)

2.2.3 Sending a message

Like email, sending emails in RT-Thread is divided into sending with or without waiting, and sending urgent messages.

No wait mode applies to all threads and interrupts, wait mode cannot be used in interrupts!

No waiting to send message:

/**
看函数原型,其实就是把等待方式发送的时间改成了0
参数:
1、mq 		消息队列对象的句柄
2、buffer 	消息内容
3、size 	消息大小
返回:
RT_EOK 		成功
-RT_EFULL 	消息队列已满
-RT_ERROR 	失败,表示发送的消息长度大于消息队列中消息的最大长度
 */
rt_err_t rt_mq_send(rt_mq_t mq, const void *buffer, rt_size_t size)
{
    
    
    return rt_mq_send_wait(mq, buffer, size, 0);
}

Waiting to send mail:

/**
除了最后多一个时间,其他参数,和上面无等待方式一样
timeout 	超时时间(时钟节拍)
*/
rt_err_t rt_mq_send_wait(rt_mq_t     mq,
                         const void *buffer,
                         rt_size_t   size,
                         rt_int32_t  timeout)

Send urgent message:

/**
参数:
1、mq 		消息队列对象的句柄
2、buffer 	消息内容
3、size 	消息大小
返回:
RT_EOK 		成功
-RT_EFULL 	消息队列已满
-RT_ERROR 	失败
 */
rt_err_t rt_mq_urgent(rt_mq_t mq, const void *buffer, rt_size_t size)

2.2.4 Receiving messages

When receiving a message, the receiver needs to specify the message queue object handle to store the message, and specify a memory buffer, and the content of the received message will be copied to the buffer.

/**
参数:
mq 				消息队列对象的句柄
buffer 			消息内容
size 			消息大小
timeout 		指定的超时时间
返回:
RT_EOK 			成功收到
-RT_ETIMEOUT 	超时
-RT_ERROR 		失败,返回错误
 */
rt_err_t rt_mq_recv(rt_mq_t    mq,
                    void      *buffer,
                    rt_size_t  size,
                    rt_int32_t timeout)

2.3 Brief analysis of message queue principle

Message queue control block:

To understand the principle of message queue, we have to start from its initialized state:
insert image description here

Sending a message, in fact, all the steps are in the rt_mq_send_waitfunction, once again, learn to read the source code!

A few key points to explain:
insert image description here
Of course, there is no specific explanation of the waiting time problem, because both sending and receiving can block waiting, this is not the point to understand.
insert image description here

After the sending is completed, if it is found that a thread is waiting for the message queue, a scheduling will occur:
insert image description here
receiving a message, in fact, is similar, you can view the source code yourself and try to analyze it.

For the understanding of the above process, I wrote a separate example, combined with the example to understand the above steps, more intuitive! See below for an example of understanding how message queues work.

2.4 Example (understanding of message queue principle)

Two examples, the first is for a more intuitive understanding of the message queue principle, and the second is for simple message passing.

For an example of a typical serial port receiving variable length data, I will use a separate article to introduce it.

2.4.1 Understand the principle of message queue

We analyzed the principle of message queue in " 2.3 Brief Analysis of Message Queuing Principle" above, and let's use an example to intuitively deepen our understanding.

Create a new message queue (note the parameters when creating a new one):
insert image description here

We press 2 buttons to send messages through Key2:
insert image description here
print the status value corresponding to the message queue through Key3:
insert image description here
When we test, we observe the status of the message queue after initialization, and then observe the changes of head, tail, and free after each transmission. To deepen our understanding of message queues:

insert image description here

It is very intuitive to understand the principle of the message queue through the above example. If there is a message received, observe the change of the address, and also analyze the principle of receiving the message.

2.4.2 Messaging

Relatively speaking, message passing is much simpler. On the basis of the above, create a new task to receive the message (because no length identification is done, no parsing is done here):
insert image description here
still send the message through the Key2 button above:
insert image description here

Epilogue

Although this article only introduces two IPC mechanisms, they are used everywhere in the project.

The application of the message queue is very important in our actual use. The serial communication to receive data is realized by using the message queue. For the serial port application of message queue, I will open a separate blog post to summarize.

This article gives a good example of the implementation principle of message queue, or that sentence, learn to read more source code, and do more hands-on testing!

The blogger will write every blog post carefully, I hope everyone will support it! Thanks!

Guess you like

Origin blog.csdn.net/weixin_42328389/article/details/123770091