讲完了线程同步的机制,我们要开始线程通讯的学习,
线程通讯中的邮箱消息队列也属于 RT-Thread 的IPC机制。
content
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:
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
:
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:
Email creation:
In the receiving thread, we print out the received value: the
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:
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:
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:
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 size
size of the mailbox, as follows:
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! ! !
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_size
The unit is bytes. RT-Thread is the default in 32-bit systems #define RT_ALIGN_SIZE 4
, so if it msg_size
is 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 flag
uses, 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:
Sending a message, in fact, all the steps are in the rt_mq_send_wait
function, once again, learn to read the source code!
A few key points to explain:
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.
After the sending is completed, if it is found that a thread is waiting for the message queue, a scheduling will occur:
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):
We press 2 buttons to send messages through Key2:
print the status value corresponding to the message queue through Key3:
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:
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):
still send the message through the Key2 button above:
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!