Doc: Migration Signaling Semaphores and priority inheritance
Migrate https://cwiki.apache.org/confluence/display/NUTTX/Signaling+Semaphores+and+Priority+Inheritance to official wiki Signed-off-by: Ludovic Vanasse <ludovicvanasse@gmail.com>
This commit is contained in:
parent
a5e714a71e
commit
2532a79c13
|
@ -53,3 +53,4 @@ Guides
|
|||
semihosting.rst
|
||||
renode.rst
|
||||
signal_events_interrupt_handlers.rst
|
||||
signaling_sem_priority_inheritance.rst
|
|
@ -0,0 +1,212 @@
|
|||
=============================================
|
||||
Signaling Semaphores and Priority Inheritance
|
||||
=============================================
|
||||
|
||||
.. warning:: Migrated from
|
||||
https://cwiki.apache.org/confluence/display/NUTTX/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** |
|
||||
+=====================+====================+
|
||||
| `have access` | |
|
||||
+---------------------+--------------------+
|
||||
| `priority boost` | **sem_wait(sem);** |
|
||||
+---------------------+--------------------+
|
||||
| `priority restored` | `have access` |
|
||||
+---------------------+--------------------+
|
||||
| **sem_post(sem);** | |
|
||||
+---------------------+--------------------+
|
||||
| **sem_wait(sem);** | |
|
||||
+---------------------+--------------------+
|
||||
| | `blocked` |
|
||||
+---------------------+--------------------+
|
||||
|
||||
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 details
|
||||
=====================================================
|
||||
|
||||
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 occurrence 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 `different`
|
||||
threads.
|
||||
|
||||
Usage in Drivers
|
||||
----------------
|
||||
|
||||
This usage case is used often within drivers, for example,
|
||||
when the user calls the ``read()`` method and there is no data
|
||||
available. ``sem_wait()`` is called to wait for new data to be
|
||||
received; ``sem_post()`` is called when the new data arrives
|
||||
and the user task is re-awakened.
|
||||
|
||||
Priority Inheritance Fails
|
||||
--------------------------
|
||||
|
||||
These two usage models, the locking modeling and the
|
||||
signaling model, are really very different and priority
|
||||
inheritance simply does not apply when the semaphore is
|
||||
used for signalling rather than locking. In this signaling
|
||||
case priority inheritance can interfere with the operation
|
||||
of the semaphore. The problem is that when TASK A is
|
||||
awakened it is a holder of the semaphore. Normally, a
|
||||
task is removed from the holder list when it finally
|
||||
releases the semaphore via ``sem_post()``.
|
||||
|
||||
In this case, TASK B calls ``sem_post(sem)`` but TASK B is
|
||||
not the holder of the semaphore. Since TASK A never
|
||||
calls ``sem_post(sem)`` it becomes a permanently a holder
|
||||
of the semaphore and may have its priority boosted at
|
||||
any time when any other task tries to acquire the
|
||||
semaphore.
|
||||
|
||||
Who's to Blame
|
||||
--------------
|
||||
|
||||
In the POSIX case, priority inheritance is specified only
|
||||
in the pthread mutex layer. In NuttX, on the other hand,
|
||||
pthread mutexes are simply built on top of binary locking
|
||||
semaphores. Hence, in NuttX, priority inheritance is
|
||||
implemented in the semaphore layer.
|
||||
|
||||
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:: C
|
||||
|
||||
sem_t sem
|
||||
// ...
|
||||
sem_init(&sem, 0, 0);
|
||||
sem_setprotocol(&sem, SEM_PRIO_NONE);
|
||||
|
||||
Here is the rule: If you have priority inheritance
|
||||
enabled and you use semaphores for signaling events,
|
||||
then you `must` call ``sem_setprotocol(SEM_PRIO_NONE)``
|
||||
immediately after initializing the semaphore.
|
||||
|
||||
|
||||
Why Another Non-Standard OS Interface?
|
||||
--------------------------------------
|
||||
|
||||
The non-standard ``sem_setprotocol()`` is the `moral`
|
||||
`equivalent` of the POSIX ``pthread_mutexattr_setprotocol()``
|
||||
and its naming reflects that relationship. In most
|
||||
implementations, priority inheritance is implemented
|
||||
only in the pthread mutex layer. In NuttX, on the
|
||||
other hand, pthread mutexes are simply built on top
|
||||
of binary locking semaphores. Hence, in NuttX,
|
||||
priority inheritance is implemented in the semaphore
|
||||
layer. This architecture then requires an interface
|
||||
like ``sem_setprotocol()`` in order to manage the protocol
|
||||
of the underlying semaphore.
|
||||
|
||||
|
||||
``pthread_mutexattr_setprotocol()``
|
||||
-----------------------------------
|
||||
|
||||
Since NuttX implements pthread mutexes on top of
|
||||
binary semaphores, the above recommendation also
|
||||
applies when pthread mutexes are used for inter-thread
|
||||
signaling. That is, a mutex that is used for
|
||||
signaling should be initialize like this (simplified,
|
||||
no error checking here):
|
||||
|
||||
.. code-block:: c
|
||||
|
||||
pthread_mutexattr_t attr;
|
||||
pthread_mutex_t mutex;
|
||||
// ...
|
||||
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:
|
||||
|
||||
* If the driver is used by only a single task, or
|
||||
* If the semaphore is only taken on the worker thread.
|
||||
|
||||
But this can be a serious problem if multiple tasks
|
||||
ever wait on the signaling semaphore. Drivers like
|
||||
the serial driver, for example, have many user
|
||||
threads that may call into the driver.
|
Loading…
Reference in New Issue