Task Semaphore

About Task Semaphores

Signalling a task using a semaphore is a very popular method of synchronization and, in µC/OS-III, each task has its own built-in semaphore. This feature not only simplifies code, but is also more efficient than using a separate semaphore object. The semaphore, which is built into each task, is shown in the figure below.

Task semaphore services in µC/OS-III start with the OSTaskSem???() prefix, and the services available to the application programmer are described in Appendix A, “µC/OS-III API Reference”. Task semaphores are built into µC/OS-III and cannot be disabled at compile time as can other services. The code for task semaphores is found in os_task.c.

You can use this feature if your code knows which task to signal when the event occurs. For example, if you receive an interrupt from an Ethernet controller, you can signal the task responsible for processing the received packet as it is preferable to perform this processing using a task instead of the ISR.

Semaphore built-into a Task

There is a variety of operations to perform on task semaphores, summarized in the table below.

Task Semaphore API Summary
Function NameOperation
OSTaskSemPend()Wait on a task semaphore.
OSTaskSemPendAbort()Abort the wait on a task semaphore.
OSTaskSemPost()Signal a task.
OSTaskSemSet()Force the semaphore count to a desired value.

Pending (i.e., Waiting) on a Task Semaphore

When a task is created, it automatically creates an internal semaphore with an initial value of zero (0). Waiting on a task semaphore is quite simple, as shown in the listing below .

Pending (or waiting) on a Task's Internal Semaphore
void MyTask (void *p_arg)
{
    OS_ERR err;
    CPU_TS ts;
    :
    while (DEF_ON) {
        OSTaskSemPend(10,                   (1)
                      OS_OPT_PEND_BLOCKING, (2)
                      &ts,                  (3)
                      &err);                (4)
        /* Check "err" */
    :
    :
 }

(1) A task pends (or waits) on the task semaphore by calling OSTaskSemPend(). There is no need to specify which task, as the current task is assumed. The first argument is a timeout specified in number of clock ticks. The actual timeout obviously depends on the tick rate. If the tick rate (see os_cfg_app.h) is set to 1000, a timeout of 10 ticks represents 10 milliseconds. Specifying a timeout of zero (0) means that the task will wait forever for the task semaphore.

(2) The second argument specifies how to pend. There are two options: OS_OPT_PEND_BLOCKING and OS_OPT_PEND_NON_BLOCKING. The blocking option means that, if the task semaphore has not been signaled (or posted to), the task will wait until the semaphore is signaled, the pend is aborted by another task or, until the timeout expires.

(3) When the semaphore is signaled, µC/OS-III reads a “timestamp” and places it in the receiving task’s OS_TCB. When OSTaskSemPend() returns, the value of the timestamp is placed in the local variable “ts”. This feature captures “when” the signal actually happened. You can call OS_TS_GET() to read the current timestamp and compute the difference. This establishes how long it took for the task to receive the signal from the posting task or ISR.

(4) OSTaskSemPend() returns an error code based on the outcome of the call. If the call was successful, err will contain OS_ERR_NONE. If not, the error code will indicate the reason of the error (see µC-OS-III API Reference for a list of possible error code for OSTaskSemPend().

Posting (i.e., Signaling) a Task Semaphore

An ISR or a task signals a task by calling OSTaskSemPost(), as shown in the listing below.

Posting (or signaling) a Semaphore
OS_TCB MyTaskTCB;
  
  
void MyISR (void *p_arg)
{
    OS_ERR err;
    :
    OSTaskSemPost(&MyTaskTCB, (1)
                  OS_OPT_POST_NONE, (2)
                  &err); (3)
    /* Check "err" */
    :
    :
}

(1) A task posts (or signals) the task by calling OSTaskSemPost(). It is necessary to pass the address of the desired task’s OS_TCB and of course, the task must exist.

(2) The next argument specifies how the user wants to post. There are only two choices.

Specify OS_OPT_POST_NONE, which indicates the use of the default option of calling the scheduler after posting the semaphore.

Or, specify OS_OPT_POST_NO_SCHED to indicate that the scheduler is not to be called at the end of OSTaskSemPost(), possibly because there will be additional postings, and rescheduling would take place when finished (the last post would not specify this option).

(3) OSTaskSemPost() returns an error code based on the outcome of the call. If the call was successful, err will contain OS_ERR_NONE. If not, the error code will indicate the reason of the error (see uC-OS-III API Reference for a list of possible error codes for OSTaskSemPost().

Bilateral Rendez-Vous – Task Synchronization

Two tasks can synchronize their activities by using two task semaphores, as shown in the figure below, and is called a bilateral rendez-vous. A bilateral rendez-vous is similar to a unilateral rendez-vous, except that both tasks must synchronize with one another before proceeding. A bilateral rendez-vous cannot be performed between a task and an ISR because an ISR cannot wait on a semaphore.

Bilateral Rendezvous

The code for a bilateral rendez-vous is shown in the listing below. Of course, a bilateral rendez-vous can use two separate semaphores, but the built-in task semaphore makes setting up this type of synchronization quite straightforward.

Tasks Synchronizing their Activities
OS_TCB  MyTask1_TCB;
OS_TCB  MyTask2_TCB;
 
 
void Task1 (void *p_arg) 
{
    OS_ERR  err;
    CPU_TS  ts;
 
    while (DEF_ON) {
        :
        OSTaskSemPost(&MyTask2_TCB,             (1) 
                      OS_OPT_POST_NONE,
                      &err);                
        /* Check 'err" */
        OSTaskSemPend(0,                        (2) 
                      OS_OPT_PEND_BLOCKING,
                      &ts,
                      &err);
        /* Check 'err" */
        :
    } 
} 
 
 
void Task2 (void *p_arg) 
{
    OS_ERR  err;
    CPU_TS  ts;
 
    while (DEF_ON) {
        :
        OSTaskSemPost(&MyTask1_TCB,             (3) 
                      OS_OPT_POST_NONE,
                      &err);                
        /* Check 'err" */
        OSTaskSemPend(0,                        (4) 
                      OS_OPT_PEND_BLOCKING,
                      &ts,
                      &err);
        /* Check 'err" */
        :
    } 
}

(1) Task #1 is executing and signals Task #2’s semaphore.

(2) Task #1 pends on its internal semaphore to synchronize with Task #2. Because Task #2 has not executed yet, Task #1 is blocked waiting on its semaphore to be signaled. µC/OS-III context switches to Task #2.

(3) Task #2 executes, and signals Task #1’s semaphore.

(4) Since it has already been signaled, Task #2 is now synchronized to Task #1. If Task #1 is higher in priority than Task #2, µC/OS-III will switch back to Task #1. If not, Task #2 continues execution.