Skip to end of metadata
Go to start of metadata

You are viewing an old version of this page. View the current version.

Compare with Current View Page History

« Previous Version 10 Next »

About Semaphores

As defined in Resource Management, a semaphore is a protocol mechanism offered by most multitasking kernels. Semaphores were originally used to control access to shared resources. However, better mechanisms exist to protect access to shared resources. Semaphores are best used to synchronize an ISR to a task, or synchronize a task with another task as shown in the figure below. 

Note that the semaphore is drawn as a flag to indicate that it is used to signal the occurrence of an event. The initial value for the semaphore is typically zero (0), indicating the event has not yet occurred.

The value “N” next to the flag indicates that the semaphore can accumulate events or credits. An ISR (or a task) can post (or signal) multiple times to a semaphore and the semaphore will remember how many times it was posted. It is possible to initialize the semaphore with a value other than zero, indicating that the semaphore initially contains that number of events.

Also, the small hourglass close to the receiving task indicates that the task has an option to specify a timeout. This timeout indicates that the task is willing to wait for the semaphore to be signaled (or posted to) within a certain amount of time. If the semaphore is not signaled within that time, µC/OS-III resumes the task and returns an error code indicating that the task was made ready-to-run because of a timeout and not the semaphore was signaled.

There are a number of operations to perform on semaphores as summarized in the table below and the figure above. However, in this chapter, we will only discuss the three functions used most often: OSSemCreate(), OSSemPend(), and OSSemPost(). The other functions are described in uC-OS-III API Reference. Also note that every semaphore function is callable from a task, but only OSSemPost() can be called by an ISR.

When used for synchronization, a semaphore keeps track of how many times it was signaled using a counter. The counter can take values between 0 and 255, 65,535, or 4,294,967,295, depending on whether the semaphore mechanism is implemented using 8, 16, or 32 bits, respectively. For µC/OS-III, the maximum value of a semaphore is determined by the data type OS_SEM_CTR (see os_type.h), which is changeable, as needed (assuming access to µC/OS-III’s source code). Along with the semaphore’s value, µC/OS-III keeps track of tasks waiting for the semaphore to be signaled.

Unilateral Rendez-Vous

The figure below shows that a task can be synchronized with an ISR (or another task) by using a semaphore. In this case, no data is exchanged, however there is an indication that the ISR or the task (on the left) has occurred. Using a semaphore for this type of synchronization is called a unilateral rendez-vous.

A unilateral rendez-vous is used when a task initiates an I/O operation and waits (i.e., call OSSemPend()) for the semaphore to be signaled (posted). When the I/O operation is complete, an ISR (or another task) signals the semaphore (i.e., calls OSSemPost()), and the task is resumed. This process is also shown on the timeline of the figure below and described below. The code for the ISR and task is shown in the listing follows the figure below.

A few interesting things are worth noting about this process. First, the task does not need to know about the details of what happens behind the scenes. As far as the task is concerned, it called a function (OSSemPend()) that will return when the event it is waiting for occurs. Second, µC/OS-III maximizes the use of the CPU by selecting the next most important task, which executes until the ISR occurs. In fact, the ISR may not occur for many milliseconds and, during that time, the CPU will work on other tasks. As far as the task that is waiting for the semaphore is concerned, it does not consume CPU time while it is waiting. Finally, the task waiting for the semaphore will execute immediately after the event occurs (assuming it is the most important task that needs to run).

Credit Tracking

As previously mentioned, a semaphore “remembers” how many times it was signaled (or posted to). In other words, if the ISR occurs multiple times before the task waiting for the event becomes the highest-priority task, the semaphore will keep count of the number of times it was signaled. When the task becomes the highest priority ready-to-run task, it will execute without blocking as many times as there were ISRs signaled. This is called Credit Tracking and is illustrated in the figure below and described below.

Multiple Tasks Waiting on a Semaphore

It is possible for more than one task to wait on the same semaphore, each with its own timeout as illustrated in the figure below.

When the semaphore is signaled (whether by an ISR or task), µC/OS-III makes the highest-priority task waiting on the semaphore ready-to-run. However, it is also possible to specify that all tasks waiting on the semaphore be made ready-to-run. This is called broadcasting and is accomplished by specifying OS_OPT_POST_ALL as an option when calling OSSemPost(). If any of the waiting tasks has a higher priority than the previously running task, µC/OS-III will execute the highest-priority task made ready by OSSemPost().

Broadcasting is a common technique used to synchronize multiple tasks and have them start executing at the same time. However, some of the tasks that we want to synchronize might not be waiting for the semaphore. It is fairly easy to resolve this problem by combining semaphores and event flags. This will be described after examining event flags.

Semaphore Internals (for synchronization)

Note that some of the material presented in this section is also contained in Resource Management, as semaphores were also discussed in that chapter. However, the material presented here will be applicable to semaphores used for synchronization and thus will differ somewhat.

A counting semaphore allows values between 0 and 255, 65,535, or 4,294,967,295, depending on whether the semaphore mechanism is implemented using 8, 16, or 32 bits, respectively. For µC/OS-III, the maximum value of a semaphore is determined by the data type OS_SEM_CTR (see os_type.h), which can be changed as needed. Along with the semaphore’s value, µC/OS-III keeps track of tasks waiting for the semaphore’s availability.

The application programmer can create an unlimited number of semaphores (limited only by available RAM). Semaphore services in µC/OS-III start with the OSSem???() prefix, and services available to the application programmer are described in µC-OS-III API Reference. Semaphore services are enabled at compile time by setting the configuration constant OS_CFG_SEM_EN to DEF_ENABLED in os_cfg.h.

Semaphores must be created before they can be used by the application. The second listing below shows how to create a semaphore.

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.

Even for users who understand 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.

Semaphores must be created before they can be used by an application. The listing below shows how to create a semaphore.

OSSemCreate() performs a check on the arguments passed to this function (assuming OS_CFG_ARG_CHK_EN is set to DEF_ENABLED  in os_cfg.h ) and only initializes the contents of the variable of type OS_SEM used for signaling.

A task waits for a signal from an ISR or another task by calling OSSemPend() as shown in the listing below (see Appendix A, µC-OS-III API Reference for details regarding the arguments).

To signal a task (either from an ISR or a task), simply call OSSemPost() as shown in the listing below.

  • No labels