RT-Thread Quick Start - Message Mailbox

The previous articles introduced the synchronization mechanism between threads (tasks): semaphores, mutexes, and event sets. Next we learn the communication mechanism between threads (tasks).

Generally speaking, RTOS will provide two mechanisms for inter-thread communication: message mailbox and message queue. The same is true for RT-Thread.

This article introduces the content related to RT-Thread message mailbox.

1 The working mechanism of the mailbox

1. Understand the message mailbox

Mailbox is a simple way of passing messages between threads, which is characterized by low overhead and high efficiency. Each message in the mailbox can hold a fixed size of content (for 32-bit processors, it can hold 4 bytes of content, so a message can hold exactly one pointer).

The working diagram of the mailbox is as follows. The interrupt service routine or thread sends a 4-byte email to the mailbox, and one or more threads can read the emails from the mailbox and process them.

picture

In an interrupt service routine, mail can only be sent in a non-blocking manner. The sending timeout period can be set in the thread to send mails in a blocking manner.

When a thread sends mail to the mailbox, if the mailbox is not full, the mail will be copied to the mailbox. If the mailbox is full, the sending thread can set a timeout period, choose to wait for suspension or return directly  -RT_EFULL .

In the process of receiving mail, when there is no mail in the mailbox and the timeout period is not 0, the mail receiving process will become a blocking mode. At this time, only the thread can receive the mail.

2. Mailbox control block

The data structure for managing mailboxes in RT-Thread is the mailbox control block, which is represented by a structure  struct rt_mailbox . In addition, rt_mailbox_t it represents the handle of the mailbox, that is, the pointer to the mailbox control block. The mailbox control block structure is defined as follows:

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;  /* 发送线程的挂起等待队列 */
};
typedef struct rt_mailbox *rt_mailbox_t;

rt_mailbox Objects are derived rt_ipc_object from it  , IPC managed by the container. The structure is  rt_ipc_object defined as follows:

struct rt_object
{
    char       name[RT_NAME_MAX]; /* 内核对象名称 */
    rt_uint8_t type;              /* 内核对象类型 */
    rt_uint8_t flag;              /* 内核对象的参数 */

#ifdef RT_USING_MODULE
    void      *module_id;  /* 应用程序模块 ID */
#endif
    rt_list_t  list;       /* 内核对象管理链表 */
};

struct rt_ipc_object
{
    struct rt_object parent;          /* 继承自 rt_object */
    rt_list_t        suspend_thread;  /* 挂起的线程链表 */
};

In the structure definition, the inheritance relationship is clear at a glance, so I won't repeat it.

2Manage mailboxes

The operation functions related to RT-Thread mailboxes are as follows, including: create/initialize mailboxes, send mails, receive mails, delete/leave mailboxes
.

picture

This article only focuses on several commonly used interface functions.

1. Create a mailbox

RT-Thread creates a mailbox in two ways: dynamic creation and static initialization.

The system function for dynamically creating a mailbox is as follows. When calling this function to create a mailbox, the kernel will first allocate a mailbox object from the object manager, then create a mailbox control block, and then initialize the mailbox control block, including the address of the mailbox buffer , the number of emails, the offset of the sent email in the mailbox, etc.

rt_mailbox_t rt_mb_create (const char* name, rt_size_t size, rt_uint8_t flag)

During the initialization of the mailbox control block, the kernel will dynamically allocate a piece of memory space to store the message mail, and the size of this memory is equal to the product of the mail size (4 bytes) and the mailbox capacity.

rt_mb_create()The parameter of the function name is the name of the mailbox; size it indicates the capacity of the mailbox; flag it is the sign of the mailbox, and the value is  RT_IPC_FLAG_FIFO or  RT_IPC_FLAG_PRIO.

If the mailbox is created successfully, the mailbox control block pointer will be returned; if the creation fails, it will be returned  RT_NULL.

Creating a mailbox in static mode requires two steps: (1) define a mailbox control block and a buffer for storing mail (2) initialize the mailbox control block.

The mailbox control block initialization function interface is as follows:

rt_err_t rt_mb_init(rt_mailbox_t mb,
                    const char* name,
                    void* msgpool,
                    rt_size_t size,
                    rt_uint8_t flag)

The parameter  mb is the pointer of the mailbox control block; name the name of the mailbox; msgpool the buffer pointer of the mailbox size ; the capacity of  the flag mailbox; rt_mb_create()

The parameter here  size specifies the capacity of the mailbox, that is, if  msgpool the number of bytes of the pointed buffer is  N, then the capacity of the mailbox
should be  N/4.

rt_mb_init() The return value of  the function is RT_EOK.

There are two types of flag variable values ​​for creating mailboxes:

  • RT_IPC_FLAG_FIFO, the threads waiting for the mailbox are arranged in a first-in first-out manner.

  • RT_IPC_FLAG_PRIO, the threads waiting for the mailbox are arranged according to the priority.

2. Send email

There are two types of interface functions for sending emails provided by RT-Thread: one is the interface without waiting timeout, and the other is the interface with waiting timeout.

A thread or interrupt service program can send messages to other threads through the mailbox. The function interface for sending mail is as follows. This function does not have a waiting timeout parameter.

rt_err_t rt_mb_send (rt_mailbox_t mb, rt_ubase_t value)

The parameter  mb is the handle of the mailbox object; value it is the content of the mail.

If the sending is successful, the function returns  RT_EOK; if the sending fails, the function returns  -RT_EFULL, indicating that the mailbox is full.

The function interface for sending emails in waiting mode is as follows. This function has a waiting timeout parameter:

rt_err_t rt_mb_send_wait (rt_mailbox_t mb,
                          rt_ubase_t value,
                          rt_int32_t timeout)

