This article refers to [Wildfire EmbedFire] "RT-Thread Kernel Implementation and Application Development - Based on STM32", which is only used as a personal study note. For more detailed content and steps, please check the original text (you can download it from the Wildfire Data Download Center)
Article directory
Basic Concepts of Mailboxes
The mailbox is a common IPC communication method in the operating system, and the mailbox can be between threads. Messages are transmitted between interrupts and threads. In addition, mailboxes have lower overhead and higher efficiency than semaphores and message queues, so they are often used for thread-to-thread, interrupt-to-thread communication. Each mail in the mailbox can only hold a fixed 4-byte content (STM32 is a 32-bit processing system, the size of a pointer is 4 bytes, so a mail can hold exactly one pointer), when the thread needs to When transferring larger messages between each other, a pointer to a buffer can be sent to the mailbox as a message.
--original
Mailboxes are similar to message queues, except that the size of mailboxes can only be a fixed 4 bytes, but message queues can be of any length.
How mailboxes work
The operating principle of the mailbox is shown in the figure below. In the figure below, the thread or interrupt service function sends a 4-byte email to the mailbox. There are several threads in the thread waiting queue waiting to receive the mailbox.
The thread can read mail messages from the mailbox. When the mail in the mailbox is empty, it decides whether to suspend the reading thread according to the user-defined blocking time; when there are new mails in the mailbox, the suspended reading thread is awakened , the mailbox is also an asynchronous communication method.
One or more messages can be put into a mailbox by a mailbox, a thread, or an interrupt service function. Likewise, one or more threads can get mail messages from mailboxes. When multiple emails are sent to the mailbox, usually the email that enters the mailbox first should be passed to the thread first, that is, the thread gets the message that enters the mailbox first, that is, the first-in-first-out principle (FIFO), while RT - Mailboxes in Thread support priority, which means that among all threads waiting for mail, the one with the highest priority will get the mail first.--original
Application scenarios of mailboxes
The mailbox of RT-Thread can store a fixed number of mails, which is determined when the mailbox is initialized or created. Although a message can only be 4 bytes in size, it is possible to use a pointer to a buffer (array, 4 bytes per element) as a message, so that larger content can be sent.
Compared with other communication methods, mailboxes are characterized by low overhead and high efficiency. Both sending and receiving mailboxes support blocking waiting, which is very suitable for inter-thread, interrupt and inter-thread communication, but be careful not to use blocking to send emails in interrupts.
Since the mailbox message size can only be 4 bytes, and the pointer of the 32-bit system is exactly 4 bytes, the mailbox is very suitable for passing pointer data.
How to use email
In actual project development, a lot of information is a structure variable, can it be delivered by email? The answer is no problem, just use a struct pointer.
//定义一个结构体指针(结构体成员自定义)
struct temp *pTemp;
//申请动态堆空间
pTemp = (struct temp*)rt_malloc(sizeof(struct temp));
//发送方在数据处理完成后,将这个消息指针发送给一个邮箱,邮箱名为mb
rt_mb_send(mb, (rt_uint32_t)pTemp);
Since the memory space of the above message pointer is manually applied for, after the thread receives the mailbox information and the processing is completed, it is also necessary to manually release the space of the message pointer
struct temp *pTemp;
if(rt_mb_recv(mb, (rt_uint32_t *)&pTemp) == RT_EOK)
{
//数据处理完成后,释放消息指针内存空间
rt_free(pTemp);
}
mailbox control block
struct rt_mailbox
{
struct rt_ipc_object parent; /**< 继承自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; /**< 发送线程的挂起等待队列 */
};
Mailbox function interface
Create mailbox rt_mb_create()
rt_mailbox_t
rt_mb_create
(const char* name, rt_size_t size, rt_uint8_t flag);
When creating a mailbox object, a mailbox object control block is first created, and then a memory space is allocated to the mailbox to store the mail. The size of this memory is equal to the product of the mail size (4 bytes) and the mailbox capacity, and then initialized to receive mail and send The offset of the message in the mailbox. For IPC objects created with the RT_IPC_FLAG_PRIO priority flag, when multiple threads are waiting for resources, the thread with higher priority will get the resources first. The IPC object created by using the RT_IPC_FLAG_FIFO FIFO flag will obtain resources in a first-come, first-served order when multiple threads are waiting for resources.
parameter | describe |
---|---|
name | the name of the mailbox |
size | Mailbox Capacity |
flag | mailbox sign |
Delete mailbox rt_mb_delete()
rt_err_t
rt_mb_delete
(rt_mailbox_t mb);
When deleting a mailbox, if a thread is suspended on the mailbox object, the kernel first wakes up all threads suspended on the mailbox (the thread gets the return value of -RT_ERROR), then releases the memory used by the mailbox, and finally deletes the mailbox object .
parameter | describe |
---|---|
mb | handle to the mailbox object |
Initialize mailbox rt_mb_init()
rt_err_t
rt_mb_init
(rt_mailbox_t mb, const char* name, void* msgpool,
rt_size_t size, rt_uint8_t flag)
When initializing the mailbox, this function interface needs to obtain the mailbox object control block, the pointer of the buffer, and the mailbox name and mailbox capacity that the user has applied for. For IPC objects created with the RT_IPC_FLAG_PRIO priority flag, when multiple threads are waiting for resources, the thread with higher priority will get the resources first. The IPC object created by using the RT_IPC_FLAG_FIFO FIFO flag will obtain resources in a first-come, first-served order when multiple threads are waiting for resources.
parameter | describe |
---|---|
mb | handle to the mailbox object |
name | the name of the mailbox |
msgpool | buffer pointer |
size | Mailbox Capacity |
flag | mailbox sign |
Mailbox receive rt_mb_recv()
rt_err_t
rt_mb_recv
(rt_mailbox_t mb, rt_uint32_t* value, rt_int32_t timeout);
When receiving emails, the recipient needs to specify the mailbox handle to receive the emails, and specify the storage location of the received emails and the maximum timeout period that can be waited. If a timeout is set when receiving, when the email is still not received within the specified time, -RT_ETIMEOUT will be returned.
parameter | describe |
---|---|
mb | handle to the mailbox object |
value | content of email |
timeout | Specify timeout |
Mailbox send rt_mb_send() (non-blocking)
rt_err_t
rt_mb_send
(rt_mailbox_t mb, rt_uint32_t value);
The sent mail can be 32-bit data in any format, an integer value or a pointer to a buffer. When the mail in the mailbox is full, the thread sending the mail or the interrupt program will receive the return value of -RT_EFULL.
parameter | describe |
---|---|
mb | handle to the mailbox object |
value | content of email |
Mailbox send rt_mb_send_wait() (blocking)
rt_err_t
rt_mb_send_wait
(rt_mailbox_t mb, rt_uint32_t value, rt_int32_t timeout);
The difference between rt_mb_send_wait and rt_mb_send is that if the mailbox is full, the sending thread will wait for the space in the mailbox to be vacated for receiving mail according to the set timeout parameter. If there is still no free space after the set timeout time, the sending thread will be woken up and return an error code.
parameter | describe |
---|---|
mb | handle to the mailbox object |
value | content of email |
timeout | Specify timeout |
mailbox experiment
To use mailbox communication in RT-Thread, you need to modify the rtconfigh
configuration file first. You can use the following two methods:
- Uncomment, open macro definition,
- Or use the
Configuration Wizard
wizard for graphical configuration,
This experiment refers to the original text corresponding to the experimental code, which only contains the man() function, and the peripheral initialization related code is not given below.
This experiment needs to create two threads, one is the receiving thread and the other is the sending thread. The receiving thread is responsible for receiving mailbox information and printing it to the serial port. The sending thread is responsible for scanning the buttons. When the button is detected, it sends the corresponding mailbox information to the mailbox.
#include "board.h"
#include "rtthread.h"
// 定义线程控制块指针
static rt_thread_t recv_thread = RT_NULL;
static rt_thread_t send_thread = RT_NULL;
// 定义邮箱控制块
static rt_mailbox_t test_mb = RT_NULL;
// 实验需要用到的全局变量
char test_str1[] = "this is a mail test 1";
char test_str2[] = "this is a mail test 2";
/******************************************************************************
* @ 函数名 : recv_thread_entry
* @ 功 能 : 接收线程入口函数
* @ 参 数 : parameter 外部传入的参数
* @ 返回值 : 无
******************************************************************************/
static void recv_thread_entry(void *parameter)
{
rt_err_t uwRet = RT_EOK;
char *r_str;
while(1)
{
// 等待接收邮箱信息
uwRet = rt_mb_recv(test_mb, // 邮箱对象句柄
(rt_uint32_t*)&r_str, // 接收邮箱信息
RT_WAITING_FOREVER); // 超时一直等
if(uwRet == RT_EOK)
{
rt_kprintf("邮箱接收到的内容:%s\n\n", r_str);
LED0_TOGGLE; // LED0 反转
}
else
{
rt_kprintf("邮箱接收错误!\n");
}
}
}
/******************************************************************************
* @ 函数名 : send_thread_entry
* @ 功 能 : 发送线程入口函数
* @ 参 数 : parameter 外部传入的参数
* @ 返回值 : 无
******************************************************************************/
static void send_thread_entry(void *parameter)
{
rt_err_t uwRet = RT_EOK;
while(1)
{
// KEY0 被按下
if(Key_Scan(KEY0_GPIO_PORT, KEY0_GPIO_PIN) == KEY_ON)
{
rt_kprintf("send:KEY0被单击\n");
// 发送邮箱信息1
uwRet = rt_mb_send(test_mb, (rt_uint32_t)&test_str1);
if(uwRet == RT_EOK)
rt_kprintf("邮箱信息发送成功!\n\n");
else
rt_kprintf("邮箱信息发送失败!\n\n");
}
// WK_UP 被按下
if(Key_Scan(WK_UP_GPIO_PORT, WK_UP_GPIO_PIN) == KEY_ON)
{
rt_kprintf("send:WK_UP被单击\n");
// 发送邮箱2
uwRet = rt_mb_send(test_mb, (rt_uint32_t)&test_str2);
if(uwRet == RT_EOK)
rt_kprintf("邮箱信息发送成功!\n\n");
else
rt_kprintf("邮箱信息发送失败!\n\n");
}
rt_thread_delay(20); //每20ms扫描一次
}
}
int main(void)
{
// 硬件初始化和RTT的初始化已经在component.c中的rtthread_startup()完成
// 创建一个邮箱
test_mb = // 邮箱控制块指针
rt_mb_create("test_mb", // 邮箱初始值
10, // 邮箱大小
RT_IPC_FLAG_FIFO); // FIFO队列模式(先进先出)
if(test_mb != RT_NULL)
rt_kprintf("邮箱创建成功!\n");
// 创建一个动态线程
recv_thread = // 线程控制块指针
rt_thread_create("recv", // 线程名字
recv_thread_entry, // 线程入口函数
RT_NULL, // 入口函数参数
255, // 线程栈大小
5, // 线程优先级
10); // 线程时间片
// 开启线程调度
if(recv_thread != RT_NULL)
rt_thread_startup(recv_thread);
else
return -1;
// 创建一个动态线程
send_thread = // 线程控制块指针
rt_thread_create("send", // 线程名字
send_thread_entry, // 线程入口函数
RT_NULL, // 入口函数参数
255, // 线程栈大小
5, // 线程优先级
10); // 线程时间片
// 开启线程调度
if(send_thread != RT_NULL)
rt_thread_startup(send_thread);
else
return -1;
}
Experimental phenomena
When KEY0 is pressed, the send thread sends mailbox information 1 to the recv thread. When WK_UP is pressed, the send thread sends mailbox information 2 to the recv thread; when the recv thread receives the mailbox information, it prints the received content: