Message Mailbox Management
- 1 Mailbox Configuration
- 2 Creating a Mailbox, OSMboxCreate()
- 3 Deleting a Mailbox, OSMboxDel()
- 4 Waiting for a Message at a Mailbox, OSMboxPend()
- 5 Sending a message to a mailbox, OSMboxPost()
- 6 Sending a message to a mailbox, OSMboxPostOpt()
- 7 Getting a message without waiting (non-blocking), OSMboxAccept()
- 8 Obtaining the status of a mailbox, OSMboxQuery()
- 9 Using a Mailbox as a Binary Semaphore
- 10 Using a Mailbox instead of OSTimeDly()
Mailbox Configuration
A message mailbox (or simply a mailbox) is a µC/OS-II object that allows a task or an ISR to send a pointer-sized variable to another task. The pointer is typically initialized to point to some application specific data structure containing a “message.” µC/OS-II provides seven services to access mailboxes: OSMboxCreate(), OSMboxDel(), OSMboxPend(), OSMboxPost(), OSMboxPostOpt(), OSMboxAccept(), and OSMboxQuery().
To enable µC/OS-II message mailbox services, you must set configuration constants in
OS_CFG.H . Specifically, table 10.1 shows which services are compiled based on the value of configuration constants found in
OS_CFG.H . You should note that NONE of the mailbox services are enabled when OS_MBOX_EN is set to 0. To enable specific features (i.e. service) listed in Table 10.1, simply set the configuration constant to 1. You will notice that OSMboxCreate() and OSMboxPend() cannot be individually disabled like the other services. That’s because they are always needed when you enable µC/OS-II message mailbox management. You must enable at least one of the post services: OSMboxPost() and OSMboxPostOpt().
µC/OS-II Mailbox Service | Enabled when set to 1 in |
|---|---|
|
|
| |
|
|
| |
|
|
|
|
|
|
Figure 10.1 shows a flow diagram to illustrate the relationship between tasks, ISRs, and a message mailbox. Note that the symbology used to represent a mailbox is an I-beam. The hourglass represents a timeout that can be specified with the OSMboxPend() call. The content of the mailbox is a pointer to a message. What the pointer points to is application specific. A mailbox can only contain one pointer (mailbox is full) or a pointer to NULL (mailbox is empty).
As you can see from Figure 10.1, a task or an ISR can call OSMboxPost() or OSMboxPostOpt(). However, only tasks are allowed to call OSMboxDel(), OSMboxPend() and OSMboxQuery(). Your application can have just about any number of mailboxes. The limit is set by OS_MAX_EVENTS in
OS_CFG.H.
Creating a Mailbox, OSMboxCreate()
A mailbox needs to be created before it can be used. Creating a mailbox is accomplished by calling OSMboxCreate() and specifying the initial value of the pointer. Typically, the initial value is a NULL pointer, but a mailbox can initially contain a message. If you use the mailbox to signal the occurrence of an event (i.e., send a message), you typically initialize it to a NULL pointer because the event (most likely) has not occurred. If you use the mailbox to access a shared resource, you initialize the mailbox with a non-NULL pointer. In this case, you basically use the mailbox as a binary semaphore.
The code to create a mailbox is shown in Listing 10.1.
Listing - Listing 10.1 Creating a mailbox
OS_EVENT *OSMboxCreate (void *msg)
{
#if OS_CRITICAL_METHOD == 3
OS_CPU_SR cpu_sr; (1)
#endif
OS_EVENT *pevent;
if (OSIntNesting > 0) { (2)
return ((OS_EVENT *)0);
}
OS_ENTER_CRITICAL();
pevent = OSEventFreeList; (3)
if (OSEventFreeList != (OS_EVENT *)0) { (4)
OSEventFreeList = (OS_EVENT *)OSEventFreeList->OSEventPtr; (5)
}
OS_EXIT_CRITICAL();
if (pevent != (OS_EVENT *)0) { (6)
pevent->OSEventType = OS_EVENT_TYPE_MBOX; (7)
pevent->OSEventCnt = 0; (8)
pevent->OSEventPtr = msg; (9)
OS_EventWaitListInit(pevent); (10)
}
return (pevent); (11)
}(1) A local variable called cpu_sr to support OS_CRITICAL_METHOD #3 is allocated.
(2) OSMboxCreate() starts by making sure you are not calling this function from an ISR because this is not allowed. All kernel objects need to be created from task level code or before multitasking starts.
(3) OSMboxCreate() then attempts to obtain an ECB (Event Control Block) from the free list of ECBs (see Figure 6.5).
(4)
(5) The linked list of free ECBs is adjusted to point to the next free ECB.
(6)
(7) If there is an ECB available, the ECB type is set to OS_EVENT_TYPE_MBOX. Other OSMbox???() function calls will check this structure member to make sure that the ECB is of the proper type (i.e. a mailbox). This prevents you from calling OSMboxPost() on an ECB that was created for use as a message queue.
(8) The .OSEventCnt field is then initialized to zero since this field is not used by message mailboxes.
(9) The initial value of the message is stored in the ECB.
(10) The wait list is then initialized by calling OS_EventWaitListInit() [see 6.??, Initializing an ECB, OS_EventWaitListInit()]. Because the mailbox is being initialized, there are no tasks waiting for it and thus, OS_EventWaitListInit() clears the .OSEventGrp and .OSEventTbl[] fields of the ECB.
(11) Finally, OSMboxCreate() returns a pointer to the ECB. This pointer must be used in subsequent calls to manipulate mailboxes [OSMboxAccept(), OSMboxDel(), OSMboxPend(), OSMboxPost(), OSMboxPostOpt() and OSMboxQuery()]. The pointer is basically used as the mailbox handle. If there are no more ECBs, OSMboxCreate() returns a NULL pointer. You should make it a habbit to check return values to ensure that you are getting the desired results. Passing NULL pointers to µC/OS-II will not make it fail because µC/OS-II validates arguments (only if OS_ARG_CHK_EN is set to 1, though). Figure 10.2 shows the content of the ECB just before OSMboxCreate() returns.
Deleting a Mailbox, OSMboxDel()
The code to delete a mailbox is shown in listing 10.2 and this code will only be generated by the compiler if OS_MBOX_DEL_EN is set to 1 in
OS_CFG.H . This is a function you must use with caution because multiple tasks could attempt to access a deleted mailbox. You should always use this function with great care. Generally speaking, before you would delete a mailbox, you would first delete all the tasks that can access the mailbox.
Listing - Listing 10.2, Deleting a Mailbox
OS_EVENT *OSMboxDel (OS_EVENT *pevent, INT8U opt, INT8U *err)
{
#if OS_CRITICAL_METHOD == 3
OS_CPU_SR cpu_sr;
#endif
BOOLEAN tasks_waiting;
if (OSIntNesting > 0) { (1)
*err = OS_ERR_DEL_ISR;
return (pevent);
}
#if OS_ARG_CHK_EN > 0
if (pevent == (OS_EVENT *)0) { (2)
*err = OS_ERR_PEVENT_NULL;
return (pevent);
}
if (pevent->OSEventType != OS_EVENT_TYPE_MBOX) { (3)
*err = OS_ERR_EVENT_TYPE;
return (pevent);
}
#endif
OS_ENTER_CRITICAL();
if (pevent->OSEventGrp != 0x00) { (4)
tasks_waiting = TRUE;
} else {
tasks_waiting = FALSE;
}
switch (opt) {
case OS_DEL_NO_PEND:
if (tasks_waiting == FALSE) {
pevent->OSEventType = OS_EVENT_TYPE_UNUSED; (5)
pevent->OSEventPtr = OSEventFreeList; (6)
OSEventFreeList = pevent; (7)
OS_EXIT_CRITICAL();
*err = OS_NO_ERR;
return ((OS_EVENT *)0); (8)
} else {
OS_EXIT_CRITICAL();
*err = OS_ERR_TASK_WAITING;
return (pevent);
}
case OS_DEL_ALWAYS:
while (pevent->OSEventGrp != 0x00) { (9)
OS_EventTaskRdy(pevent, (void *)0, OS_STAT_MBOX); (10)
}
pevent->OSEventType = OS_EVENT_TYPE_UNUSED; (11)
pevent->OSEventPtr = OSEventFreeList; (12)
OSEventFreeList = pevent;
OS_EXIT_CRITICAL();
if (tasks_waiting == TRUE) {
OS_Sched(); (13)
}
*err = OS_NO_ERR;
return ((OS_EVENT *)0); (14)
default:
OS_EXIT_CRITICAL();
*err = OS_ERR_INVALID_OPT;
return (pevent);
}
}(1) OSMboxDel() starts by making sure that this function is not called from an ISR because that’s not allowed.
(2)
(3) We then validate pevent to ensure that it’s not a NULL pointer and that it points to an ECB that was created as a mailbox.
(4) OSMboxDel() then determines whether there are any tasks waiting on the mailbox. The flag tasks_waiting is set accordingly.
Based on the option (i.e., opt) specified in the call, OSMboxDel() will either delete the mailbox only if no tasks are pending on the mailbox (opt == OS_DEL_NO_PEND) or, delete the mailbox even if tasks are waiting (opt == OS_DEL_ALWAYS).
(5)
(6)
(7) When opt is set to OS_DEL_NO_PEND and there is no task waiting on the mailbox, OSMboxDel() marks the ECB as unused and the ECB is returned to the free list of ECBs. This will allow another mailbox (or any other ECB based object) to be created.
(8) You will note that OSMboxDel() returns a NULL pointer since, at this point, the mailbox should no longer be accessed through the original pointer. You ought to call OSMboxDel() as follows:
MbxPtr = OSMboxDel(MbxPtr, opt, &err);
This allows the pointer to the mailbox to be altered by the call. OSMboxDel() returns an error code if there were task waiting on the mailbox (i.e. OS_ERR_TASK_WAITING) because by specifying OS_DEL_NO_PEND you indicated that you didn’t want to delete the mailbox if there are tasks waiting on the mailbox.
(9)
(10) When opt is set to OS_DEL_ALWAYS then all tasks waiting on the mailbox will be readied. Each task will think it received a NULL message. Each task should examine the returned pointer to make sure it’s non-NULL. Also, you should note that interrupts are disabled while each task is being readied. This, of course, increases interrupt latency of your system.
(11)
(12) Once all pending tasks are readied, OSMboxDel() marks the ECB as unused and the ECB is returned to the free list of ECBs.
(13) The scheduler is called only if there were tasks waiting on the mailbox.
(14) Again, you will note that OSMboxDel() returns a NULL pointer since, at this point, the mailbox should no longer be accessed through the original pointer.
Waiting for a Message at a Mailbox, OSMboxPend()
The code to wait for a message to arrive at a mailbox is shown in Listing 10.3.
Listing - Listing 10.3 Waiting for a message at a Mailbox (blocking)
void *OSMboxPend (OS_EVENT *pevent, INT16U timeout, INT8U *err)
{
#if OS_CRITICAL_METHOD == 3
OS_CPU_SR cpu_sr;
#endif
void *msg;
if (OSIntNesting > 0) { (1)
*err = OS_ERR_PEND_ISR;
return ((void *)0);
}
#if OS_ARG_CHK_EN > 0
if (pevent == (OS_EVENT *)0) { (2)
*err = OS_ERR_PEVENT_NULL;
return ((void *)0);
}
if (pevent->OSEventType != OS_EVENT_TYPE_MBOX) { (3)
*err = OS_ERR_EVENT_TYPE;
return ((void *)0);
}
#endif
OS_ENTER_CRITICAL();
msg = pevent->OSEventPtr; (4)
if (msg != (void *)0) {
pevent->OSEventPtr = (void *)0; (5)
OS_EXIT_CRITICAL();
*err = OS_NO_ERR;
return (msg); (6)
}
OSTCBCur->OSTCBStat |= OS_STAT_MBOX; (7)
OSTCBCur->OSTCBDly = timeout; (8)
OS_EventTaskWait(pevent); (9)
OS_EXIT_CRITICAL();
OS_Sched(); (10)
OS_ENTER_CRITICAL();
msg = OSTCBCur->OSTCBMsg;
if (msg != (void *)0) { (11)
OSTCBCur->OSTCBMsg = (void *)0;
OSTCBCur->OSTCBStat = OS_STAT_RDY;
OSTCBCur->OSTCBEventPtr = (OS_EVENT *)0;
OS_EXIT_CRITICAL();
*err = OS_NO_ERR;
return (msg); (12)
}
OS_EventTO(pevent); (13)
OS_EXIT_CRITICAL();
*err = OS_TIMEOUT;
return ((void *)0); (14)
}(1) OSMboxPend() checks to see if the function was called by an ISR. It doesn’t make sense to call OSMboxPend() from an ISR because an ISR cannot be made to wait. Instead, you should call OSMboxAccept() (see section 10.05).
(2)
(3) If OS_ARG_CHK_EN (see OS_CFG.H) is set to 1, OSMboxPend() checks that pevent is not a NULL pointer and the ECB being pointed to by pevent has been created by OSMboxCreate().
(4)
(5)
(6) If a message has been deposited in the mailbox (non NULL pointer), the message is extracted from the mailbox and replaced with a NULL pointer and the function returns to its caller with the message that was in the mailbox. An error code is also set indicating success. If your code calls OSMboxPend(), this is the outcome you are looking for because it indicates that another task or an ISR already deposited a message. This happens to be the fastest path through OSMboxPend().
If the mailbox was empty, the calling task needs to be put to sleep until another task (or an ISR) sends a message through the mailbox (see section 10.04). OSMboxPend() allows you to specify a timeout value (in integral number of ticks) as one of its arguments (i.e., timeout). This feature is useful to avoid waiting indefinitely for a message to arrive at the mailbox. If the timeout value is nonzero, OSMboxPend() suspends the task until the mailbox receives a message or the specified timeout period expires. Note that a timeout value of 0 indicates that the task is willing to wait forever for a message to arrive.
(7) To put the calling task to sleep, OSMboxPend() sets the status flag in the task’s TCB (Task Control Block) to indicate that the task is suspended waiting at a mailbox.
(8) The timeout is also stored in the TCB so that it can be decremented by OSTimeTick(). You should recall (see section 3.11, Clock Tick) that OSTimeTick() decrements each of the created task’s .OSTCBDly field if it’s nonzero.
(9) The actual work of putting the task to sleep is done by OS_EventTaskWait() [see section 6.06, Making a Task Wait for an Event, OS_EventTaskWait()].
(10) Because the calling task is no longer ready to run, the scheduler is called to run the next highest priority task that is ready to run. As far as your task is concerned, it made a call to OSMboxPend() and it doesn’t know that it will be suspended until a message arrives. When the mailbox receives a message (or the timeout period expires) OSMboxPend() will resume execution immediately after the call to OS_Sched().
(11) When OS_Sched() returns, OSMboxPend() checks to see if a message was placed in the task’s TCB by OSMboxPost().
(12) If so, the call is successful and the message is returned to the caller.
(13) If a message is not received then OS_Sched() must have returned because of a timeout. The calling task is then removed from the mailbox wait list by calling OS_EventTO().
(14) Note that the returned pointer is set to NULL because there is no message to return. The calling task should either examine the contents of the return pointer or the return code to determine whether a valid message was received.