Mailboxes are a simple method of intertask communication supported by Nucleus SE. They provide a low-cost, but flexible, means of passing simple data messages between tasks. In this chapter, the operation of mailboxes is outlined, with details of all the relevant application program interfaces. The implementation of mailboxes in Nucleus SE is also detailed.
RTOS; Nucleus; mailbox
Mailboxes were introduced in Chapter 3, RTOS Services and Facilities. They are perhaps the second simplest method of intertask communication—after signals—supported by Nucleus SE. They provide a low-cost, but flexible, means of passing simple messages between tasks.
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 write to it until a task does a read on the mailbox or the mailbox is reset. Trying to write to a full mailbox or read from an empty one may result in an error or task suspension, depending on options selected in the application program interface (API) call and the Nucleus SE configuration.
In some operating system implementations, mailboxes are not supported, 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, however, worth considering this 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 the next chapter.
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 nonzero value is the “master enable” for mailboxes. This results in some data structures being defined and sized accordingly, of which more later in this chapter. It also activates the API enabling settings.
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.
#define NUSE_MAILBOX_NUMBER 0 /* Number of mailboxes in the system - 0–16 */
/* 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.
Nucleus RTOS supports nine service calls that appertain to mailboxes, which provide the following functionality:
The implementation of each of these service calls is examined in detail.
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.
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; that is, 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 the “Unimplemented API calls” section later in this chapter.
This API call supports the key functionality of the Nucleus RTOS API.
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 Nucleus RTOS API call for reading from a mailbox is very flexible, enabling you to suspend indefinitely, or with a timeout, if the operation cannot be completed immediately; that is, you try to read from an empty mailbox. Nucleus SE provides the same service, except task suspend is optional and timeout is not implemented.
This API call supports the key functionality of the Nucleus RTOS API.
The bulk of the code of the NUSE_Mailbox_Receive() 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 empty */
{
return_value = NUSE_MAILBOX_EMPTY;
}
else
{ /* mailbox full */
*message = NUSE_Mailbox_Data[mailbox];
NUSE_Mailbox_Status[mailbox] = FALSE;
return_value = NUSE_SUCCESS;
}
The message is extracted from the appropriate element of NUSE_Mailbox_Data[] and the mailbox marked as being empty.
When blocking is enabled, the code becomes more complex:
do
{
if (NUSE_Mailbox_Status[mailbox]) /* mailbox full */
{
*message = NUSE_Mailbox_Data[mailbox];
NUSE_Mailbox_Status[mailbox] = FALSE;
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 empty */
{
if (suspend == NUSE_NO_SUSPEND)
{
return_value = NUSE_MAILBOX_EMPTY;
}
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:
Nucleus RTOS has four API calls that provide utility functions associated with mailboxes: reset mailbox, return information about a mailbox, return number of mailboxes in the application, and return pointers to all mailboxes in the application. The first three of these are implemented in Nucleus SE.
This API call restores the mailbox to its initial, unused state. Any message stored in the mailbox is lost. Any tasks that were suspended on the mailbox are resumed and receive a return code of NUSE_MAILBOX_WAS_RESET.
This API call supports the key functionality of the Nucleus RTOS API.
The bulk of the code of the NUSE_Mailbox_Reset() 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 API function code is almost trivial. The mailbox is marked as unused by setting its entry in NUSE_Mailbox_Status[] to FALSE.
When blocking is enabled, the code becomes more complex:
while (NUSE_Mailbox_Blocking_Count[mailbox]!= 0)
{
U8 index; /* check whether any tasks are blocked */
/* on this 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_MAILBOX_WAS_RESET;
NUSE_Task_Status[index] = NUSE_READY;
break;
}
}
NUSE_Mailbox_Blocking_Count[mailbox]--;
}
#if NUSE_SCHEDULER_TYPE == NUSE_PRIORITY_SCHEDULER
NUSE_Reschedule(NUSE_NO_TASK);
#endif
Initially the mailbox is marked as empty.
Each task suspended on the mailbox is marked as Ready with a suspend return code of NUSE_MAILBOX_WAS_RESET. After this process is complete, if the Priority scheduler is in use, a call is made to NUSE_Reschedule(), as one or more higher priority tasks may have been readied and needs to be allowed to run.
This service call obtains a selection of information about a mailbox. The Nucleus SE implementation differs from Nucleus RTOS in that it returns less information, as object naming and suspend ordering are not supported and task suspend may not be enabled.
This API call supports the key functionality of the Nucleus RTOS API.
The implementation of this API call is quite straightforward:
*message_present = NUSE_Mailbox_Status[mailbox];
#if NUSE_BLOCKING_ENABLE
*tasks_waiting = NUSE_Mailbox_Blocking_Count[mailbox];
if (NUSE_Mailbox_Blocking_Count[mailbox]!= 0)
{
U8 index;
for (index=0; index<NUSE_TASK_NUMBER; index++)
{
if ((LONIB(NUSE_Task_Status[index]) == NUSE_MAILBOX_SUSPEND)
&& (HINIB(NUSE_Task_Status[index]) == mailbox))
{
*first_task = index;
break;
}
}
}
else
{
*first_task = 0;
}
#else
*tasks_waiting = 0;
*first_task = 0;
#endif
return NUSE_SUCCESS;
The function returns the mailbox status. Then, if blocking API calls is enabled, the number of waiting tasks and the index of the first one are returned (otherwise, these two parameters are set to 0).
This service call returns the number of mailboxes configured in the application. Whilst in Nucleus RTOS this will vary over time and the returned value will represent the current number of mailboxes, in Nucleus SE the value returned is set at build time and cannot change.
Service call prototype:
UNSIGNED NU_Established_Mailboxes(VOID);
This API call supports the key functionality of the Nucleus RTOS API.
Service call prototype:
U8 NUSE_Mailbox_Count(void);
Returns:
The number of configured mailboxes in the application
The implementation of this API call is almost trivially simple: the value of the #define symbol NUSE_MAILBOX_NUMBER is returned.
Mailboxes utilize two or three data structures—all in RAM—which, like other Nucleus SE objects, are a series of tables, included and dimensioned according to the number of mailboxes configured and options selected.
I strongly recommend that application code does not access these data structures directly but uses the provided API functions. This avoids incompatibility with future versions of Nucleus SE and unwanted side effects and simplifies porting of an application to Nucleus RTOS. The details of data structures are included here to facilitate easier understanding of the working of the service call code and for debugging.
These data structures are all initialized to zeros by NUSE_Init_Mailbox() when Nucleus SE starts up. This is logical, as it renders every mailbox as being empty (unused). Chapter 17, Nucleus SE Initialization and Start-up, provides a full description of Nucleus SE start-up procedures.
Here are the definitions of these data structures in nuse_init.c file.
RAM ADDR NUSE_Mailbox_Data[NUSE_MAILBOX_NUMBER];
RAM U8 NUSE_Mailbox_Status[NUSE_MAILBOX_NUMBER];
#if NUSE_BLOCKING_ENABLE
RAM U8 NUSE_Mailbox_Blocking_Count[NUSE_MAILBOX_NUMBER];
#endif
There are no ROM data structures associated with mailboxes.
Like all kernel objects in Nucleus SE, the amount of data memory required for mailboxes is readily predictable.
The ROM data footprint for all the mailboxes in an application is 0.
The RAM data footprint (in bytes) for all the mailboxes in an application, when blocking API calls is enabled, may be computed thus:
NUSE_MAILBOX_NUMBER * (sizeof(ADDR) + 2)
Otherwise, it is:
NUSE_MAILBOX_NUMBER * (sizeof(ADDR) + 1)
Four mailbox API calls found in Nucleus RTOS are not implemented in Nucleus SE:
This API call creates a mailbox. It is not needed with Nucleus SE, as mailboxes are created statically.
This API call deletes a previously created mailbox. It is not needed with Nucleus SE, as mailboxes are created statically and cannot be deleted.
Service call prototype:
STATUS NU_Delete_Mailbox(NU_MAILBOX *mailbox);
This API call builds a sequential list of pointers to all mailboxes in the system. It is not needed with Nucleus SE, as mailboxes are identified by a simple index, not a pointer, and it would be redundant.
This API call broadcasts a message to all tasks waiting for a message from the specified mailbox. It is not implemented with Nucleus SE, as it would have added excessive complexity.
With all aspects of Nucleus SE, it was my goal to maintain as high a level of applications code compatibility with Nucleus RTOS as possible. Mailboxes are no exception, and, from a user’s perspective, they are implemented in much the same way as in Nucleus RTOS. There are areas of incompatibility, which have come about where I determined that such an incompatibility would be acceptable, given that the resulting code is easier to understand, or, more likely, could be made more memory efficient. Otherwise, Nucleus RTOS API calls may be almost directly mapped onto Nucleus SE calls. Chapter 20, Using Nucleus SE, includes further information on using Nucleus SE for users of Nucleus RTOS.
In Nucleus RTOS, all objects are described by a data structure—a control block—which has a specific data type. A pointer to this control block serves as an identifier for the mailbox. In Nucleus SE, I decided that a different approach was needed for memory efficiency, and all kernel objects are described by a number of tables in RAM and/or ROM. The size of these tables is determined by the number of each object type that is configured. The identifier for a specific object is simply an index into those tables. So, I have defined NUSE_MAILBOX as being equivalent to U8; a variable—not a pointer—of this type then serves as the mailbox identifier. This is a small incompatibility, which is easily handled if code is ported to or from Nucleus RTOS. Object identifiers are normally just stored and passed around and not operated on in any way.
Nucleus RTOS also supports naming of mailboxes. These names are only used for target-based debug facilities. I omitted them from Nucleus SE to save memory.
In Nucleus RTOS, a mailbox message consists of four 32-bit words. I decided to reduce this to a single variable of type ADDR in Nucleus SE. This change imparts significant increases in memory and execution time efficiency. It also recognizes that a common application for a mailbox is to send a pointer to something from one task to another. This incompatibility will present few challenges when porting application code to Nucleus RTOS. Nucleus SE could be modified quite readily, if a different message format were required.
Nucleus RTOS supports nine service calls to work with mailboxes. Of these, four are not implemented in Nucleus SE. Details of these and of the decision to omit them may be found in “Unimplemented API calls” section earlier in this chapter.
18.117.196.184