Synchronization by sending and receiving messages
In Part 3, we explained the event flags and semaphores as inter-task synchronization.
Event flags and semaphores are very useful when multiple tasks work together to execute a process, or when you want to use exclusive control. However, there are times when you not only want to synchronize between tasks, but also want to be notified of information at the same time. For example, you may want to process in another task based on the results of processing in one task, or you may want to send and receive data (called “messages” in T-Kernel) between tasks without going through shared memory.
Such data transmission and reception between multiple tasks is called “communication”. Since T-Kernel provides message buffer and mailbox functions, etc., which will be explained later, T-Kernel allows you to develop efficient programs by using the most appropriate synchronization function according to your needs.
Message buffer
T-Kernel provides several functions that can send and receive messages, and message buffer is one of them. The message buffer is one of the synchronization and communication functions that allows to send and receive variable length messages.
The sender stores a message in the message buffer area for transmission and reception, and the receiver takes a message out of the message buffer area to achieve synchronization and communication.
If a message is not stored in the message buffer, a message will be waiting to be received. On the other hand, if a message is sent when there is not enough free space in the message buffer, the message will be waiting to be sent.
List 1 is an example of programming using the message buffer in such a way that “the process of Task A receives the result of the process doWorkB () to be executed in Task B and uses the result in doWorkA ()” (*1).
(*1) doWorkA and doWorkB shall perform the processing for each task and may generate a wait if necessary. Each task would perform a variety of processes in addition to doWorkA and doWorkB, but these have been omitted in the list for simplicity.
【Listing 1: Example of Programming Using the Message Buffer】
#include <basic.h> #include <tk/tkernel.h> #include <tm/tmonitor.h> IMPORT void doWorkA( W ); IMPORT W doWorkB( void ); ID mbfid; void taskA( INT stacd, VP exinf ) { INT msgsz; W rcv_msg; while(1){ msgsz = tk_rcv_mbf( mbfid, &rcv_msg, TMO_FEVR ); /* Waiting for the completion of the process of doWorkB () and receiving the result of that process. */ if( msgsz > 0 ) { doWorkA( rcv_msg ); /* Use the results received */ } } tk_ext_tsk(); } void taskB( INT stacd, VP exinf ) { W snd_msg; while(1){ snd_msg = doWorkB(); /* Processing to be done before doWorkA () */ tk_snd_mbf( mbfid, &snd_msg, sizeof(snd_msg), TMO_FEVR ); /* Notify Task A of the processing result of doWorkB () */ } tk_ext_tsk(); } EXPORT INT usermain( void ) /* Functions called by the initial task */ { T_CMBF cmbf = { NULL, TA_TFIFO, 0x10, 0x4 }; T_CTSK ctskA = { NULL, TA_HLNG|TA_RNG0, taskA, 1, 4*1024 }; T_CTSK ctskB = { NULL, TA_HLNG|TA_RNG0, taskB, 2, 4*1024 }; ID tskIdA; /* Task A Identifier */ ID tskIdB; /* Task B Identifier */ mbfid = tk_cre_mbf( &cmbf ); /* Create a message buffer */ tskIdA = tk_cre_tsk( &ctskA ); /* Generate Task A */ tk_sta_tsk( tskIdA, 0 ); /* Start executing Task A. */ tskIdB = tk_cre_tsk( &ctskB ); /* Generate Task B */ tk_sta_tsk( tskIdB, 0 ); /* Start execution of Task B. */ tk_slp_tsk( TMO_FEVR ); /* Transitioning to a wake-up call. */ return 0; }
tk_rcv_mbf
is the function to receive messages and tk_snd_mbf
is the function to send messages.
When the program is executed, task A starts executing before task A, but since tk_rcv_mbf
is inserted in line 17 (in task A), it waits for a message to be received and stops the process at this point. After that, task B is executed. When Task B executes tk_snd_mbf
on line 32 (after doWorkB
processing is completed), Task A with a high task priority (=1) is stopped by tk_rcv_mbf
(on T-Kernel This will return from the “waiting state”) and allow doWorkA
to be executed.
In addition, it is necessary to prepare the message buffer to be used beforehand on T-Kernel as well as the event flags, etc. This is called tk_cre_mbf
at line 46. This is tk_cre_mbf
(creation of message buffer) at line 46.
The synchronization methods shown here are just an example. For example, if you create a message buffer with a message buffer size of 0, you can use tk_snd_mbf
to wait for a message to be sent. In this case, the message is copied directly from the buffer prepared by the sender’s task to the buffer prepared by the receiver’s task.
Message buffers can be used to provide other functions for synchronization and communication depending on various dependencies besides the ones shown here.
Mailbox
There are multiple functions to send and receive messages, but there is also a mailbox as well as a message buffer.
Listing 2 shows an example of programming using a mailbox (*1).
(*1) doWorkA and doWorkB shall perform the processing for each task and may generate a wait if necessary. Each task would perform a variety of processes in addition to doWorkA and doWorkB, but these have been omitted in the list for simplicity.
【List 2: An example of programming using a mailbox】
#include <basic.h> #include <tk/tkernel.h> #include <tm/tmonitor.h> IMPORT void doWorkA( UH ); IMPORT UH doWorkB( void ); typedef struct { T_MSG msg; UH data; } UserMSG; ID mbxid; ID tskIdA; /* Task A Identifier */ ID tskIdB; /* Task B Identifier */ void taskA( INT stacd, VP exinf ) { ER ercd; UserMSG rcv_umsg; while(1){ ercd = tk_rcv_mbx( mbxid, (T_MSG **)&rcv_umsg, TMO_POL ); if( ercd == E_TMOUT ) { tk_wup_tsk( tskIdB ); } else { doWorkA( rcv_umgs.data ); /* Processing for Task A */ } } tk_ext_tsk(); } void taskB( INT stacd, VP exinf ) { UserMSG umsg[5]; INT cnt = 0; while(1){ umsg.data[cnt] = doWorkB(); /* Processing for Task B */ tk_snd_mbx( mbxid, (T_MSG *)&umsg[cnt] ); if( cnt != 4 ) { cnt++; } else { tk_slp_tsk( TMO_FEVR ); cnt = 0; } } tk_ext_tsk(); } EXPORT INT usermain( void ) /* Functions called by the initial task */ { T_CMBX cmbx = { NULL, TA_TFIFO|TA_MFIFO }; T_CTSK ctskA = { NULL, TA_HLNG|TA_RNG0, taskA, 2, 4*1024 }; T_CTSK ctskB = { NULL, TA_HLNG|TA_RNG0, taskB, 1, 4*1024 }; mbxid = tk_cre_mbx( &cmbx ); /* Generate a mailbox */ tskIdB = tk_cre_tsk( &ctskB ); /* Generate Task B */ tk_sta_tsk( tskIdB, 0 ); /* Start execution of Task B. */ tskIdA = tk_cre_tsk( &ctskA ); /* Generate Task A */ tk_sta_tsk( tskIdA, 0 ); /* Start executing Task A. */ tk_slp_tsk(TMO_FEVR); /* Transitioning to a wake-up call. */ return 0; }
tk_rcv_mbx
is for receiving messages and tk_snd_mbx
is for sending messages.
When you run the program, task B starts executing first. A message is created on line 40 in Task B and sent by tk_snd_mbx
on line 42. After sending the message five times, it waits in the waiting state for waking up and stops the process at this point. After that, Task A enters the execution state. Task A executes tk_rcv_mbx
on line 23 to receive the message sent by Task B. When there are no more messages sent, it requests a wakeup call to task B at line 26. Task B returns from the standstill state (“waiting state” in T-Kernel) caused by tk_slp_tsk
and starts sending messages again.
The mailbox, like the message buffer, needs to be prepared for this beforehand. This is tk_cre_mbx
(creation of mailboxes) on line 60, and T-Kernel allows multiple mailboxes to be created and used for different purposes.
An advantage of mailboxes over message buffers is that they do not require a message buffer area and can be implemented by dynamically allocating the minimum necessary memory area. Unlike message buffers, mailboxes do not copy messages, but rather report the top address of the message. This has the advantage of being faster than a mailbox when the message size is large. However, the message must not be altered or deleted after it has been sent until it has been received and processed by the receiver. If you modify a message before processing is finished, you must be careful because it may cause unintended behavior.
Normally, the sender allocates a memory area using the “memory pool function” (see Part 8), creates a message there, and then sends it to the mailbox. At the receiving end, the message is returned to the memory pool after it is used. By implementing it in this way, we can avoid the mistake that the receiver alters the message before using it.
Read
“もっと見る” カテゴリーなし
Mbed TLS overview and features
In this article, I'd like to discuss Mbed TLS, which I've touched on a few times in the past, Transport …
What is an “IoT device development platform”?
I started using Mbed because I wanted a microcontroller board that could connect natively to the Internet. At that time, …
Mbed OS overview and features
In this article, I would like to write about one of the components of Arm Mbed, and probably the most …