...
To enable µC/OS-II semaphore services, you must set the configuration constants in OS_CFG.H
. Specifically, table 7.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 semaphore services are enabled when OS_SEM_EN
is set to 0. To enable the feature (i.e. service), simply set the configuration constant to 1. You will notice that OSSemCreate()
, OSSemPend()
and OSSemPost()
cannot be individually disabled like the other services. That’s because they are always needed when you enable µC/OS-II semaphore management.
Figure
Anchor | ||
---|---|---|
|
OSSemPend()
call.As you can see from Figure 7.1, a task or an ISR can call OSSemAccept()
, OSSemPost()
or OSSemQuery()
. However, only tasks are allowed to call OSSemDel()
or OSSemPend()
.
Creating a Semaphore, OSSemCreate()
A semaphore needs to be created before it can be used. You create a semaphore by calling OSSemCreate()
(see next section) and specifying the initial count of the semaphore. The initial value of a semaphore can be between 0 and 65535. If you use the semaphore to signal the occurrence of one or more events, you would typically initialize the semaphore to 0. If you use the semaphore to access a single shared resource, you need to initialize the semaphore to 1 (i.e., use it as a binary semaphore). Finally, if the semaphore allows your application to obtain any one of n identical resources, initialize the semaphore to n and use it as a counting semaphore.
The code to create a semaphore is shown in Listing 7.1.
Figure 7.2 shows the content of the ECB just before OSSemCreate()
returns.
Deleting a Semaphore, OSSemDel()
The code to delete a semaphore is shown in listing 7.2 and code will only be generated by the compiler if OS_SEM_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 semaphore. You should always use this function with great care. Generally speaking, before you would delete a semaphore, you would first delete all the tasks that access the semaphore.
Waiting on a Semaphore (blocking), OSSemPend()
The code to wait on a semaphore is shown in Listing 7.3.
Signaling a Semaphore, OSSemPost()
The code to signal a semaphore is shown in Listing 7.4.
It’s important to note that a context switch does NOT occur if OSSemPost()
is called by an ISR because context switching from an ISR can only occur when OSIntExit()
is called at the completion of the ISR from the last nested ISR (see section 3.??, Interrupts under µC/OS-II).
Getting a Semaphore without Waiting (non-blocking), OSSemAccept()
It is possible to obtain a semaphore without putting a task to sleep if the semaphore is not available. This is accomplished by calling OSSemAccept()
as shown in Listing 7.5.
The code that called OSSemAccept()
needs to examine the returned value. A returned value of zero indicates that the semaphore is not available; a nonzero value indicates that the semaphore is available. Furthermore, a nonzero value indicates to the caller the number of resources that are available. Keep in mind that, in this case, one of the resources has been allocated to the calling task because the count has been decremented.
An ISR could use OSSemAccept()
. However, it’s not recommended to have a semaphore shared between a task and an ISR. Semaphores are supposed to be task level objects. If a semaphore is used as a signalling object between an ISR and a task then, the ISR should only POST to the semaphore.
Obtaining the Status of a Semaphore, OSSemQuery()
OSSemQuery()
allows your application to take a “snapshot” of an ECB that is used as a semaphore (Listing 7.6). OSSemQuery()
receives two arguments: pevent contains a pointer to the semaphore, which is returned by OSSemCreate()
when the semaphore is created, and pdata is a pointer to a data structure (OS_SEM_DATA
, see uCOS_II.H
) that holds information about the semaphore. Your application will thus need to allocate a variable of type OS_SEM_DATA
that will be used to receive the information about the desired semaphore. I decided to use a new data structure because the caller should only be concerned with semaphore-specific data as opposed to the more generic OS_EVENT
data structure, which contain two additional fields (.OSEventType
and .OSEventPtr
). OS_SEM_DATA
contains the current semaphore count (.OSCnt
) and the list of tasks waiting on the semaphore (.OSEventTbl[]
and .OSEventGrp
)
|
Panel | ||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| ||||||||||||||||||
|
Figure 7.1 shows a flow diagram to illustrate the relationship between tasks, ISRs, and a semaphore. Note that the symbology used to represent a semaphore is either a key or a flag. You would us a key symbol in such flow diagrams if the semaphore is used to access shared resources. The N next to the key represents how many resources are available. N is 1 for a binary semaphore. Use a flag symbol when a semaphore is used to signal the occurrence of an event. N in this case represents the number of times the event can be signaled. The hourglass represents a timeout that can be specified with the OSSemPend()
call.
As you can see from Figure 7.1, a task or an ISR can call OSSemAccept()
, OSSemPost()
or OSSemQuery()
. However, only tasks are allowed to call OSSemDel()
or OSSemPend()
.
Anchor | ||||
---|---|---|---|---|
|
Panel | ||||
---|---|---|---|---|
| ||||
Creating a Semaphore, OSSemCreate()
A semaphore needs to be created before it can be used. You create a semaphore by calling OSSemCreate()
(see next section) and specifying the initial count of the semaphore. The initial value of a semaphore can be between 0 and 65535. If you use the semaphore to signal the occurrence of one or more events, you would typically initialize the semaphore to 0. If you use the semaphore to access a single shared resource, you need to initialize the semaphore to 1 (i.e., use it as a binary semaphore). Finally, if the semaphore allows your application to obtain any one of n identical resources, initialize the semaphore to n and use it as a counting semaphore.
The code to create a semaphore is shown in Listing 7.1.
Figure 7.2 shows the content of the ECB just before OSSemCreate()
returns.
Anchor | ||||
---|---|---|---|---|
|
Code Block | ||||||
---|---|---|---|---|---|---|
| ||||||
OS_EVENT *OSSemCreate (INT16U cnt)
{
#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_SEM; (7)
pevent->OSEventCnt = cnt; (8)
pevent->OSEventPtr = (void *)0; (9)
#if OS_EVENT_NAME_EN > 0u
pevent->OSEventName = (INT8U *)(void *)"?";
#endif
OS_EventWaitListInit(pevent); (10)
}
return (pevent); (11)
} |
Panel | ||
---|---|---|
| ||
(1) A local variable called cpu_sr to support (2) (3) (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 (8) Next, the desired initial count for the semaphore is stored in the ECB. (9) The (10) The wait list is then initialized by calling (11) Finally, |
Anchor | ||||
---|---|---|---|---|
|
Panel | ||||
---|---|---|---|---|
| ||||
Deleting a Semaphore, OSSemDel()
The code to delete a semaphore is shown in listing 7.2 and code will only be generated by the compiler if OS_SEM_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 semaphore. You should always use this function with great care. Generally speaking, before you would delete a semaphore, you would first delete all the tasks that access the semaphore.
Anchor | ||||
---|---|---|---|---|
|
Code Block | ||||||
---|---|---|---|---|---|---|
| ||||||
OS_EVENT *OSSemDel (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_SEM) { (3)
*err = OS_ERR_EVENT_TYPE;
return (pevent);
}
#endif
OS_ENTER_CRITICAL();
if (pevent->OSEventGrp != 0x00) { (4)
tasks_waiting = OS_TRUE;
} else {
tasks_waiting = OS_FALSE;
}
switch (opt) {
case OS_DEL_NO_PEND:
if (tasks_waiting == OS_FALSE) { (5)
#if OS_EVENT_NAME_EN > 0u
pevent->OSEventName = (INT8U *)(void *)"?";
#endif
pevent->OSEventType = OS_EVENT_TYPE_UNUSED; (6)
pevent->OSEventPtr = OSEventFreeList; (7)
pevent->OSEventCnt = 0u;
OSEventFreeList = pevent;
OS_EXIT_CRITICAL();
*err = OS_ERR_NONE;
return ((OS_EVENT *)0); (8)
} else {
OS_EXIT_CRITICAL();
*err = OS_ERR_TASK_WAITING;
return (pevent);
}
case OS_DEL_ALWAYS: (9)
while (pevent->OSEventGrp != 0x00) { (10)
OS_EventTaskRdy(pevent, (void *)0, OS_STAT_SEM);
}
#if OS_EVENT_NAME_EN > 0u
pevent->OSEventName = (INT8U *)(void *)"?";
#endif
pevent->OSEventType = OS_EVENT_TYPE_UNUSED; (11)
pevent->OSEventPtr = OSEventFreeList; (12)
pevent->OSEventCnt = 0u;
OSEventFreeList = pevent;
OS_EXIT_CRITICAL();
if (tasks_waiting == OS_TRUE) {
OS_Sched(); (13)
}
*err = OS_ERR_NONE;
return ((OS_EVENT *)0); (14)
default:
OS_EXIT_CRITICAL();
*err = OS_ERR_INVALID_OPT;
return (pevent);
}
} |
Panel | ||
---|---|---|
| ||
(1) (2) & (3) (4) Based on the option (i.e. opt) specified in the call, (5), (6) & (7) When opt is set to (8) You will note that (9) & (10) When opt is set to (11) & (12) Once all pending tasks are readied, (13) The scheduler is called only if there were tasks waiting on the semaphore. (14) Again, you will note that |
Waiting on a Semaphore (blocking), OSSemPend()
The code to wait on a semaphore is shown in Listing 7.3.
Anchor | ||||
---|---|---|---|---|
|
Code Block | ||||||
---|---|---|---|---|---|---|
| ||||||
void OSSemPend (OS_EVENT *pevent, INT16U timeout, INT8U *err)
{
#if OS_CRITICAL_METHOD == 3
OS_CPU_SR cpu_sr;
#endif
#if OS_ARG_CHK_EN > 0
if (pevent == (OS_EVENT *)0) { (1)
*err = OS_ERR_PEVENT_NULL;
return;
}
#endif
if (pevent->OSEventType != OS_EVENT_TYPE_SEM) {
*err = OS_ERR_EVENT_TYPE;
return;
}
if (OSIntNesting > 0) { (2)
*err = OS_ERR_PEND_ISR;
return;
}
if (OSLockNesting > 0) { (3)
*err = OS_ERR_PEND_LOCKED;
return;
}
OS_ENTER_CRITICAL();
if (pevent->OSEventCnt > 0) { (4)
pevent->OSEventCnt--; (5)
OS_EXIT_CRITICAL();
*err = OS_ERR_NONE; (6)
return;
}
OSTCBCur->OSTCBStat |= OS_STAT_SEM; (7)
OSTCBCur->OSTCBDly = timeout; (8)
OS_EventTaskWait(pevent); (9)
OS_EXIT_CRITICAL();
OS_Sched(); (10)
OS_ENTER_CRITICAL();
switch (OSTCBCur->OSTCBStatPend) { (11)
case OS_STAT_PEND_OK:
*perr = OS_ERR_NONE;
break;
case OS_STAT_PEND_ABORT:
*perr = OS_ERR_PEND_ABORT;
break;
case OS_STAT_PEND_TO:
default:
OS_EventTaskRemove(OSTCBCur, pevent); (12)
*perr = OS_ERR_TIMEOUT;
break;
}
OSTCBCur->OSTCBStat = OS_STAT_RDY; (13)
OSTCBCur->OSTCBStatPend = OS_STAT_PEND_OK;
OSTCBCur->OSTCBEventPtr = (OS_EVENT *)0;
#if (OS_EVENT_MULTI_EN > 0u)
OSTCBCur->OSTCBEventMultiPtr = (OS_EVENT **)0;
#endif
OS_EXIT_CRITICAL();
} |
Panel | ||
---|---|---|
| ||
(1) If (2) (3) You should not wait on a semaphore when the scheduler is locked. (4) & (5) If the semaphore is available (its count is nonzero), the count is decremented and the function returns to its caller with an error code indicating success. If your code calls (6) If the semaphore is not available (the count was zero), If the semaphore count is zero, the calling task needs to be put to sleep until another task (or an ISR) signals the semaphore (see section 7.04). (7) To put the calling task to sleep, (8) The timeout is also stored in the TCB so that it can be decremented by (9) The actual work of putting the task to sleep is done by (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 (11) When the semaphore is signaled (or the timeout period expires) (12) In this case, the task is removed from the wait list for the semaphore by calling (14) Finally, the link to the ECB is removed. |
Signaling a Semaphore, OSSemPost()
The code to signal a semaphore is shown in Listing 7.4.
Anchor | ||||
---|---|---|---|---|
|
Code Block | ||||||
---|---|---|---|---|---|---|
| ||||||
INT8U OSSemPost (OS_EVENT *pevent)
{
#if OS_CRITICAL_METHOD == 3
OS_CPU_SR cpu_sr;
#endif
#if OS_ARG_CHK_EN > 0
if (pevent == (OS_EVENT *)0) { (1)
return (OS_ERR_PEVENT_NULL);
}
#endif
if (pevent->OSEventType != OS_EVENT_TYPE_SEM) { (2)
return (OS_ERR_EVENT_TYPE);
}
OS_ENTER_CRITICAL();
if (pevent->OSEventGrp != 0u) { (3)
OS_EventTaskRdy(pevent, (void *)0, OS_STAT_SEM, OS_STAT_PEND_OK); (4)
OS_EXIT_CRITICAL();
OS_Sched(); (5)
return (OS_ERR_NONE);
}
if (pevent->OSEventCnt < 65535u) {
pevent->OSEventCnt++; (6)
OS_EXIT_CRITICAL();
return (OS_ERR_NONE);
}
OS_EXIT_CRITICAL();
return (OS_ERR_SEM_OVF); (7)
} |
Panel | ||
---|---|---|
| ||
(1) & (2) If (3) (4) & (5) The highest priority task waiting for the semaphore is removed from the wait list by (6) & (7) If there are no tasks waiting on the semaphore, the semaphore count simply gets incremented. Note that a counting semaphore is implemented in µC/OS-II using a 16-bit variable and |
It’s important to note that a context switch does NOT occur if OSSemPost()
is called by an ISR because context switching from an ISR can only occur when OSIntExit()
is called at the completion of the ISR from the last nested ISR (see section 3.??, Interrupts under µC/OS-II).
Getting a Semaphore without Waiting (non-blocking), OSSemAccept()
It is possible to obtain a semaphore without putting a task to sleep if the semaphore is not available. This is accomplished by calling OSSemAccept()
as shown in Listing 7.5.
The code that called OSSemAccept()
needs to examine the returned value. A returned value of zero indicates that the semaphore is not available; a nonzero value indicates that the semaphore is available. Furthermore, a nonzero value indicates to the caller the number of resources that are available. Keep in mind that, in this case, one of the resources has been allocated to the calling task because the count has been decremented.
An ISR could use OSSemAccept()
. However, it’s not recommended to have a semaphore shared between a task and an ISR. Semaphores are supposed to be task level objects. If a semaphore is used as a signalling object between an ISR and a task then, the ISR should only POST to the semaphore.
Anchor | ||||
---|---|---|---|---|
|
Code Block | ||||||
---|---|---|---|---|---|---|
| ||||||
INT16U OSSemAccept (OS_EVENT *pevent)
{
#if OS_CRITICAL_METHOD == 3
OS_CPU_SR cpu_sr;
#endif
INT16U cnt;
#if OS_ARG_CHK_EN > 0
if (pevent == (OS_EVENT *)0) { (1)
return (0);
}
#endif
if (pevent->OSEventType != OS_EVENT_TYPE_SEM) { (2)
return (0);
}
OS_ENTER_CRITICAL();
cnt = pevent->OSEventCnt; (3)
if (cnt > 0u) { (4)
pevent->OSEventCnt--; (5)
}
OS_EXIT_CRITICAL();
return (cnt); (6)
} |
Panel | ||
---|---|---|
| ||
(1) & (2) If (3) & (4) (5) The count is decremented only if the semaphore was available. (6) Finally, the original count of the semaphore is returned to the caller. |
Obtaining the Status of a Semaphore, OSSemQuery()
OSSemQuery()
allows your application to take a “snapshot” of an ECB that is used as a semaphore (Listing 7.6). OSSemQuery()
receives two arguments: pevent contains a pointer to the semaphore, which is returned by OSSemCreate()
when the semaphore is created, and pdata is a pointer to a data structure (OS_SEM_DATA
, see uCOS_II.H
) that holds information about the semaphore. Your application will thus need to allocate a variable of type OS_SEM_DATA
that will be used to receive the information about the desired semaphore. I decided to use a new data structure because the caller should only be concerned with semaphore-specific data as opposed to the more generic OS_EVENT
data structure, which contain two additional fields (.OSEventType
and .OSEventPtr
). OS_SEM_DATA
contains the current semaphore count (.OSCnt
) and the list of tasks waiting on the semaphore (.OSEventTbl[]
and .OSEventGrp
).
Anchor | ||||
---|---|---|---|---|
|
Code Block | ||||||
---|---|---|---|---|---|---|
| ||||||
INT8U OSSemQuery (OS_EVENT *pevent, OS_SEM_DATA *p_sem_data)
{
#if OS_CRITICAL_METHOD == 3
OS_CPU_SR cpu_sr;
#endif
INT8U *psrc;
INT8U *pdest;
#if OS_ARG_CHK_EN > 0
if (pevent == (OS_EVENT *)0) { (1)
return (OS_ERR_PEVENT_NULL);
}
if (p_sem_data == (OS_SEM_DATA *)0) {
return (OS_ERR_PDATA_NULL);
}
#endif
if (pevent->OSEventType != OS_EVENT_TYPE_SEM) { (2)
return (OS_ERR_EVENT_TYPE);
}
OS_ENTER_CRITICAL();
p_sem_data->OSEventGrp = pevent->OSEventGrp; (3)
psrc = &pevent->OSEventTbl[0];
pdest = &p_sem_data->OSEventTbl[0];
for (i = 0u; i < OS_EVENT_TBL_SIZE; i++) { (4)
*pdest++ = *psrc++;
}
p_sem_data->OSCnt = pevent->OSEventCnt;
OS_EXIT_CRITICAL();
return (OS_ERR_NONE);
} |
Panel | ||
---|---|---|
| ||
(1) & (2) As always, if (3) (4) Finally, |