486 lines
19 KiB
ReStructuredText
486 lines
19 KiB
ReStructuredText
.. _mailboxes_v2:
|
|
|
|
Mailboxes
|
|
#########
|
|
|
|
A :dfn:`mailbox` is a kernel object that provides enhanced message queue
|
|
capabilities that go beyond the capabilities of a message queue object.
|
|
A mailbox allows threads to send and receive messages of any size
|
|
synchronously or asynchronously.
|
|
|
|
.. contents::
|
|
:local:
|
|
:depth: 2
|
|
|
|
Concepts
|
|
********
|
|
|
|
Any number of mailboxes can be defined (limited only by available RAM). Each
|
|
mailbox is referenced by its memory address.
|
|
|
|
A mailbox has the following key properties:
|
|
|
|
* A **send queue** of messages that have been sent but not yet received.
|
|
|
|
* A **receive queue** of threads that are waiting to receive a message.
|
|
|
|
A mailbox must be initialized before it can be used. This sets both of its
|
|
queues to empty.
|
|
|
|
A mailbox allows threads, but not ISRs, to exchange messages.
|
|
A thread that sends a message is known as the **sending thread**,
|
|
while a thread that receives the message is known as the **receiving thread**.
|
|
Each message may be received by only one thread (i.e. point-to-multipoint and
|
|
broadcast messaging is not supported).
|
|
|
|
Messages exchanged using a mailbox are handled non-anonymously,
|
|
allowing both threads participating in an exchange to know
|
|
(and even specify) the identity of the other thread.
|
|
|
|
Message Format
|
|
==============
|
|
|
|
A **message descriptor** is a data structure that specifies where a message's
|
|
data is located, and how the message is to be handled by the mailbox.
|
|
Both the sending thread and the receiving thread supply a message descriptor
|
|
when accessing a mailbox. The mailbox uses the message descriptors to perform
|
|
a message exchange between compatible sending and receiving threads.
|
|
The mailbox also updates certain message descriptor fields during the exchange,
|
|
allowing both threads to know what has occurred.
|
|
|
|
A mailbox message contains zero or more bytes of **message data**.
|
|
The size and format of the message data is application-defined, and can vary
|
|
from one message to the next.
|
|
|
|
A **message buffer** is an area of memory provided by the thread that sends or
|
|
receives the message data. An array or structure variable can often be used for
|
|
this purpose.
|
|
|
|
A message that has neither form of message data is called an **empty message**.
|
|
|
|
.. note::
|
|
A message whose message buffer exists, but contains zero bytes of actual
|
|
data, is *not* an empty message.
|
|
|
|
Message Lifecycle
|
|
=================
|
|
|
|
The life cycle of a message is straightforward. A message is created when
|
|
it is given to a mailbox by the sending thread. The message is then owned
|
|
by the mailbox until it is given to a receiving thread. The receiving thread
|
|
may retrieve the message data when it receives the message from the mailbox,
|
|
or it may perform data retrieval during a second, subsequent mailbox operation.
|
|
Only when data retrieval has occurred is the message deleted by the mailbox.
|
|
|
|
Thread Compatibility
|
|
====================
|
|
|
|
A sending thread can specify the address of the thread to which the message
|
|
is sent, or send it to any thread by specifying :c:macro:`K_ANY`.
|
|
Likewise, a receiving thread can specify the address of the thread from which
|
|
it wishes to receive a message, or it can receive a message from any thread
|
|
by specifying :c:macro:`K_ANY`.
|
|
A message is exchanged only when the requirements of both the sending thread
|
|
and receiving thread are satisfied; such threads are said to be **compatible**.
|
|
|
|
For example, if thread A sends a message to thread B (and only thread B)
|
|
it will be received by thread B if thread B tries to receive a message
|
|
from thread A or if thread B tries to receive from any thread.
|
|
The exchange will not occur if thread B tries to receive a message
|
|
from thread C. The message can never be received by thread C,
|
|
even if it tries to receive a message from thread A (or from any thread).
|
|
|
|
Message Flow Control
|
|
====================
|
|
|
|
Mailbox messages can be exchanged **synchronously** or **asynchronously**.
|
|
In a synchronous exchange, the sending thread blocks until the message
|
|
has been fully processed by the receiving thread. In an asynchronous exchange,
|
|
the sending thread does not wait until the message has been received
|
|
by another thread before continuing; this allows the sending thread to do
|
|
other work (such as gather data that will be used in the next message)
|
|
*before* the message is given to a receiving thread and fully processed.
|
|
The technique used for a given message exchange is determined
|
|
by the sending thread.
|
|
|
|
The synchronous exchange technique provides an implicit form of flow control,
|
|
preventing a sending thread from generating messages faster than they can be
|
|
consumed by receiving threads. The asynchronous exchange technique provides an
|
|
explicit form of flow control, which allows a sending thread to determine
|
|
if a previously sent message still exists before sending a subsequent message.
|
|
|
|
Implementation
|
|
**************
|
|
|
|
Defining a Mailbox
|
|
==================
|
|
|
|
A mailbox is defined using a variable of type :c:struct:`k_mbox`.
|
|
It must then be initialized by calling :c:func:`k_mbox_init`.
|
|
|
|
The following code defines and initializes an empty mailbox.
|
|
|
|
.. code-block:: c
|
|
|
|
struct k_mbox my_mailbox;
|
|
|
|
k_mbox_init(&my_mailbox);
|
|
|
|
Alternatively, a mailbox can be defined and initialized at compile time
|
|
by calling :c:macro:`K_MBOX_DEFINE`.
|
|
|
|
The following code has the same effect as the code segment above.
|
|
|
|
.. code-block:: c
|
|
|
|
K_MBOX_DEFINE(my_mailbox);
|
|
|
|
Message Descriptors
|
|
===================
|
|
|
|
A message descriptor is a structure of type :c:struct:`k_mbox_msg`.
|
|
Only the fields listed below should be used; any other fields are for
|
|
internal mailbox use only.
|
|
|
|
*info*
|
|
A 32-bit value that is exchanged by the message sender and receiver,
|
|
and whose meaning is defined by the application. This exchange is
|
|
bi-directional, allowing the sender to pass a value to the receiver
|
|
during any message exchange, and allowing the receiver to pass a value
|
|
to the sender during a synchronous message exchange.
|
|
|
|
*size*
|
|
The message data size, in bytes. Set it to zero when sending an empty
|
|
message, or when sending a message buffer with no actual data. When
|
|
receiving a message, set it to the maximum amount of data desired, or to
|
|
zero if the message data is not wanted. The mailbox updates this field with
|
|
the actual number of data bytes exchanged once the message is received.
|
|
|
|
*tx_data*
|
|
A pointer to the sending thread's message buffer. Set it to ``NULL``
|
|
when sending an empty message. Leave this field uninitialized when
|
|
receiving a message.
|
|
|
|
*tx_target_thread*
|
|
The address of the desired receiving thread. Set it to :c:macro:`K_ANY`
|
|
to allow any thread to receive the message. Leave this field uninitialized
|
|
when receiving a message. The mailbox updates this field with
|
|
the actual receiver's address once the message is received.
|
|
|
|
*rx_source_thread*
|
|
The address of the desired sending thread. Set it to :c:macro:`K_ANY`
|
|
to receive a message sent by any thread. Leave this field uninitialized
|
|
when sending a message. The mailbox updates this field
|
|
with the actual sender's address when the message is put into
|
|
the mailbox.
|
|
|
|
Sending a Message
|
|
=================
|
|
|
|
A thread sends a message by first creating its message data, if any.
|
|
|
|
Next, the sending thread creates a message descriptor that characterizes
|
|
the message to be sent, as described in the previous section.
|
|
|
|
Finally, the sending thread calls a mailbox send API to initiate the
|
|
message exchange. The message is immediately given to a compatible receiving
|
|
thread, if one is currently waiting. Otherwise, the message is added
|
|
to the mailbox's send queue.
|
|
|
|
Any number of messages may exist simultaneously on a send queue.
|
|
The messages in the send queue are sorted according to the priority
|
|
of the sending thread. Messages of equal priority are sorted so that
|
|
the oldest message can be received first.
|
|
|
|
For a synchronous send operation, the operation normally completes when a
|
|
receiving thread has both received the message and retrieved the message data.
|
|
If the message is not received before the waiting period specified by the
|
|
sending thread is reached, the message is removed from the mailbox's send queue
|
|
and the send operation fails. When a send operation completes successfully
|
|
the sending thread can examine the message descriptor to determine
|
|
which thread received the message, how much data was exchanged,
|
|
and the application-defined info value supplied by the receiving thread.
|
|
|
|
.. note::
|
|
A synchronous send operation may block the sending thread indefinitely,
|
|
even when the thread specifies a maximum waiting period.
|
|
The waiting period only limits how long the mailbox waits
|
|
before the message is received by another thread. Once a message is received
|
|
there is *no* limit to the time the receiving thread may take to retrieve
|
|
the message data and unblock the sending thread.
|
|
|
|
For an asynchronous send operation, the operation always completes immediately.
|
|
This allows the sending thread to continue processing regardless of whether the
|
|
message is given to a receiving thread immediately or added to the send queue.
|
|
The sending thread may optionally specify a semaphore that the mailbox gives
|
|
when the message is deleted by the mailbox, for example, when the message
|
|
has been received and its data retrieved by a receiving thread.
|
|
The use of a semaphore allows the sending thread to easily implement
|
|
a flow control mechanism that ensures that the mailbox holds no more than
|
|
an application-specified number of messages from a sending thread
|
|
(or set of sending threads) at any point in time.
|
|
|
|
.. note::
|
|
A thread that sends a message asynchronously has no way to determine
|
|
which thread received the message, how much data was exchanged, or the
|
|
application-defined info value supplied by the receiving thread.
|
|
|
|
Sending an Empty Message
|
|
------------------------
|
|
|
|
This code uses a mailbox to synchronously pass 4 byte random values
|
|
to any consuming thread that wants one. The message "info" field is
|
|
large enough to carry the information being exchanged, so the data
|
|
portion of the message isn't used.
|
|
|
|
.. code-block:: c
|
|
|
|
void producer_thread(void)
|
|
{
|
|
struct k_mbox_msg send_msg;
|
|
|
|
while (1) {
|
|
|
|
/* generate random value to send */
|
|
uint32_t random_value = sys_rand32_get();
|
|
|
|
/* prepare to send empty message */
|
|
send_msg.info = random_value;
|
|
send_msg.size = 0;
|
|
send_msg.tx_data = NULL;
|
|
send_msg.tx_block.data = NULL;
|
|
send_msg.tx_target_thread = K_ANY;
|
|
|
|
/* send message and wait until a consumer receives it */
|
|
k_mbox_put(&my_mailbox, &send_msg, K_FOREVER);
|
|
}
|
|
}
|
|
|
|
Sending Data Using a Message Buffer
|
|
-----------------------------------
|
|
|
|
This code uses a mailbox to synchronously pass variable-sized requests
|
|
from a producing thread to any consuming thread that wants it.
|
|
The message "info" field is used to exchange information about
|
|
the maximum size message buffer that each thread can handle.
|
|
|
|
.. code-block:: c
|
|
|
|
void producer_thread(void)
|
|
{
|
|
char buffer[100];
|
|
int buffer_bytes_used;
|
|
|
|
struct k_mbox_msg send_msg;
|
|
|
|
while (1) {
|
|
|
|
/* generate data to send */
|
|
...
|
|
buffer_bytes_used = ... ;
|
|
memcpy(buffer, source, buffer_bytes_used);
|
|
|
|
/* prepare to send message */
|
|
send_msg.info = buffer_bytes_used;
|
|
send_msg.size = buffer_bytes_used;
|
|
send_msg.tx_data = buffer;
|
|
send_msg.tx_block.data = NULL;
|
|
send_msg.tx_target_thread = K_ANY;
|
|
|
|
/* send message and wait until a consumer receives it */
|
|
k_mbox_put(&my_mailbox, &send_msg, K_FOREVER);
|
|
|
|
/* info, size, and tx_target_thread fields have been updated */
|
|
|
|
/* verify that message data was fully received */
|
|
if (send_msg.size < buffer_bytes_used) {
|
|
printf("some message data dropped during transfer!");
|
|
printf("receiver only had room for %d bytes", send_msg.info);
|
|
}
|
|
}
|
|
}
|
|
|
|
Receiving a Message
|
|
===================
|
|
|
|
A thread receives a message by first creating a message descriptor that
|
|
characterizes the message it wants to receive. It then calls one of the
|
|
mailbox receive APIs. The mailbox searches its send queue and takes the message
|
|
from the first compatible thread it finds. If no compatible thread exists,
|
|
the receiving thread may choose to wait for one. If no compatible thread
|
|
appears before the waiting period specified by the receiving thread is reached,
|
|
the receive operation fails.
|
|
Once a receive operation completes successfully the receiving thread
|
|
can examine the message descriptor to determine which thread sent the message,
|
|
how much data was exchanged,
|
|
and the application-defined info value supplied by the sending thread.
|
|
|
|
Any number of receiving threads may wait simultaneously on a mailboxes'
|
|
receive queue. The threads are sorted according to their priority;
|
|
threads of equal priority are sorted so that the one that started waiting
|
|
first can receive a message first.
|
|
|
|
.. note::
|
|
Receiving threads do not always receive messages in a first in, first out
|
|
(FIFO) order, due to the thread compatibility constraints specified by the
|
|
message descriptors. For example, if thread A waits to receive a message
|
|
only from thread X and then thread B waits to receive a message from
|
|
thread Y, an incoming message from thread Y to any thread will be given
|
|
to thread B and thread A will continue to wait.
|
|
|
|
The receiving thread controls both the quantity of data it retrieves from an
|
|
incoming message and where the data ends up. The thread may choose to take
|
|
all of the data in the message, to take only the initial part of the data,
|
|
or to take no data at all. Similarly, the thread may choose to have the data
|
|
copied into a message buffer of its choice.
|
|
|
|
The following sections outline various approaches a receiving thread may use
|
|
when retrieving message data.
|
|
|
|
Retrieving Data at Receive Time
|
|
-------------------------------
|
|
|
|
The most straightforward way for a thread to retrieve message data is to
|
|
specify a message buffer when the message is received. The thread indicates
|
|
both the location of the message buffer (which must not be ``NULL``)
|
|
and its size.
|
|
|
|
The mailbox copies the message's data to the message buffer as part of the
|
|
receive operation. If the message buffer is not big enough to contain all of the
|
|
message's data, any uncopied data is lost. If the message is not big enough
|
|
to fill all of the buffer with data, the unused portion of the message buffer is
|
|
left unchanged. In all cases the mailbox updates the receiving thread's
|
|
message descriptor to indicate how many data bytes were copied (if any).
|
|
|
|
The immediate data retrieval technique is best suited for small messages
|
|
where the maximum size of a message is known in advance.
|
|
|
|
The following code uses a mailbox to process variable-sized requests from any
|
|
producing thread, using the immediate data retrieval technique. The message
|
|
"info" field is used to exchange information about the maximum size
|
|
message buffer that each thread can handle.
|
|
|
|
.. code-block:: c
|
|
|
|
void consumer_thread(void)
|
|
{
|
|
struct k_mbox_msg recv_msg;
|
|
char buffer[100];
|
|
|
|
int i;
|
|
int total;
|
|
|
|
while (1) {
|
|
/* prepare to receive message */
|
|
recv_msg.info = 100;
|
|
recv_msg.size = 100;
|
|
recv_msg.rx_source_thread = K_ANY;
|
|
|
|
/* get a data item, waiting as long as needed */
|
|
k_mbox_get(&my_mailbox, &recv_msg, buffer, K_FOREVER);
|
|
|
|
/* info, size, and rx_source_thread fields have been updated */
|
|
|
|
/* verify that message data was fully received */
|
|
if (recv_msg.info != recv_msg.size) {
|
|
printf("some message data dropped during transfer!");
|
|
printf("sender tried to send %d bytes", recv_msg.info);
|
|
}
|
|
|
|
/* compute sum of all message bytes (from 0 to 100 of them) */
|
|
total = 0;
|
|
for (i = 0; i < recv_msg.size; i++) {
|
|
total += buffer[i];
|
|
}
|
|
}
|
|
}
|
|
|
|
Retrieving Data Later Using a Message Buffer
|
|
--------------------------------------------
|
|
|
|
A receiving thread may choose to defer message data retrieval at the time
|
|
the message is received, so that it can retrieve the data into a message buffer
|
|
at a later time.
|
|
The thread does this by specifying a message buffer location of ``NULL``
|
|
and a size indicating the maximum amount of data it is willing to retrieve
|
|
later.
|
|
|
|
The mailbox does not copy any message data as part of the receive operation.
|
|
However, the mailbox still updates the receiving thread's message descriptor
|
|
to indicate how many data bytes are available for retrieval.
|
|
|
|
The receiving thread must then respond as follows:
|
|
|
|
* If the message descriptor size is zero, then either the sender's message
|
|
contained no data or the receiving thread did not want to receive any data.
|
|
The receiving thread does not need to take any further action, since
|
|
the mailbox has already completed data retrieval and deleted the message.
|
|
|
|
* If the message descriptor size is non-zero and the receiving thread still
|
|
wants to retrieve the data, the thread must call :c:func:`k_mbox_data_get`
|
|
and supply a message buffer large enough to hold the data. The mailbox copies
|
|
the data into the message buffer and deletes the message.
|
|
|
|
* If the message descriptor size is non-zero and the receiving thread does *not*
|
|
want to retrieve the data, the thread must call :c:func:`k_mbox_data_get`.
|
|
and specify a message buffer of ``NULL``. The mailbox deletes
|
|
the message without copying the data.
|
|
|
|
The subsequent data retrieval technique is suitable for applications where
|
|
immediate retrieval of message data is undesirable. For example, it can be
|
|
used when memory limitations make it impractical for the receiving thread to
|
|
always supply a message buffer capable of holding the largest possible
|
|
incoming message.
|
|
|
|
The following code uses a mailbox's deferred data retrieval mechanism
|
|
to get message data from a producing thread only if the message meets
|
|
certain criteria, thereby eliminating unneeded data copying. The message
|
|
"info" field supplied by the sender is used to classify the message.
|
|
|
|
.. code-block:: c
|
|
|
|
void consumer_thread(void)
|
|
{
|
|
struct k_mbox_msg recv_msg;
|
|
char buffer[10000];
|
|
|
|
while (1) {
|
|
/* prepare to receive message */
|
|
recv_msg.size = 10000;
|
|
recv_msg.rx_source_thread = K_ANY;
|
|
|
|
/* get message, but not its data */
|
|
k_mbox_get(&my_mailbox, &recv_msg, NULL, K_FOREVER);
|
|
|
|
/* get message data for only certain types of messages */
|
|
if (is_message_type_ok(recv_msg.info)) {
|
|
/* retrieve message data and delete the message */
|
|
k_mbox_data_get(&recv_msg, buffer);
|
|
|
|
/* process data in "buffer" */
|
|
...
|
|
} else {
|
|
/* ignore message data and delete the message */
|
|
k_mbox_data_get(&recv_msg, NULL);
|
|
}
|
|
}
|
|
}
|
|
|
|
Suggested Uses
|
|
**************
|
|
|
|
Use a mailbox to transfer data items between threads whenever the capabilities
|
|
of a message queue are insufficient.
|
|
|
|
Configuration Options
|
|
*********************
|
|
|
|
Related configuration options:
|
|
|
|
* :kconfig:option:`CONFIG_NUM_MBOX_ASYNC_MSGS`
|
|
|
|
API Reference
|
|
*************
|
|
|
|
.. doxygengroup:: mailbox_apis
|