
Mailboxes have been mentioned in a previous article (# 5). They are the second most simple, after signals, method of inter-task communication, which is supported in the Nucleus SE, and provide a low-cost and flexible way to transfer simple messages between tasks.
Previous articles in the series:
Article # 20. Semaphores: helper services and data structuresArticle # 19. Semaphores: introduction and basic servicesArticle # 18. Event flag groups: helper services and data structuresArticle # 17. Event flag groups: introduction and basic servicesArticle # 16. SignalsArticle # 15. Memory sections: services and data structuresArticle # 14. Memory sections: introduction and basic servicesArticle # 13. Task data structures and unsupported API callsArticle # 12. Task ServicesArticle # 11. Tasks: configuration and introduction to the APIArticle # 10. Scheduler: additional features and context preservationArticle # 9. Scheduler: implementationArticle # 8. Nucleus SE: Inside and DeploymentArticle # 7. Nucleus SE: introductionArticle # 6. Other RTOS servicesArticle # 5. Interaction between tasks and synchronizationArticle # 4. Tasks, context switching and interruptsArticle # 3. Tasks and planningArticle # 2. RTOS: Structure and Real Time
Article # 1. RTOS: introduction.
Mailbox Usage
In Nucleus SE, mailboxes are defined during the build phase. Up to 16 mailboxes can exist in an application. If the application does not have mailboxes, the service call code and data structures associated with the mailboxes will not be included in the application.
A mailbox is simply a place to store data, the size of which is enough to store a variable of type
ADDR and secure access to which is controlled so that several tasks can use it. The task can send data to the mailbox. As a result, the box will become full, and no task will be able to send data to it until a task completes the mailbox read operation or until the box is cleared. Attempting to send data to a full mailbox or attempting to read an empty mailbox will result in an error or task suspension, depending on the selected API call settings and the configuration of the Nucleus SE.
')
Mailboxes and Queues
In some implementations of the OS, mailboxes are not implemented, and alternatively, it is proposed to use a queue. This sounds logical, since such a queue will provide the same functionality as the mailbox. However, a queue is a more complex data structure and carries much more service data, code, and has a longer service time.
In Nucleus SE, as in Nucleus RTOS, you can select any of these types of objects.
If your application has several queues and one mailbox, then it makes sense to consider replacing the mailbox with a queue. This will slightly increase the amount of service data, but will get rid of all the API code associated with mailboxes. Alternatively, you can configure the application with both methods and compare the data volume and performance.
Queues will be covered in future articles.
Mailbox Setup
Number of mailboxes
As with most Nucleus SE objects, mailbox configuration is mainly set by the
#define directives in the
nuse_config.h file. The main parameter is
NUSE_MAILBOX_NUMBER , which determines the number of mailboxes in the application. The default value is zero (that is, there are no mailboxes) and can take values ​​up to 16. An incorrect value will cause a compile-time error, which will be generated by checking in
nuse_config_check.h (it is included in the
nuse_config.c file and compiled with it ), which will trigger the
#error directive.
The choice of a non-zero value is the main activator for mailboxes. This parameter is used when defining data structures and their size depends on its value (for more details, in the next article). In addition, a non-zero value activates the API settings.
Activate API calls
Each API function (service call) in the Nucleus SE is activated by the
#define directive in the
nuse_config.h file. For mailboxes, these directives are:
NUSE_MAILBOX_SEND NUSE_MAILBOX_RECEIVE NUSE_MAILBOX_RESET NUSE_MAILBOX_INFORMATION NUSE_MAILBOX_COUNT
By default, they are assigned the value
FALSE , thus disabling all service calls and blocking the inclusion of the code implementing them. To set up mailboxes in the application, you need to select the necessary API calls and set them to
TRUE .
Below is a section of code from the file
nuse_config.h .
/* 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
If the mailbox API function is activated, and there are no mailboxes in the application (except
NUSE_Mailbox_Count () , which is always allowed), an error occurs during compilation. If your code uses an API call that has not been activated, a build error will occur because the implementation code was not included in the application.
Mailbox Calls
Nucleus RTOS supports nine service calls that are associated with mailboxes and provide the following functionality:
- Sending messages to the mailbox. Nucleus SE is implemented in the NUSE_Mailbox_Send () function.
- Reading a message from the mailbox. Nucleus SE is implemented as a NUSE_Mailbox_Receive () function.
- Mailbox recovery to unused state with the release of all suspended tasks (reset). In Nucleus SE, implemented in NUSE_Mailbox_Reset () .
- Providing information about a specific mailbox. In Nucleus SE, implemented in NUSE_Mailbox_Information () .
- Return the number of currently configured mailboxes in the application. In Nucleus SE, it is implemented in NUSE_Mailbox_Count () .
- Add a new mailbox (create). Nucleus SE is not implemented.
- Delete mailbox. Nucleus SE is not implemented.
- Return pointers to all mailboxes in the application. Nucleus SE is not implemented.
- Sending a message to all tasks suspended on the mailbox (broadcast). Nucleus SE is not implemented.
Consider in detail the implementation of each service call.
Utility calls read and write mailboxes
The basic operations that can be performed on mailboxes are writing and reading data (sending and receiving). Nucleus RTOS and Nucleus SE provide two basic API calls for these operations, which will be described below.
Mailbox Record
Calling the Nucleus RTOS API to write to the mailbox is very flexible, which allows you to suspend the task implicitly or with a timeout if the operation cannot be completed immediately (for example, when you try to write to the full mailbox). Nucleus SE provides a similar service call, only pausing the task is optional, and the timeout is not implemented.
Nucleus RTOS also offers a service call for translating data to a mailbox, this call is not supported in Nucleus SE and will be described in the “Unrealized API calls” section in the next article.
Call to write to the mailbox in the Nucleus RTOSService Call Prototype:
STATUS NU_Send_To_Mailbox (NU_MAILBOX * mailbox, VOID * message, UNSIGNED suspend);Options:
mailbox - pointer to the mailbox;
message is a pointer to the message to be sent, consisting of four
unsigned elements;
suspend - the specification of the suspension of the task, can be
NU_NO_SUSPEND ,
NU_SUSPEND, or the timeout value.
Return value:
NU_SUCCESS - the call was successfully completed;
NU_INVALID_MAILBOX - invalid mailbox pointer;
NU_INVALID_POINTER - null pointer to the message (
NULL );
NU_INVALID_SUSPEND - an attempt to pause from a
thread that is not associated with the task;
NU_MAILBOX_FULL - the mailbox is full, and the type of suspension is not specified;
NU_TIMEOUT — the mailbox is still full, even after being suspended for a 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.
Call to write to the mailbox in Nucleus SEThis API call supports the core Nucleus RTOS API.
Service Call Prototype:
STATUS NUSE_Mailbox_Send (NUSE_MAILBOX mailbox, ADDR * message, U8 suspend);Options:
mailbox -
mailbox index;
message - a pointer to the message to be sent, is a single variable of type
ADDR ;
suspend - task suspension specification, can be
NUSE_NO_SUSPEND or
NUSE_SUSPEND .
Return value:
NUSE_SUCCESS - the call was successfully completed;
NUSE_INVALID_MAILBOX - invalid mailbox index;
NUSE_INVALID_POINTER - null pointer to the message (
NULL );
NUSE_INVALID_SUSPEND - an attempt to suspend from a
thread that is not associated with the task or when the API call blocking functionality is deactivated
NUSE_MAILBOX_FULL - the mailbox is full, and the type of suspension is not specified;
NUSE_MAILBOX_WAS_RESET - the mailbox was reset while the task was suspended.
Writing to the mailbox in Nucleus SEThe code variant of the
NUSE_Mailbox_Send () API function (after checking the parameters) is selected using conditional compilation, depending on whether support for API calls is activated to block (suspend tasks) or not. Consider both options.
If task blocking is disabled, the logic of this API call is quite simple, and the code does not require an 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 corresponding
NUSE_Mailbox_Data [] element, and the mailbox is marked as used.
If task lock is activated, 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 explanations may be helpful.
The code is enclosed in a
do ... while loop , which runs until the
suspend parameter is
NUSE_SUSPEND .
If the mailbox is empty, the message is recorded, and the mailbox status is changed to indicate that it is full. Checks for suspended tasks (that are waiting to be read) on this mailbox. If there are such tasks, the first one resumes. The variable suspend is assigned the value
NUSE_NO_SUSPEND , and the API call ends and returns
NUSE_SUCCESS .
If the mailbox is full and suspend is
NUSE_NO_SUSPEND , the API call will return
NUSE_MAILBOX_FULL . If suspend is
NUSE_SUSPEND , the task is suspended. After the function completes (for example, when the task is resumed), if the return value is
NUSE_SUCCESS (which indicates that the task was resumed because the message was read, and not that the mailbox was reset), the cycle starts from the beginning.
Reading mailbox
The Nucleus RTOS API service call for reading a mailbox is very flexible and allows you to suspend tasks implicitly or with a timeout if the operation cannot be completed immediately (for example, when reading from an empty mailbox). Nucleus SE provides a similar service, only task suspension is optional, and timeout is not implemented.
Call to read the mailbox in the Nucleus RTOSService Call Prototype:
STATUS NU_Receive_From_Mailbox (NU_MAILBOX * mailbox, VOID * message, UNSIGNED suspend);Options:
mailbox - pointer to the mailbox control unit provided by the user;
message is a pointer to the repository for the received message, which has a size equal to four
unsigned variables;
suspend - task suspension specification, can be
NUSE_NO_SUSPEND ,
NUSE_SUSPEND, or timeout value.
Return value:
NU_SUCCESS - the call was successfully completed;
NU_INVALID_MAILBOX - invalid mailbox pointer;
NU_INVALID_POINTER - null pointer to the message (
NULL );
NU_INVALID_SUSPEND — attempt to suspend from an incorrect stream (not associated with a task stream);
NU_MAILBOX_EMPTY - the mailbox is empty and the type of suspension has not been specified;
NU_TIMEOUT - the mailbox is still empty, even after pausing the task for the specified timeout value;
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.
Call to read mailbox in Nucleus SEThis API call supports the core Nucleus RTOS API.
Service Call Prototype:
STATUS NUSE_Mailbox_Receive (NUSE_MAILBOX mailbox, ADDR * message, U8 suspend);Options:
mailbox -
mailbox index;
message - a pointer to the repository of the received message, it is a single variable of the type
ADDR ;
suspend - task suspension specification, can be
NUSE_NO_SUSPEND or
NUSE_SUSPEND .
Return value:
NUSE_SUCCESS - the call was successfully completed;
NUSE_INVALID_MAILBOX - invalid mailbox index;
NUSE_INDALID_POINTER - null pointer to the message (
NULL );
NUSE_INVALID_SUSPEND — attempt to pause from an incorrect thread or when API calls to block tasks are disabled;
NUSE_MAILBOX_EMPTY - the mailbox is empty and the type of task suspension is not specified;
NUSE_MAILBOX_WAS_RESET - the mailbox was reset while the task was suspended.
Implementing Reading Mailboxes in Nucleus SEThe code variant of the
NUSE_Mailbox_Receive () API function (after checking the parameters) is selected using conditional compilation, depending on whether API calls are activated for blocking (pausing tasks) or not. We will consider both options.
If task blocking is disabled, the logic of this API call is quite simple, and the code does not require an 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 read from the corresponding
NUSE_Mailbox_Data [] element, and the mailbox is marked as empty.
If task lock is activated, 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 explanations may be helpful.
The code is enclosed in a
do ... while loop , which runs until the
suspend parameter is
NUSE_SUSPEND .
If the mailbox is full, the stored message is returned, and the mailbox status is changed to indicate that it is empty. Checks for suspended tasks (which are waiting for recording) on ​​this mailbox. If there are such tasks, the first one resumes. The variable suspend is assigned the value
NUSE_NO_SUSPEND , and the API call ends and returns
NUSE_SUCCESS .
If the mailbox is empty and the
suspend parameter is
NUSE_NO_SUSPEND , the API call returns
NUSE_MAILBOX_EMPTY . If suspend is
NUSE_SUSPEND , the task is suspended. If at the end of the call the return value is
NUSE_SUCCESS , which indicates that the task was resumed because the message was sent (and not that the mailbox was reset), the cycle starts from the beginning.
The next article will look at additional API calls related to mailboxes, as well as the corresponding data structures.
About the author: Colin Walls has been working in the electronics industry for more than thirty years, spending a significant amount of time on embedded software. He is now an embedded software engineer in Mentor Embedded (a division of Mentor Graphics). Colin Walls often speaks at conferences and seminars, author of numerous technical articles and two books on embedded software. Lives in the UK.
Colin's professional
blog , e-mail: colin_walls@mentor.com.