RT-THREAD Kernel Quick Start Mailbox, Message Queue, Signal

Catalogue of Series Articles

RT-THREAD Kernel Quick Start (1) Threads

RT-THREAD Kernel Quick Start (2) Timer

RT-THREAD Kernel Quick Start (3) Semaphores, Mutexes, Events

Preface

This is the fourth in the Quick Start series, the last to last in the Programming section, followed by a Memory Management and Interrupts section, which is the end of the Kernel Quick Start series.

This is a communication between threads, that is, the data transfer between threads is slightly different from the previous synchronization. Synchronization generally does not involve data transfer between threads, but communication is designed to transfer data, which was also discussed in the previous section Synchronization Paper , the extension of the semaphore inside, a good understanding of how the semaphore is implemented, will soon understand how to communicate. Since this is the basis of the previous article, here is a quick and quick introduction, with only a brief introduction to the working methods. If you don't understand the previous sync, it's recommended that you look at it first. This article focuses on thread communication and thread synchronization, as well as their connections and concerns.

If you don't know the framework of the series, you can see the preface of this link
Series Framework in Foreword

1. Mailbox

1. Way of working

Mailboxes work in a very similar way to semaphores, except that semaphores are usually sent without regard to semaphore overflow. Mailboxes are full, and how many messages they can store depends on the size of the set memory pool (mailbox size).

Understand:

Postmasters and recipients can be used to explain how mailboxes work.

Send emails:
The postman delivers the message according to the way the mailbox is set. When the mailbox is full, the postman delivers the message according to whether he chooses to time out or not. If the mailbox is still full within a certain period of time (no notice of empty mailbox is received), you will be prompted (return the RT_EFULL value). Mail is stored in the same order as it is in reality. The sooner the message arrives, the later it will be, that is, the first one will be placed below the others and will be picked up first. Postmasters can also send urgent messages, placing them on top of all messages that have been sent for retrieval.

Get mail:
If there are multiple recipients (threads), messages are queued a certain way (usually with priority) to get them, once per queue.

2. Features

Mailboxes differ from synchronous communication in that they have a memory pool mechanism that determines how many messages can be stored in the mailbox, four bytes per message. Four bytes, or 32, is the size (fixed) of the MCU pointer, which holds exactly one pointer's content. When the pointer is passed in, it gets the address of the content it needs to access, and it can access the content address through mail to manipulate memory. Others, such as how threads queue to fetch mail, send mail to activate the thread, and send semaphore to activate it (the difference is that the mailbox is full).

3. Routines

Here, the structure pointer is sent as a pointer (pointer size is type independent, operating system and MCU bits and compiler bits related), where 32 bits, exactly 4 bytes


/*
 * List of programs: Mailbox routines
 *
 * Create two dynamic mailboxes, one sending and one receiving
 * Here, the structure pointer is sent as a pointer (pointer size is type independent, operating system and MCU bits and compiler bits related), where 32 bits, exactly 4 bytes
 * 
 */
#include <rtthread.h>

#define THREAD_PRIORITY      10
#define THREAD_TIMESLICE     5

/* Mailbox Control Block */
static struct rt_mailbox mb;
/* Memory pool for mailing */
static char mb_pool[128];

static int mb_data=0;

ALIGN(RT_ALIGN_SIZE)
static char thread1_stack[1024];
static struct rt_thread thread1;

//Mailbox case definition structure
struct msg
{
    rt_uint8_t *data_ptr;
    rt_uint32_t data_size;
};
/* Thread 1 Entry */
static void thread1_entry(void *parameter)
{
	struct msg* msg_ptr;
	int *a;
    while (1)
    {

				if (rt_mb_recv(&mb, (rt_uint32_t*)&msg_ptr,RT_WAITING_FOREVER) == RT_EOK)
				{
					
					rt_kprintf("thread1: sent a mail from mailbox, the adree:%d,const:%d,size:%d\n", msg_ptr->data_ptr,*msg_ptr->data_ptr,msg_ptr->data_size);			
						/* After the receiving thread has finished processing, the corresponding memory block needs to be released */
						rt_free(msg_ptr);//Released memory
				}
				
				if (rt_mb_recv(&mb, (rt_uint32_t*)&a,RT_WAITING_FOREVER) == RT_EOK)
				{
					
					rt_kprintf("thread1: sent a mail from mailbox, the adree:%d,const:%d\n", &a,a);			
				}				
		}
}

