zephyr/doc/kernel_v2/data_passing/mailboxes.rst

634 lines
25 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. 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. There are two forms of message data:
* A **message buffer** is an area of memory provided by the thread
that sends or receives the message. An array or structure variable
can often be used for this purpose.
* A **message block** is an area of memory allocated from a memory pool.
A message may *not* have both a message buffer and a message block.
A message that has neither form of message data is called an **empty message**.
.. note::
A message whose message buffer or memory block 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 it 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:type:`struct k_mbox`.
It must then be initialized by calling :cpp: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:type:`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 or message block 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 :c:macro:`NULL`
when sending a memory block, or when sending an empty message.
Leave this field uninitialized when receiving a message.
*tx_block*
The descriptor for the sending thread's memory block. Set tx_block.pool_id
to :c:macro:`NULL` when sending an empty message. Leave this field
uninitialized when sending a message buffer, or 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 once the message is received.
Sending a Message
=================
A thread sends a message by first creating its message data, if any.
A message buffer is typically used when the data volume is small,
and the cost of copying the data is less than the cost of allocating
and freeing a message block.
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.pool_id = 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_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);
}
}
}
Sending Data Using a Message Block
----------------------------------
This code uses a mailbox to send asynchronous messages. A semaphore is used
to hold off the sending of a new message until the previous message
has been consumed, so that a backlog of messages doesn't build up
when the consuming thread is unable to keep up.
The message data is stored in a memory block obtained from ``TXPOOL``,
thereby eliminating unneeded data copying when exchanging large messages.
.. code-block:: c
/* define a semaphore, indicating that no message has been sent */
K_SEM_DEFINE(my_sem, 1, 1);
void producer_thread(void)
{
struct k_mbox_msg send_msg;
volatile char *hw_buffer;
while (1) {
/* allocate a memory block to hold the message data */
k_mem_pool_alloc(&send_msg.tx_block, TXPOOL, 4096, K_FOREVER);
/* keep overwriting the hardware-generated data in the block */
/* until the previous message has been received by the consumer */
do {
memcpy(send_msg.tx_block.pointer_to_data, hw_buffer, 4096);
} while (k_sem_take(&my_sem, K_NO_WAIT) != 0);
/* finish preparing to send message */
send_msg.size = 4096;
send_msg.rx_target_thread = K_ANY;
/* send message containing most current data and loop around */
k_mbox_async_put(&my_mailbox, &send_msg, MY_SEMA);
}
}
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's
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 or to have it placed in a message
block. A message buffer is typically used when the volume of data
involved is small, and the cost of copying the data is less than the cost
of allocating and freeing a memory pool block.
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 :c:macro:`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.
.. note::
This technique can be used when the message data is actually located
in a memory block supplied by the sending thread. The mailbox copies
the data into the message buffer specified by the receiving thread, then
frees the meessage block back to its memory pool. This allows
a receiving thread to retrieve message data without having to know
whether the data was sent using a message buffer or a message block.
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 :c:macro:`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 :c:macro:`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.
.. note::
This technique can be used when the message data is actually located
in a memory block supplied by the sending thread. The mailbox copies
the data into the message buffer specified by the receiving thread, then
frees the message block back to its memory pool. This allows
a receiving thread to retrieve message data without having to know
whether the data was sent using a message buffer or a message block.
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);
}
}
}
Retrieving Data Later Using a Message Block
-------------------------------------------
A receiving thread may choose to retrieve message data into a memory block,
rather than a message buffer. This is done in much the same way as retrieving
data subsequently into a message buffer --- the receiving thread first
receives the message without its data, then retrieves the data by calling
:c:func:`k_mbox_data_block_get()`. The mailbox fills in the block descriptor
supplied by the receiving thread, allowing the thread to access the data.
The mailbox also deletes the received message, since data retrieval
has been completed. The receiving thread is then responsible for freeing
the message block back to the memory pool when the data is no longer needed.
This technique is best suited for applications where the message data has
been sent using a memory block.
.. note::
This technique can be used when the message data is located in a message
buffer supplied by the sending thread. The mailbox automatically allocates
a memory block and copies the message data into it. However, this is much
less efficient than simply retrieving the data into a message buffer
supplied by the receiving thread. In addition, the receiving thread
must be designed to handle cases where the data retrieval operation fails
because the mailbox cannot allocate a suitable message block from the memory
pool. If such cases are possible, the receiving thread must either try
retrieving the data at a later time or instruct the mailbox to delete
the message without retrieving the data.
The following code uses a mailbox to receive messages sent using a memory block,
thereby eliminating unneeded data copying when processing a large message.
(The messages may be sent synchronously or asynchronously.)
.. code-block:: c
void consumer_thread(void)
{
struct k_mbox_msg recv_msg;
struct k_mem_block recv_block;
int total;
char *data_ptr;
int i;
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 as a memory block and discard message */
k_mbox_data_block_get(&recv_msg, RXPOOL, &recv_block, K_FOREVER);
/* compute sum of all message bytes in memory block */
total = 0;
data_ptr = (char *)(recv_block.pointer_to_data);
for (i = 0; i < recv_msg.size; i++) {
total += data_ptr++;
}
/* release memory block containing data */
k_mem_pool_free(&recv_block);
}
}
.. note::
An incoming message that was sent using a message buffer is also processed
correctly by this algorithm, since the mailbox automatically creates
a memory block containing the message data using ``RXPOOL``. However,
the performance benefit of using the memory block approach is lost.
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:
* :option:`CONFIG_NUM_MBOX_ASYNC_MSGS`
APIs
****
The following APIs for a mailbox are provided by :file:`kernel.h`:
* :cpp:func:`k_mbox_put()`
* :cpp:func:`k_mbox_async_put()`
* :cpp:func:`k_mbox_get()`
* :cpp:func:`k_mbox_data_get()`
* :cpp:func:`k_mbox_data_block_get()`