Mailboxes: introduction and basic services

Mailboxes were introduced in an earlier article. They are perhaps the second simplest method of inter-task communication – after signals – supported by Nucleus SE. They provide a low cost, but flexible, means of passing simple messages between tasks.

Using Mailboxes

In Nucleus SE, mailboxes are configured at build time. There may be a maximum of 16 mailboxes configured for an application. If no mailboxes are configured, no data structures or service call code appertaining to mailboxes are included in the application.

A mailbox is simply a storage location, big enough to hold a single variable of type ADDR, access to which is controlled so that it may be safely utilized by multiple tasks. One task can write to a mailbox. It is then full, and no task can send to it until a task does a read on the mailbox or the mailbox is reset. Trying to send to a full mailbox or read from an empty one may result in an error or task suspension, depending on options selected in the API call and the Nucleus SE configuration.

Mailboxes and Queues

In some OS implementations, mailboxes are not implemented, and the use of a single-entry queue is recommended as an alternative. This sounds reasonable, as such a queue would provide the same functionality as a mailbox. However, a queue is a rather more complex data structure than a mailbox and carries considerably more overhead in data (head and tail pointers etc.), code and execution time.

With Nucleus SE, like Nucleus RTOS, you have the choice of both object types and can make the decision for yourself.

It is worth considering the alternative approach if your application includes multiple queues, but perhaps a single mailbox. Replacing that mailbox with a queue will incur a small data overhead, but eliminates all the mailbox-related API code. It would be very easy to configure the application both ways and compare the memory footprints and performance.

Queues will be discussed in future articles.

Configuring Mailboxes

Number of Mailboxes

As with most aspects of Nucleus SE, the configuration of mailboxes is primarily controlled by #define statements in nuse_config.h. The key setting is NUSE_MAILBOX_NUMBER, which determines how many mailboxes are configured for the application. The default setting is 0 (i.e. no mailboxes are in use) and you can set it to any value up to 16. An erroneous value will result in a compile time error, which is generated by a test in nuse_config_check.h (this is included into nuse_config.c and hence compiled with this module) resulting in a #error statement being compiled.

Choosing a non-zero value is the “master enable” for mailboxes. This results in some data structures being defined and sized accordingly, of which more in the next article. It also activates the API enabling settings.

API Enables

Every API function (service call) in Nucleus SE has an enabling #define symbol in nuse_config.h. For mailboxes, these are:

By default, all of these are set to FALSE, thus disabling each service call and inhibiting the inclusion of any implementation code. To configure mailboxes for an application, you need to select the API calls that you want to use and set their enabling symbols to TRUE.

A compile time error will result if a mailbox API function is enabled and no mailboxes are configured (except for NUSE_Mailbox_Count() which is always permitted). If your code uses an API call, which has not been enabled, a link time error will result, as no implementation code will have been included in the application.

Mailbox Service Calls

Nucleus RTOS supports nine service calls that appertain to mailboxes, which provide the following functionality:

Send a message to a mailbox. Implemented by NUSE_Mailbox_Send() in Nucleus SE.

Receive a message from a mailbox. Implemented by NUSE_Mailbox_Receive() in Nucleus SE.

Restore a mailbox to the unused state, with no tasks suspended (reset). Implemented by NUSE_Mailbox_Reset() in Nucleus SE.

Provide information about a specified mailbox. Implemented by NUSE_Mailbox_Information() in Nucleus SE.

Return a count of how many mailboxes are (currently) configured for the application. Implemented by NUSE_Mailbox_Count() in Nucleus SE.

Add a new mailbox to the application (create). Not implemented in Nucleus SE.

Remove a mailbox from the application (delete). Not implemented in Nucleus SE.

Return pointers to all the mailboxes (currently) in the application. Not implemented in Nucleus SE.

Send a message to all the tasks that are suspended on a mailbox (broadcast). Not implemented in Nucleus SE.

The implementation of each of these service calls is examined in detail.

Mailbox Write and Read Services

The fundamental operations, which can be performed on a mailbox, are writing data to it – which is sometimes termed sending or posting – and reading data from it – which is also termed receiving. Nucleus RTOS and Nucleus SE each provide two basic API calls for these operations, which will be discussed here.

Writing to a Mailbox

The Nucleus RTOS API call for writing to a mailbox is very flexible, enabling you to suspend indefinitely, or with a timeout, if the operation cannot be completed immediately; i.e. you try to write to a full mailbox. Nucleus SE provides the same service, except task suspend is optional and timeout is not implemented.

Nucleus RTOS also offers a facility to broadcast to a mailbox, but this is not supported by Nucleus SE. It will be described under Unimplemented APIs in the next article.

message – a pointer to the message to be sent, which is a single variable of type ADDR

suspend – specification for task suspend; may be NUSE_NO_SUSPEND or NUSE_SUSPEND

Returns:

NUSE_SUCCESS – the call was completed successfully

NUSE_INVALID_MAILBOX – the mailbox index is invalid

NUSE_INVALID_POINTER – the message pointer is NULL

NUSE_INVALID_SUSPEND – suspend was attempted from a non-task thread or when blocking API calls were not enabled

NUSE_MAILBOX_FULL – the mailbox is full and suspend was not specified

NUSE_MAILBOX_WAS_RESET – the mailbox was reset while the task was suspended

Nucleus SE Implementation of Mailbox Send

The bulk of the code of the NUSE_Mailbox_Send() API function – after parameter checking – is selected by conditional compilation, dependent on whether support for blocking (task suspend) API calls is enabled. We will look at the two variants separately here.

If blocking is not enabled, the logic for this API call is quite simple and the code requires little explanation:

The code is enclosed in a do...while loop, which continues while the parameter suspend has the value NUSE_SUSPEND.

If the mailbox is empty, the supplied message is stored, and the mailbox status changed to indicate that it is full. A check is made on whether any tasks are suspended (waiting to receive) on the mailbox. If there are any tasks waiting, the first one is woken. The suspend variable is set to NUSE_NO_SUSPEND and the API call exits with NUSE_SUCCESS.

If the mailbox is full and suspend is set to NUSE_NO_SUSPEND, the API call exits with NUSE_MAILBOX_FULL. If suspend was set to NUSE_SUSPEND, the task is suspended. On return (i.e. when the task is woken up), if the return value is NUSE_SUCCESS, indicating that the task was woken because a message had been read (as opposed to a reset of the mailbox) the code loops back to the top.