ALIGN(RT_ALIGN_SIZE)
static char thread2_stack[1024];
static struct rt_thread thread2;

/* Thread 2 Entry */
static void thread2_entry(void *parameter)
{
		struct msg* msg_ptr;
	  int a=0;
    while (1)
    {
			mb_data++;
			
			msg_ptr = (struct msg*)rt_malloc(sizeof(struct msg));
			msg_ptr->data_ptr = (rt_uint8_t *)&mb_data; /* Point to the corresponding data block address */
			msg_ptr->data_size =sizeof(mb_data); /* Length of data block */

			rt_kprintf("thread2: sent a mail from mailbox, the adree:%d,const:%d,size:%d\n", msg_ptr->data_ptr,*msg_ptr->data_ptr,msg_ptr->data_size);			
			rt_kprintf("thread2: sent a mail from mailbox, the adree:%d,const:%d\n",&a,a);			
			/* Send this message pointer to mb mailbox */
			rt_mb_send(&mb, (rt_uint32_t)msg_ptr);
					  /* Send this message pointer to mb mailbox */
			rt_mb_send(&mb, (rt_uint32_t)&a);
      rt_thread_mdelay(500);
    }
}

int mailbox_sample(void)
{
    rt_err_t result;

    /* Initialize a mailbox */
    result = rt_mb_init(&mb,
                        "mbt",                      /* Name is mbt */
                        &mb_pool[0],                /* The memory pool used by the mailbox is mb_pool */
                        sizeof(mb_pool) / 4,        /* Number of messages in mailbox because one message accounts for 4 bytes */
                        RT_IPC_FLAG_FIFO);          /* Thread Waiting with FIFO */
    if (result != RT_EOK)
    {
        rt_kprintf("init mailbox failed.\n");
        return -1;
    }

    rt_thread_init(&thread1,
                   "thread1",
                   thread1_entry,
                   RT_NULL,
                   &thread1_stack[0],
                   sizeof(thread1_stack),
                   THREAD_PRIORITY, THREAD_TIMESLICE);
    rt_thread_startup(&thread1);

    rt_thread_init(&thread2,
                   "thread2",
                   thread2_entry,
                   RT_NULL,
                   &thread2_stack[0],
                   sizeof(thread2_stack),
                   THREAD_PRIORITY, THREAD_TIMESLICE);
    rt_thread_startup(&thread2);
    return 0;
}

/* Export to msh command list */
MSH_CMD_EXPORT(mailbox_sample, mailbox sample);

Run result:

Routine analysis:

It works like a semaphore, except that sending mail sends addresses from memory. Once you get the address, you can access it through the pointer inside the object in your mailbox. Mailbox picks up messages by copying them directly into the data block.

Note:

  • If malloc is used to request memory, remember to free the requested memory in order to prevent memory leaks. The use of local variables is not stable enough, although it is also possible that the memory of local variables is randomly allocated and easily overwritten, once overwritten, the content will be lost. Send mail pointer/content, malloc is recommended.
  • You cannot use malloc inside an interrupt because it can easily lead to memory leaks. Queues can be used if you want to send content and use features like mailboxes inside an interrupt.

2. Message Queue

1. Way of working

A queue is an enhanced version of a mailbox that can hold indefinite bytes of content. The equivalent content of a message is not limited to four bytes (although the same thing can be done using a pointer).

2. Contact

Difference:
There are about two differences. The other uses are the same as email and should not be discussed.

  • The content of the message queue is variable in length. Mail is fixed length, 4 bytes, and message queue can also be set to 4 bytes, which is used as a mailbox. The message sending pointer sent when sending is OK.
  • Message queues send content. It copies the sent content directly, and
    Mailboxes are sent their own content and do not copy the content. Content addresses are easily lost when they are changed (especially when sending addresses).

3. Routines

Send a 4-byte message queue for easy comparison with mail.

/*
 * Program List: Message Queuing Routines
 * One Send Message, One Receive Message, Send Content
 */
#include <rtthread.h>

/* Message Queue Control Block */
static struct rt_messagequeue mq;
/* Memory pool used to place messages in message queues */
static rt_uint8_t msg_pool[2048];

ALIGN(RT_ALIGN_SIZE)
static char thread1_stack[1024];
static struct rt_thread thread1;


