doc: Enhance information about microkernel mailboxes
Completely overhauls Kernel Primer section on mailboxes to fill in a host of omissions, correct errors, and improve readability. Change-Id: I7f27230f59a593f6b7f44f399363ba423156b43e Signed-off-by: Allan Stephens <allan.stephens@windriver.com>
This commit is contained in:
parent
5355aa8a76
commit
ed78a03da2
|
@ -3,31 +3,316 @@
|
|||
Mailboxes
|
||||
#########
|
||||
|
||||
Definition
|
||||
**********
|
||||
|
||||
A mailbox is defined in include :file:`/microkernel/mailbox.h`.
|
||||
Mailboxes are a flexible way to pass data and for tasks to exchange messages.
|
||||
|
||||
Function
|
||||
Concepts
|
||||
********
|
||||
|
||||
Each transfer within a mailbox can vary in size. The size of a data
|
||||
transfer is only limited by the available memory on the platform.
|
||||
Transmitted data is not buffered in the mailbox itself. Instead, the
|
||||
buffer is either allocated from a memory pool block, or in block of
|
||||
memory defined by the user.
|
||||
The microkernel's mailbox object type is an implementation of a traditional
|
||||
message queue.
|
||||
|
||||
Mailboxes can work synchronously and asynchronously. Asynchronous
|
||||
mailboxes require the sender to allocate a buffer from a memory pool
|
||||
block, while synchronous mailboxes will copy the sender data to the
|
||||
receiver buffer.
|
||||
A mailbox allows tasks to exchange messages. A task that sends a message
|
||||
is known as the *sending task*, while a task that receives the message
|
||||
is known as the *receiving task*. Messages may not be sent or received
|
||||
by fibers or ISRs, nor may a given message be received by more than one task
|
||||
(i.e. point-to-multipoint messaging is not supported).
|
||||
|
||||
A mailbox has a queue of messages that have been sent, but not yet received.
|
||||
The messages in the queue are sorted by priority, allowing a higher priority
|
||||
message to be received before a lower priority message that was sent earlier.
|
||||
Messages of equal priority are handled in a first in, first out manner.
|
||||
|
||||
Any number of mailboxes can be defined in a microkernel system. Each mailbox
|
||||
has a name that uniquely identifies it. A mailbox does not limit the number
|
||||
of messages it can queue, nor does it place limits on the size of the messages
|
||||
it handles.
|
||||
|
||||
The content of a message is stored in an array of bytes, called the
|
||||
*message data*. The size and format of the message data is application-defined,
|
||||
and can vary from one message to the next. Message data may be stored
|
||||
in a buffer provided by the task that sends or receives the message,
|
||||
or in a memory pool block. The message data portion of a message is optional;
|
||||
a message without any message data is called an *empty message*.
|
||||
|
||||
The lifecycle of a message is fairly simple. A message is created when it
|
||||
is given to a mailbox by the sending task. The message is then owned
|
||||
by the mailbox until it is given to a receiving task. The receiving task 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 been performed is the message deleted
|
||||
by the mailbox.
|
||||
|
||||
Messages can be exchanged non-anonymously or anonymously. A sending task
|
||||
can specify the name of the task to which the message is being sent,
|
||||
or it can specify that any task may receive the message. Likewise, a receiving
|
||||
task can specify the name of the task it wishes to receive a message from,
|
||||
or it can specify that it is willing to receive a message from any task.
|
||||
A message is exchanged only if the requirements of both the sending task and
|
||||
receiving task can both be satisfied; such tasks are said to be *compatible*.
|
||||
|
||||
For example, if task A sends a message to task B it will be received by task B
|
||||
if the latter tries to receive a message from task A (or from any task), but
|
||||
not if task B tries to receive a message from task C. The message can never
|
||||
be received by task C, even if it is trying to receive a message from task A
|
||||
(or from any task).
|
||||
|
||||
Messages can be exchanged sychronously or asynchronously. In a synchronous
|
||||
exchange the sending task blocks until the message has been fully processed
|
||||
by the receiving task. In an asynchronous exchange the sending task does not
|
||||
wait until the message has been received by another task before continuing,
|
||||
thereby allowing the task to do other work (such as gathering data that will be
|
||||
used in the next message) before the message is given to a receiving task and
|
||||
fully processed. The technique used for a given message exchange is determined
|
||||
by the sending task.
|
||||
|
||||
The synchronous exchange technique provides an inherent form of flow control,
|
||||
preventing a sending task from generating messages faster than they can be
|
||||
consumed by receiving tasks. The asynchronous exchange technique provides an
|
||||
optional form of flow control, which allows a sending task to determine
|
||||
if a previously sent message still exists before sending a subsequent message.
|
||||
|
||||
Message Descriptor
|
||||
==================
|
||||
|
||||
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 task and the receiving task pass a message descriptor to the mailbox
|
||||
when accessing a mailbox. The mailbox uses both message descriptors to perform
|
||||
a message exchange between compatible sending and receiving tasks. The mailbox
|
||||
also updates some fields of the descriptors during the exchange to allow both
|
||||
tasks to know what occurred.
|
||||
|
||||
A message descriptor is a structure of type :c:type:`struct k_msg`. The fields
|
||||
listed below are available for application use; all other fields are for
|
||||
kernel use only.
|
||||
|
||||
:c:option:`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.
|
||||
|
||||
:c:option:`size`
|
||||
The message data size, in bytes. Set it to zero when sending an empty
|
||||
message, or when discarding the message data of a received message.
|
||||
The mailbox updates this field with the actual number of data bytes
|
||||
exchanged once the message is received.
|
||||
|
||||
:c:option:`tx_data`
|
||||
A pointer to the sending task's message data buffer. Set it to
|
||||
:c:macro:`NULL` when sending a memory pool block, or when sending
|
||||
an empty message. (Not used when receiving a message.)
|
||||
|
||||
:c:option:`tx_block`
|
||||
The descriptor for the memory pool block containing the sending task's
|
||||
message data. (Not used when sending a message data buffer,
|
||||
or when sending an empty message. Not used when receiving a message.)
|
||||
|
||||
:c:option:`rx_data`
|
||||
A pointer to the receiving task's message data buffer. Set it to
|
||||
:c:macro:`NULL` when the message's data is not wanted, or when it will be
|
||||
retrieved by a subsequent mailbox operation. (Not used when sending
|
||||
a message.)
|
||||
|
||||
:c:option:`tx_task`
|
||||
The name of the sending task. Set it to :c:macro:`ANYTASK` to receive
|
||||
a message sent by any task. The mailbox updates this field with the
|
||||
actual sender's name once the message is received. (Not used when
|
||||
sending a message.)
|
||||
|
||||
:c:option:`rx_task`
|
||||
The name of the receiving task. Set it to :c:macro:`ANYTASK` to allow
|
||||
any task to receive the message. The mailbox updates this field with
|
||||
the actual receiver's name once the message is received, but only if
|
||||
the message is sent synchronously. (Not used when receiving a message.)
|
||||
|
||||
Sending a Message
|
||||
=================
|
||||
|
||||
A task sends a message by first creating the message data to be sent (if any).
|
||||
The data may be placed in a message buffer---such as an array or structure
|
||||
variable---whose contents are copied to an area supplied by the receiving task
|
||||
during the message exchange. Alternatively, the data may be placed in a block
|
||||
allocated from a memory pool, which is handed off to the receiving task
|
||||
during the exchange. A message buffer is typically used when the amount 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. A memory pool block *must*
|
||||
be used when a non-empty message is sent asynchronously.
|
||||
|
||||
Next, the task creates a message descriptor that characterizes the message
|
||||
to be sent, as described in the previous section.
|
||||
|
||||
Finally, the task calls one of the mailbox send APIs to initiate the message
|
||||
exchange. The message is immediately given to a compatible receiving task,
|
||||
if one is currently waiting for a message. Otherwise, the message is added
|
||||
to the mailbox's queue of messages, according to the priority specified by
|
||||
the sending task. Typically, a sending task sets the message priority to
|
||||
its own task priority level, allowing messages sent by higher priority tasks
|
||||
to take precedence over those sent by lower priority tasks.
|
||||
|
||||
For a synchronous send operation the operation normally completes when a
|
||||
receiving task has both received the message and retrieved the message data.
|
||||
If the message is not received before the waiting period specified by the
|
||||
sending task is reached, the message is removed from the mailbox's queue
|
||||
and the sending task continues processing. When a send operation completes
|
||||
successfully the sending task can examine the message descriptor to determine
|
||||
which task received the message and how much data was exchanged, as well as
|
||||
the application-defined info value supplied by the receiving task.
|
||||
|
||||
.. note::
|
||||
A synchronous send operation may block the sending task indefinitely---even
|
||||
when the task specifies a maximum waiting period---since the waiting period
|
||||
only limits how long the mailbox waits before the message is received
|
||||
by another task. Once a message is received there is no limit to the time
|
||||
the receiving task may take to retrieve the message data and unblock
|
||||
the sending task.
|
||||
|
||||
For an asynchronous send operation the operation always completes immediately.
|
||||
This allows the sending task to continue processing regardless of whether the
|
||||
message is immediately given to a receiving task or is queued by the mailbox.
|
||||
The sending task may optionally specify a semaphore that the mailbox gives
|
||||
when the message is deleted by the mailbox (i.e. when the message has been
|
||||
received and its data retrieved by a receiving task). The use of a semaphore
|
||||
allows the sending task 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 task (or set of sending tasks) at any point in time.
|
||||
|
||||
Receiving a Message
|
||||
===================
|
||||
|
||||
A task 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 queue of messages
|
||||
and takes the first one it finds that satisfies both the sending and
|
||||
receiving tasks' message descriptor criteria. If no compatible message
|
||||
exists, the receiving task may choose to wait for one to be sent. If no
|
||||
compatible message appears before the waiting period specified
|
||||
by the receiving task is reached, the receive operation fails and
|
||||
the receiving task continues processing. Once a receive operation completes
|
||||
successfully the receiving task can examine the message descriptor
|
||||
to determine which task sent the message, how much data was exchanged,
|
||||
and the application-defined info value supplied by the sending task.
|
||||
|
||||
The receiving task controls both the amount of data it retrieves from an
|
||||
incoming message and where the data ends up. The task 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 task may choose to have the data
|
||||
copied into a buffer area of its choice or to have it placed in a memory
|
||||
pool block. A message buffer is typically used when the amount 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 task may use
|
||||
when retrieving message data.
|
||||
|
||||
Retrieving Data Immediately into a Buffer
|
||||
-----------------------------------------
|
||||
|
||||
The most straightforward way for a task to retrieve message data is to
|
||||
specify a buffer when the message is received. The task indicates
|
||||
both the location of the buffer (which must not be :c:macro:`NULL`)
|
||||
and its size (which must be greater than zero).
|
||||
|
||||
The mailbox copies the message's data to the buffer as part of the
|
||||
receive operation. If the 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 buffer is
|
||||
left unchanged. In all cases the mailbox updates the receiving task's
|
||||
message descriptor to indicate how many data bytes were copied (if any).
|
||||
|
||||
The immediate data retrieval technique is best suited for applications involving
|
||||
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 pool block supplied by the sending task. The mailbox copies
|
||||
the data into the buffer specified by the receiving task, then automatically
|
||||
frees the block back to its memory pool. This allows a receiving task
|
||||
to retrieve message data without having to know whether the data
|
||||
was sent using a buffer or a block.
|
||||
|
||||
Retrieving Data Subsequently into a Buffer
|
||||
------------------------------------------
|
||||
|
||||
A receiving task may choose to retrieve no message data at the time the message
|
||||
is received, so that it can retrieve the data into a buffer at a later time.
|
||||
The task does this by specifying a buffer location of :c:macro:`NULL`
|
||||
and a size indicating the maximum amount of data it is willing to retrieve
|
||||
later (which must be greater than or equal to zero).
|
||||
|
||||
The mailbox does not copy any message data as part of the receive operation.
|
||||
However, the mailbox still updates the receiving task's message descriptor
|
||||
to indicate how many data bytes are available for retrieval.
|
||||
|
||||
The receiving task must then respond as follows:
|
||||
|
||||
* If the message descriptor size is zero, then either the received message is
|
||||
an empty message or the receiving task did not want to receive any
|
||||
message data. The receiving task 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 task still
|
||||
wants to retrieve the data, the task must supply a buffer large enough
|
||||
to hold the data. The task first sets the message descriptor's
|
||||
:c:option:`rx_data` field to the address of the buffer, then calls
|
||||
:c:func:`task_mbox_data_get()`. This instructs the mailbox to copy the data
|
||||
and delete the message.
|
||||
|
||||
* If the message descriptor size is non-zero and the receiving task does *not*
|
||||
want to retrieve the data, the task sets the message descriptor's
|
||||
:c:option:`size` field to zero and calls :c:func:`task_mbox_data_get()`.
|
||||
This instructs the mailbox to delete 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 task to
|
||||
always supply a 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 pool block supplied by the sending task. The mailbox copies
|
||||
the data into the buffer specified by the receiving task, then automatically
|
||||
frees the block back to its memory pool. This allows a receiving task
|
||||
to retrieve message data without having to know whether the data
|
||||
was sent using a buffer or a block.
|
||||
|
||||
Retrieving Data Subsequently into a Block
|
||||
-----------------------------------------
|
||||
|
||||
A receiving task may choose to retrieve message data into a memory pool block,
|
||||
rather than a buffer area of its choice. This is done in much the same way
|
||||
as retrieving data subsequently into a buffer---the receiving task first
|
||||
receives the message without its data, then retrieves the data by calling
|
||||
:c:func:`task_mbox_data_block_get()`. The latter call fills in the block
|
||||
descriptor supplied by the receiving task, allowing the task to access the data.
|
||||
This call also causes the mailbox to delete the received message, since
|
||||
data retrieval has been completed. The receiving task is then responsible
|
||||
for freeing the 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 pool block, either because a large amount of data
|
||||
is involved or because the message was sent asynchronously.
|
||||
|
||||
.. note::
|
||||
This technique can be used when the message data is located in a buffer
|
||||
supplied by the sending task. The mailbox automatically allocates a memory
|
||||
pool block and copies the message data into it. However, this is much less
|
||||
efficient than simply retrieving the data into a buffer supplied by the
|
||||
receiving task. In addition, the receiving task must be designed to handle
|
||||
cases where the data retrieval operation fails because the mailbox cannot
|
||||
allocate a suitable block from the memory pool. If such cases are possible,
|
||||
the receiving task can call :c:func:`task_mbox_data_block_get_wait()` or
|
||||
:c:func:`task_mbox_data_block_get_wait_timeout()` to permit the task to wait
|
||||
until a suitable block can be allocated. Alternatively, the task can use
|
||||
:c:func:`task_mbox_data_get()` to inform the mailbox that it no longer wishes
|
||||
to receive the data at all, allowing the mailbox to release the message.
|
||||
|
||||
Purpose
|
||||
*******
|
||||
|
||||
Use a mailbox to transfer data items between tasks whenever the capabilities
|
||||
of a FIFO are insufficient.
|
||||
|
||||
The transfer contains one word of information that identifies either the
|
||||
sender, or the receiver, or both. The sender task specifies the task it
|
||||
wants to send to. The receiver task specifies the task it wants to
|
||||
receive from. Then the mailbox checks the identity of the sender and
|
||||
receiver tasks before passing the data.
|
||||
|
||||
Usage
|
||||
*****
|
||||
|
@ -84,8 +369,8 @@ To utilize this mailbox from a different source file use the following syntax:
|
|||
|
||||
extern const kmbox_t PRIV_MBX;
|
||||
|
||||
Example: Sending Variable-Sized Mailbox Messages
|
||||
================================================
|
||||
Example: Sending a Variable-Sized Mailbox Message
|
||||
=================================================
|
||||
|
||||
This code uses a mailbox to synchronously pass variable-sized requests
|
||||
from a producing task to any consuming task that wants it. The message
|
||||
|
@ -128,12 +413,13 @@ that each task can handle.
|
|||
}
|
||||
}
|
||||
|
||||
Example: Receiving Variable-Sized Mailbox Messages
|
||||
==================================================
|
||||
Example: Receiving a Variable-Sized Mailbox Message
|
||||
===================================================
|
||||
|
||||
This code uses a mailbox to process variable-sized requests from any
|
||||
producing task. The message "info" field is used to exchange information
|
||||
about the maximum size buffer that each task can handle.
|
||||
producing task, using the immediate data retrieval technique. The message
|
||||
"info" field is used to exchange information about the maximum size buffer
|
||||
that each task can handle.
|
||||
|
||||
.. code-block:: c
|
||||
|
||||
|
@ -204,12 +490,13 @@ portion of the message isn't used.
|
|||
}
|
||||
}
|
||||
|
||||
Example: Receiving a Mailbox Message in 2 Stages
|
||||
Example: Deferring the Retrieval of Message Data
|
||||
================================================
|
||||
|
||||
This code uses a mailbox to receive data from a producing task only if
|
||||
it meets certain criteria, thereby eliminating unneeded data copying.
|
||||
The message "info" field supplied by the sender is used to classify the message.
|
||||
This code uses a mailbox's subsequent data retrieval mechanism to get message
|
||||
data from a producing task 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
|
||||
|
||||
|
@ -227,16 +514,16 @@ The message "info" field supplied by the sender is used to classify the message.
|
|||
/* get message, but not its data */
|
||||
task_mbox_get_wait(REQUEST_BOX, &recv_msg);
|
||||
|
||||
/* get message data for only some certain messages */
|
||||
/* get message data for only certain types of messages */
|
||||
if (is_message_type_ok(recv_msg.info)) {
|
||||
/* retrieve message data and discard message */
|
||||
/* retrieve message data and delete the message */
|
||||
recv_msg.rx_data = buffer;
|
||||
task_mbox_data_get(&recv_msg);
|
||||
|
||||
/* process data in "buffer" */
|
||||
...
|
||||
} else {
|
||||
/* ignore message data and discard message */
|
||||
/* ignore message data and delete the message */
|
||||
recv_msg.size = 0;
|
||||
task_mbox_data_get(&recv_msg);
|
||||
}
|
||||
|
@ -311,7 +598,7 @@ a large message.
|
|||
/* get message, but not its data */
|
||||
task_mbox_get_wait(REQUEST_BOX, &recv_msg);
|
||||
|
||||
/* get message data as a memory block and discard message */
|
||||
/* get memory block holding data and delete the message */
|
||||
task_mbox_data_block_get_wait(&recv_msg, &recv_block, RXPOOL);
|
||||
|
||||
/* compute sum of all message bytes in memory block */
|
||||
|
@ -335,46 +622,37 @@ a large message.
|
|||
APIs
|
||||
****
|
||||
|
||||
The following APIs for synchronous mailbox operations are provided
|
||||
by :file:`microkernel.h`:
|
||||
The following APIs for mailbox operations are provided by the kernel:
|
||||
|
||||
:c:func:`task_mbox_put()`
|
||||
Puts message in a mailbox, or fails if a receiver isn't waiting.
|
||||
Sends synchrnonous message to a receiving task, with no waiting.
|
||||
|
||||
:c:func:`task_mbox_put_wait()`
|
||||
Puts message in a mailbox and waits until it is received.
|
||||
Sends synchrnonous message to a receiving task, with unlimited waiting.
|
||||
|
||||
:c:func:`task_mbox_put_wait_timeout()`
|
||||
Puts message in a mailbox and waits for a specified time period for it to
|
||||
be received.
|
||||
|
||||
:c:func:`task_mbox_get()`
|
||||
Gets message from a mailbox, or fails if no message is available.
|
||||
|
||||
:c:func:`task_mbox_get_wait()`
|
||||
Gets message from a mailbox, or waits until one is available.
|
||||
|
||||
:c:func:`task_mbox_get_wait_timeout()`
|
||||
Gets message from a mailbox, or waits for a specified time period for one
|
||||
to become available.
|
||||
|
||||
:c:func:`task_mbox_data_get()`
|
||||
Finishes receiving message that was received without its data.
|
||||
|
||||
The following APIs for asynchronous mailbox operations using memory pool blocks
|
||||
are provided by microkernel.h.
|
||||
Sends synchrnonous message to a receiving task, with time limited waiting.
|
||||
|
||||
:c:func:`task_mbox_block_put()`
|
||||
Puts message in a mailbox, even if a receiver isn't waiting.
|
||||
Sends asynchrnonous message to a receiving task, or to a mailbox queue.
|
||||
|
||||
:c:func:`task_mbox_get()`
|
||||
Gets message from a mailbox, with no waiting.
|
||||
|
||||
:c:func:`task_mbox_get_wait()`
|
||||
Gets message from a mailbox, with unlimited waiting.
|
||||
|
||||
:c:func:`task_mbox_get_wait_timeout()`
|
||||
Gets message from a mailbox, with time limited waiting.
|
||||
|
||||
:c:func:`task_mbox_data_get()`
|
||||
Retrieves message data into a buffer.
|
||||
|
||||
:c:func:`task_mbox_data_block_get()`
|
||||
Finishes receiving message that was received without its data, or fails if
|
||||
no block is available.
|
||||
Retrieves message data into a block, with no waiting.
|
||||
|
||||
:c:func:`task_mbox_data_block_get_wait()`
|
||||
Finishes receiving message that was received without its data, or waits
|
||||
until a block is available.
|
||||
Retrieves message data into a block, with unlimited waiting.
|
||||
|
||||
:c:func:`task_mbox_data_block_get_wait_timeout()`
|
||||
Finishes receiving message that was received without its data, or waits
|
||||
for a specified time period for a block to become available.
|
||||
Retrieves message data into a block, with time limited waiting.
|
||||
|
|
Loading…
Reference in a new issue