Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.

Signaling Semaphores and Priority Inheritance

Locking vs Signaling Semaphores

Locking Semaphores.
POSIX counting semaphores have multiple uses. The typical usage is where the semaphore is used as lock on one or more resources. In this typical case, priority inheritance works perfectly: The holder of a semaphore count must be remembered so that its priority can be boosted if a higher priority task requires a count from the semaphore. It remains the holder until the same task calls sem_post() to release the count on the semaphore.

Mutual Exclusion Example.
This usage is very common for providing mutual exclusion. The semaphore is initialized to a value of one. The first task to take the semaphore has access; additional tasks that need access will then block until the first holder calls sem_post() to relinquish access:

TASK A

TASK B

sem_wait(sem);

 


have access

 


priority boost

sem_wait(sem);

 


blocked

sem_post(sem);

 


priority restored

have access

The important thing to note is that sem_wait() and sem_post() both called on the same thread, TASK A. When sem_wait() succeeds, TASK A becomes the holder of the semaphore and, while it is the holder of the semaphore (1) other threads, such as TASK B, cannot access the protected resource and (2) the priority of TASK A may be modified by the priority inheritance logic. TASK A remains the holder until is calls sem_post() on the same thread. At that time, (1) its priority may be restored and (2) TASK B has access to the resource.

Signaling Semaphores.
But a very different usage model for semaphores is for signaling events. In this case, the semaphore count is initialized to zero and the receiving task calls sem_wait() to wait for the next event of interest to occur. When an event of interest is detected by another task (or even an interrupt handler), sem_post() is called which increments the count to 1 and wakes up the receiving task.

Signaling Semaphores and Priority Inheritance

Example.
For example, in the following TASK A waits on a semaphore for events and TASK B (or perhaps an interrupt handler) signals task A of the occurence of the events by posting to that semaphore:

TASK A

TASK B

sem_init(sem, 0, 0);

 


sem_wait(sem);

 


blocked

 

 



sem_post(sem);

_Awakens as holder _

 


Notice that unlike the mutual exclusion case above, sem_wait() and sem_post() are called on diferent threads.

...

In the case of a mutex this could be simply resolved since there is only one holder but for the case of counting semaphores, there may be many holders and if the holder is not the thread that calls sem_post(), then it is not possible to know which thread/holder should be released.

Selecting the Semaphore Protocol

sem_setprotocol().
The fix is to call non-standard NuttX function sem_setprotocol(SEM_PRIO_NONE) immediately after the sem_init(). The effect of this function call is to disable priority inheritance for that specific semaphore. There should then be no priority inheritance operations on this semaphore that is used for signalling.

...

Code Block
  pthread_mutexattr_init(&attr);
  pthread_mutexattr_settype(&attr, PTHREAD_PRIO_NONE);
  pthread_mutex_init(&mutex, &attr);

Is this Always a Problem?

Ideally sem_setprotocol(SEM_PRIO_NONE) should be called for all signaling semaphores. But, no, often the use of a signaling semaphore with priority inversion is not a problem. It is not a problem if the signaling semaphore is always taken on the same thread. For example:

...