/* Thread 1 entry function */
static void thread1_entry(void *parameter)
{
		int *recv;
    while (1)
    {
			/* Receive message from message queue to msg_ In PTR */
			if (rt_mq_recv(&mq, &recv, sizeof(recv), RT_WAITING_FOREVER) == RT_EOK)
			{
					/* Successfully received the message and processed the data accordingly */
				rt_kprintf("thread: recv msg from msg queue,the constest :%d the adree:%d size =%d\n", &recv,recv,4);
			}
    }
    rt_kprintf("thread1: detach mq \n");
    rt_mq_detach(&mq);
}

ALIGN(RT_ALIGN_SIZE)
static char thread2_stack[1024];
static struct rt_thread thread2;
/* Thread 2 Entry */
static void thread2_entry(void *parameter)
{ 
    static int cnt;
    while (1)
    {
			cnt++;
			rt_kprintf("thread: sent msg from msg queue,the constest :%d the adree:%d size =%d\n", &cnt,cnt,4);			
			/* Send this message pointer to the mq message queue */
			rt_mq_send (&mq, &cnt, sizeof(cnt));
      rt_thread_mdelay(500);
    }
}

/* Initialization of a message queue example */
int msgq_sample(void)
{
    rt_err_t result;

    /* Initialize message queue */
    result = rt_mq_init(&mq,
                        "mqt",
                        &msg_pool[0],               /* Memory pool points to msg_pool */
                        1,                          /* The size of each message is 1 byte */
                        sizeof(msg_pool),           /* The size of the memory pool is msg_ Size of pool */
                        RT_IPC_FLAG_FIFO);          /* If multiple threads are waiting, assign messages on a first come, first served basis */

    if (result != RT_EOK)
    {
        rt_kprintf("init message queue failed.\n");
        return -1;
    }

    rt_thread_init(&thread1,
                   "thread1",
                   thread1_entry,
                   RT_NULL,
                   &thread1_stack[0],
                   sizeof(thread1_stack), 25, 5);
    rt_thread_startup(&thread1);

    rt_thread_init(&thread2,
                   "thread2",
                   thread2_entry,
                   RT_NULL,
                   &thread2_stack[0],
                   sizeof(thread2_stack), 24, 5);
    rt_thread_startup(&thread2);

    return 0;
}

/* Export to msh command list */
MSH_CMD_EXPORT(msgq_sample, msgq sample);

Run result:

Simple analysis:
The sending and receiving addresses are different, that is, the message is taken out of the message pool when it is taken out. At the time of sending, the information is copied to the message pool being sent. By contrast, while a message is sent directly from the original message (with the same address), a message queue is really a way to copy the original content. This solves the problem of memory leaks when mailboxes cannot send messages within an interrupt (malloc cannot be used inside an interrupt, unless the local variable is constrained by a static, and the local variable is not constrained by a variable that can be easily modified).

3. Signals

Operation mode

A signal is a soft interrupt, which installs a signal to a thread. Other threads or interrupts can activate the thread by sending a signal (soft interrupt). If the current thread is running and the thread itself sends an interrupt signal, a new stack is opened to respond to soft interrupts.

contact

Soft interrupts are very similar to interrupts in that they do not know when the signal is coming and then execute the interrupt. However, this soft interrupt can only interrupt threads with lower priority than set. The system default priority is level 10. Threads with higher priority than this install interrupt cannot be interrupted. If the priority of installing a soft interrupt on its own thread is higher than that of a soft interrupt, the soft interrupt can only be executed until the thread on which the signal is installed suspends. It can be understood that soft interrupt is also a thread with priority level 10. This soft interrupt is slightly different from the thread. The variable of soft interrupt is not saved in the stack, that is, the execution like interrupt.

When using soft interrupts, it is important to note that the thread stack with signals installed needs to be enlarged.

routine

/*
 * Program List: Signal Routines
 *
 * This example creates a thread, the thread installs the signal, and then signals the thread.
 *
 */
#include <rtthread.h>

#define THREAD_PRIORITY         24
#define THREAD_STACK_SIZE       512
#define THREAD_TIMESLICE        5

static rt_thread_t tid1 = RT_NULL;

/* Signal Processing Function for Thread 1 */
void thread1_signal_handler(int sig)
{
    rt_kprintf("thread1_signal_handler  %d\n", sig);
	  rt_kprintf("thread1_signal_handler thread1 tid2->current_priority %d\n", tid1->current_priority);
}

