Semaphore Internals

As previously mentioned, a semaphore is a kernel object as defined by the OS_SEM data type, which is derived from the structure os_sem (see os.h) as shown in the listing below.

The services provided by µC/OS-III to manage semaphores are implemented in the file os_sem.c. Semaphore services are enabled at compile time by setting the configuration constant OS_CFG_SEM_EN to 1 in os_cfg.h.

OS_SEM data type
          typedef  struct  os_sem  OS_SEM;              (1) 
           
           
          struct  os_sem {
              OS_OBJ_TYPE          Type;                (2) 
              CPU_CHAR            *NamePtr;             (3) 
              OS_PEND_LIST         PendList;            (4) 
              OS_SEM_CTR           Ctr;                 (5) 
              CPU_TS               TS;                  (6) 
          };

(1) In µC/OS-III, all structures are given a data type. All data types start with “OS_” and are uppercase. When a semaphore is declared, you simply use OS_SEM as the data type of the variable used to declare the semaphore.

(2) The structure starts with a “Type” field, which allows it to be recognized by µC/OS-III as a semaphore. Other kernel objects will also have a “.Type” as the first member of the structure. If a function is passed a kernel object, µC/OS-III will confirm that it is being passed the proper data type (assuming OS_CFG_OBJ_TYPE_CHK_EN is set to 1 in os_cfg.h). For example, if you pass a message queue (OS_Q) to a semaphore service (for example OSSemPend()), µC/OS-III will recognize that an invalid object was passed, and return an error code accordingly.

(3) Each kernel object can be given a name for easier recognition by debuggers or µC/Probe. This member is simply a pointer to an ASCII string, which is assumed to be NUL terminated.

(4) Since it is possible for multiple tasks to wait (or pend) on a semaphore, the semaphore object contains a pend list as described in Pend Lists.

(5) A semaphore contains a counter. As explained above, the counter can be implemented as either an 8-, 16- or 32-bit value, depending on how the data type OS_SEM_CTR is declared in os_type.h.

µC/OS-III does not make a distinction between binary and counting semaphores. The distinction is made when the semaphore is created. If creating a semaphore with an initial value of 1, it is a binary semaphore. When creating a semaphore with a value > 1, it is a counting semaphore. In the next chapter, you will discover that a semaphore is more often used as a signaling mechanism and therefore, the semaphore counter is initialized to zero.

(6) A semaphore contains a timestamp used to indicate the last time the semaphore was posted. µC/OS-III assumes the presence of a free-running counter that allows the application to make time measurements. When the semaphore is posted, the free-running counter is read and the value is placed in this field, which is returned when OSSemPend() is called. The value of this field is more useful when a semaphore is used as a signaling mechanism (see Synchronization), as opposed to a resource-sharing mechanism.

Even if the user understands the internals of the OS_SEM data type, the application code should never access any of the fields in this data structure directly. Instead, you should always use the APIs provided with µC/OS-III.

As previously mentioned, semaphores must be created before they can be used by an application.

A task waits on a semaphore before accessing a shared resource by calling OSSemPend() as shown in the listing below.

Pending and Posting to a Semaphore
          OS_SEM  MySem;
           
           
          void MyTask (void *p_arg)
          {
              OS_ERR  err;
              CPU_TS  ts;
              
              :   
              while (DEF_ON) {
                  :
                  OSSemPend(&MySem,               /* (1) Pointer to semaphore                         */
                            10,                   /*     Wait up until this time for the semaphore    */
                            OS_OPT_PEND_BLOCKING, /*     Option(s)                                    */
                            &ts,                  /*     Returned timestamp of when sem. was released */
                            &err);                /*     Pointer to Error returned                    */
                  :
                  /* Check "err" */               /* (2)                                              */
                  :
                  OSSemPost(&MySem,               /* (3) Pointer to semaphore                         */
                            OS_OPT_POST_1,        /*     Option(s) ... always OS_OPT_POST_1             */
                            &err);                /*     Pointer to Error returned                    */
                  /* Check "err" */
                  :
                  :
              }
          }

(1) When called, OSSemPend() starts by checking the arguments passed to this function to make sure they have valid values (assuming OS_CFG_ARG_CHK_EN is set to 1 in os_cfg.h).

If the semaphore counter (.Ctr of OS_SEM) is greater than zero, the counter is decremented and OSSemPend() returns. If OSSemPend() returns without error, then the task now owns the shared resource.

If the semaphore counter is zero, then another task owns the semaphore, and the calling task will need to wait for the semaphore to be released. If you specify OS_OPT_PEND_NON_BLOCKING as the option (the application does not want the task to block), OSSemPend() returns immediately to the caller and the returned error code indicates that the semaphore is unavailable. You use this option if the task does not want to wait for the resource to be available, and would prefer to do something else and check back later.

If you specify the OS_OPT_PEND_BLOCKING option, the calling task will be inserted in the list of tasks waiting for the semaphore to become available. The task is inserted in the list by priority order and therefore, the highest priority task waiting on the semaphore is at the beginning of the list.

If you specify a non-zero timeout, the task will also be inserted in the tick list. A zero value for a timeout indicates that the user is willing to wait forever for the semaphore to be released. Most of the time, you would specify an infinite timeout when using the semaphore in resource sharing. Adding a timeout may temporarily break a deadlock, however, there are better ways of preventing deadlock at the application level (e.g., never hold more than one semaphore at the same time; resource ordering; etc.).

Assuming blocking, the scheduler is called since the current task is no longer able to run (it is waiting for the semaphore to be released). The scheduler will then run the next highest-priority task that is ready-to-run.

When the semaphore is released and the task that called OSSemPend() is again the highest-priority task, µC/OS-III examines the task status to determine the reason why OSSemPend() is returning to its caller. The possibilities are:

1) The semaphore was given to the waiting task. This is the preferred outcome.

2) The pend was aborted by another task

3) The semaphore was not posted within the specified timeout

4) The semaphore was deleted

When OSSemPend() returns, the caller is notified of the above outcome through an appropriate error code.

(2) If OSSemPend() returns with err set to OS_ERR_NONE, your code can assume that it now has access to the resource.

If err contains anything else, OSSemPend() either timed out (if the timeout argument was non-zero), the pend was aborted by another task, or the semaphore was deleted by another task. It is always important to examine the returned error code and not assume that everything went well.

(3) When the task is finished accessing the resource, it needs to call OSSemPost() and specify the same semaphore. Again, OSSemPost() starts by checking the arguments passed to this function to make sure there are valid values (assuming OS_CFG_ARG_CHK_EN is set to 1 in os_cfg.h).

OSSemPost() then calls OS_TS_GET() to obtain the current timestamp so it can place that information in the semaphore to be used by OSSemPend(). This feature is not as useful when semaphores are used to share resources as it is when used as a signaling mechanism.

OSSemPost() checks to see if any tasks are waiting for the semaphore. If not, OSSemPost() simply increments p_sem->Ctr, saves the timestamp in the semaphore, and returns.

If there are tasks waiting for the semaphore to be released, OSSemPost() extracts the highest-priority task waiting for the semaphore. This is a fast operation as the pend list is sorted by priority order.

When calling OSSemPost(), it is possible to specify as an option to not call the scheduler. This means that the post is performed, but the scheduler is not called even if a higher priority task waits for the semaphore to be released. This allows the calling task to perform other post functions (if needed) and make all posts take effect simultaneously without the possibility of context switching in between each post.