The parameter of this function  timeout is the sending waiting timeout time, and the unit is the system clock beat. Other parameters are  rt_mb_send() the same as .

If the mailbox is full, the sending thread will  timeout wait for the space in the mailbox to be vacated due to receiving mail according to the set parameters. If there is still no free space after the timeout expires, the sending thread will be woken up and an error code will be returned.

Return  RT_EOK means sending successfully; return  -RT_ETIMEOUT means timeout; return  -RT_ERROR means sending failed.

The content of the mail can be 32-bit data in any format, an integer value or a pointer to a buffer. You can set it according to your actual application.

Note : When sending mail in the interrupt service routine, it should be sent without waiting delay, directly use  rt_mb_send() or wait for the function with timeout set to 0 rt_mb_send_wait().

3. Receive mail

The function interface of the thread receiving mail is as follows. When the thread receives mail, it needs to specify the mailbox handle for receiving mail, the storage location of the mail and the timeout time for waiting.

rt_err_t rt_mb_recv (rt_mailbox_t mb, rt_ubase_t *value, rt_int32_t timeout)

The parameter  mb is the handle of the mailbox; value the storage address of the mailbox message; timeout and the waiting timeout time.

If the reception is successful, it will return  RT_EOK; if the reception times out, it will return  -RT_ETIMEOUT; if the reception fails, it will return  -RT_ERROR.

Only when there is mail in the mailbox, the receiver can immediately get the mail and return  RT_EOK; otherwise, the receiving thread will hang in the waiting thread queue or return immediately according to the set timeout time (the timeout time is set to 0).

3 practical exercises

As an example to illustrate the usage of the mailbox operation function, the code is as follows. Dynamically create two threads, one thread sends emails to the mailbox, and one thread receives emails from the mailbox.

#include <rtthread.h>

#define THREAD_PRIORITY 8
#define THREAD_TIMESLICE 5

/* 邮 箱 控 制 块 */
rt_mailbox_t mb_handle;

static char mb_str1[] = "I'm a mail!";
static char mb_str2[] = "this is another mail!";
static char mb_str3[] = "over";

/* 线程 1 入口 */
static void thread1_entry(void *parameter)
{
    char *str;

    while (1)
    {
        rt_kprintf("thread1: try to recv a mail\n");

        /* 从邮箱中收取邮件 */
        if (rt_mb_recv(mb_handle, (rt_ubase_t *)&str, RT_WAITING_FOREVER) == RT_EOK)
        {
            rt_kprintf("thread1: get a mail from mailbox, the content:%s\n", str);
            if (str == mb_str3)
            {
                break;
            }
            /* 延时 100ms */
            rt_thread_mdelay(100);
        }   
    }
}

/* 线程 2 入口 */
static void thread2_entry(void *parameter)
{
    rt_uint8_t count = 0;

    while (count < 10)
    {
        count ++;
        if (count & 0x1)
        {
            /* 发送 mb_str1 地址到邮箱中 */
            rt_mb_send(mb_handle, (rt_uint32_t)&mb_str1);
        }
        else
        {
            /* 发送 mb_str2 地址到邮箱中 */
            rt_mb_send(mb_handle, (rt_uint32_t)&mb_str2);
        }
        /* 延 时 200ms */
        rt_thread_mdelay(200);
    }
    /* 发送邮件告诉线程 1, 线程 2 已经运行结束 */
    rt_mb_send(mb_handle, (rt_uint32_t)&mb_str3);
}

int main()
{
    /* 线程控制块指针 */
    rt_thread_t thread1 = RT_NULL;
    rt_thread_t thread2 = RT_NULL;  

    /* 创建一个邮箱 */
    mb_handle = rt_mb_create("mt", 32, RT_IPC_FLAG_FIFO);
    if (mb_handle == RT_NULL)
    {
        rt_kprintf("create mailbox failed.\n");
        return -1;
    }

    /* 动态创建线程1 */
    thread1 = rt_thread_create("thread1", thread1_entry, RT_NULL,
                    1024, THREAD_PRIORITY - 1, THREAD_TIMESLICE);

    if(thread1 != RT_NULL)
    {
        /* 启动线程 */
        rt_thread_startup(thread1);
    }

    /* 动态创建线程2 */
    thread2 = rt_thread_create("thread2", thread2_entry, RT_NULL,
                    1024, THREAD_PRIORITY, THREAD_TIMESLICE);
    if(thread2 != RT_NULL)
    {
        /* 启动线程 */
        rt_thread_startup(thread2);
    }   
}

Compile and run the results as follows:

picture

In this routine, thread 2 sends emails 11 times in total; thread 1 receives emails, receives 11 emails, prints out the contents of the emails, and ends the operation according to the judgment.

4 other operation functions

For RT-Thread mailbox operations, there is also a function to delete mailboxes that is not introduced. You can simply understand it.

1. Delete the dynamically created mailbox

To delete  rt_mb_create() a mailbox created by a function, you can call the following function:

rt_err_t rt_mb_delete (rt_mailbox_t mb)

Calling this function can release the memory resources occupied by the mailbox control block and the memory occupied by the mailbox buffer. When deleting a mailbox object, you should ensure that the mailbox is no longer in use.

Before deletion, all threads suspended on the mailbox will be woken up, and then the memory block occupied by the mailbox object will be released.

2. Break away from statically created mailboxes

To delete  rt_mb_init() the initialized mailbox, you can use the following function:

rt_err_t rt_mb_detach(rt_mailbox_t mb)

When this function is called, all threads that are suspended on the mailbox waiting queue will be woken up first, and then the mailbox will be detached from the kernel object manager.

Guess you like

Origin blog.csdn.net/weixin_41114301/article/details/131877045