/* Thread 1 entry function */
static void thread1_entry(void *parameter)
{
    int cnt = 0;
	    /* Installation signal */
    rt_signal_install(SIGUSR1, thread1_signal_handler);
    rt_signal_unmask(SIGUSR1);
    while (1)
    {
        /* Thread 1 runs with low priority and keeps printing count values */
        rt_kprintf("thread1 count : %d\n", cnt);
				rt_kprintf("thread1 tid1->current_priority %d\n", tid1->current_priority);
        cnt++;
        rt_thread_mdelay(100);
    }
}
static char thread2_stack[1024];
static struct rt_thread thread2;

/* Thread 2 Entry */
static void thread2_entry(void *param)
{
    static rt_uint32_t count = 0;
			/* Send signal SIGUSR1 to thread 1 */	
		rt_thread_kill(tid1, SIGUSR1);
		while(1)
		{
							count++;
			if(count<=5)
			{
				rt_kprintf("thread2 is ruing count is: %d\n", count);

			}
			if(count > 5 )	break;
		}
    rt_kprintf("thread2 exit\n");
    /* Thread 2 will also be automatically deleted after running
    (Thread control blocks and thread stacks are still released in idle threads) */
}

/* Initialization of signal examples */
int signal_sample(void)
{
    /* Create Thread 1 */
    tid1 = rt_thread_create("thread1",
                            thread1_entry, RT_NULL,
                            THREAD_STACK_SIZE,
                            THREAD_PRIORITY, THREAD_TIMESLICE);
    
    if (tid1 != RT_NULL)       
        rt_thread_startup(tid1);
		
 /* Initialize thread 2 with name thread2 and entry thread2_entry */
    rt_thread_init(&thread2,
                   "thread2",
                   thread2_entry,
                   RT_NULL,
                   &thread2_stack[0],
                   sizeof(thread2_stack),
                   9, 10);
    rt_thread_startup(&thread2);
									 

    return 0;
}

/* Export to msh command list */
MSH_CMD_EXPORT(signal_sample, signal sample);

Run result:

5. Contact

After learning the basic thread communication and synchronization, you can build the relationship between them, find the commonalities and characteristics, and make them easy to understand and use.

Communication between threads

The most obvious link is between the message and the message queue.

Same:
Macroscopically, they work the same way, which means that message queues are messages of variable length.

Difference:
Message queues store messages as a means of copying content, while mailboxes send content directly without copying it. Message queuing does not take into account content leaks, but, accordingly, message queuing costs more than mailboxes and is less efficient than mailboxes.

Next is the relationship between soft breaks and breaks.

Same:
They work the same way, they are all interruptions. Used to deal with uncertain events.

Difference:
Soft interrupts are special threads that are scheduled as threads are implemented. Interrupts are implemented as interrupts on different chips and are not scheduled, but interrupts are managed by these classifications of interrupt priority.

Links between thread communication and synchronization


This is another picture, so it is really important to remember and understand it. The five states of a thread should always be kept in mind.

Same:
Can be used for synchronization. As long as the content of the communication is ignored and only used to receive signals, it can be used as inter-thread synchronization, but it is a waste of resources and cannot achieve mutex, which prevents priority flipping. For example, the consumer producer problem, where a producer directly produces a product and then sends it out via a message queue, solves the problem of access to the critical zone and no longer needs a lock. The reason they can share this is that they activate threads and suspend their implementation in the same way between them, and the way threads queue first is also set up, so they can be replaced (see figure above, five ways of threads). In some cases, timers can also be replaced by threads, which can be scheduled by suspending the time of a high-priority thread.

Difference:
Communication, including the contents of communication, enables the function of communication and synchronization. In fact, a practical semaphore can also achieve such a function, setting the global structure as the communication data block, when this data block is completed, you can send a signal to synchronize the threads. To achieve this function, communication does not need to construct a global structure. Instead, it can send the corresponding data block or pointer directly to avoid the negative impact of too many global variables.

summary

The main RTOS programming methods have been described, leaving a little bit of memory management and interrupt management. Memory management is the programming idea of malloc and free allocation heap, interrupt is interrupt lock (a way of critical protection) and how the upper and lower half of interrupt are handled. Both of these programming methods and programming considerations will be introduced in the next article, and the Kernel Quick Start chapter will end.

Keywords: C stm32 RTOS RTT

Added by switchdoc on Sat, 12 Feb 2022 05:18:34 +0200