Advertisement

Mailboxes: introduction and basic services

June 12, 2018

Colin Walls-June 12, 2018

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:

NUSE_MAILBOX_SEND
NUSE_MAILBOX_RECEIVE
NUSE_MAILBOX_RESET
NUSE_MAILBOX_INFORMATION
NUSE_MAILBOX_COUNT

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.

Here is an extract from the default nuse_config.h file.

/* Number of mailboxes in the system - 0-16 */
#define NUSE_MAILBOX_NUMBER     0 
/* Service call enablers: */
#define NUSE_MAILBOX_SEND           FALSE      
#define NUSE_MAILBOX_RECEIVE        FALSE      
#define NUSE_MAILBOX_RESET          FALSE      
#define NUSE_MAILBOX_INFORMATION    FALSE      
#define NUSE_MAILBOX_COUNT          FALSE  


    

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.

Nucleus RTOS API Call for Sending to a Mailbox

Service call prototype:

STATUS NU_Send_To_Mailbox(NU_MAILBOX *mailbox, VOID *message, UNSIGNED suspend);

Parameters:

mailbox – pointer to the mailbox to be utilized

message – a pointer to the message to be sent which is four unsigned elements

suspend – specification for task suspend; may be NU_NO_SUSPEND or NU_SUSPEND or a timeout value

Returns:

NU_SUCCESS – the call was completed successfully

NU_INVALID_MAILBOX – the mailbox pointer is invalid

NU_INVALID_POINTER – the message pointer is NULL

NU_INVALID_SUSPEND – suspend was attempted from a non-task thread

NU_MAILBOX_FULL – the mailbox is full and suspend was not specified

NU_TIMEOUT – the mailbox is still full even after suspending for the specified period

NU_MAILBOX_DELETED – the mailbox was deleted while the task was suspended

NU_MAILBOX_WAS_RESET – the mailbox was reset while the task was suspended

Nucleus SE API Call for Sending to a Mailbox

This API call supports the key functionality of the Nucleus RTOS API.

Service call prototype:

STATUS NUSE_Mailbox_Send(NUSE_MAILBOX mailbox, ADDR *message, U8 suspend);

Parameters:

mailbox – the index (ID) of the mailbox to be utilized

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:

if (NUSE_Mailbox_Status[mailbox])       /* mailbox full */
{
    return_value = NUSE_MAILBOX_FULL;
}
else                                    /* mailbox empty */
{
    NUSE_Mailbox_Data[mailbox] = *message;
    NUSE_Mailbox_Status[mailbox] = TRUE;
    return_value = NUSE_SUCCESS;
}


The message is stored in the appropriate element of NUSE_Mailbox_Data[] and the mailbox marked as being in use.

When blocking is enabled, the code becomes more complex:

do
{
    if (!NUSE_Mailbox_Status[mailbox])      /* mailbox empty */
    {
        NUSE_Mailbox_Data[mailbox] = *message;
        NUSE_Mailbox_Status[mailbox] = TRUE;
        if (NUSE_Mailbox_Blocking_Count[mailbox] != 0)
        {
            U8 index;       /* check whether a task is blocked */
                            /* on this mailbox */
            NUSE_Mailbox_Blocking_Count[mailbox]--;
            for (index=0; index<NUSE_TASK_NUMBER; index++)
            {
                if ((LONIB(NUSE_Task_Status[index]) ==
                     NUSE_MAILBOX_SUSPEND)
                     && (HINIB(NUSE_Task_Status[index]) ==
                     mailbox))
                {
                    NUSE_Task_Blocking_Return[index] =
                        NUSE_SUCCESS;
                    NUSE_Wake_Task(index);
                    break;
                }
            }
        }
        return_value = NUSE_SUCCESS;
        suspend = NUSE_NO_SUSPEND;
    }
    else                                /* mailbox full */
    {
        if (suspend == NUSE_NO_SUSPEND)
        {
            return_value = NUSE_MAILBOX_FULL;
        }
        else
        {                               /* block task */
            NUSE_Mailbox_Blocking_Count[mailbox]++;
            NUSE_Suspend_Task(NUSE_Task_Active, (mailbox << 4) |
                              NUSE_MAILBOX_SUSPEND);
            return_value =
                   NUSE_Task_Blocking_Return[NUSE_Task_Active];
            if (return_value != NUSE_SUCCESS)
            {
                suspend = NUSE_NO_SUSPEND;
            }
        }
    }
} while (suspend == NUSE_SUSPEND);

Some explanation may be useful:

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.

Continue reading on page two >>

 

< Previous
Page 1 of 2
Next >

Loading comments...