Skip to content

Commit 2532a79

Browse files
lvanassexiaoxiang781216
authored andcommitted
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 <[email protected]>
1 parent a5e714a commit 2532a79

File tree

2 files changed

+213
-0
lines changed

2 files changed

+213
-0
lines changed

Documentation/guides/index.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,3 +53,4 @@ Guides
5353
semihosting.rst
5454
renode.rst
5555
signal_events_interrupt_handlers.rst
56+
signaling_sem_priority_inheritance.rst
Lines changed: 212 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,212 @@
1+
=============================================
2+
Signaling Semaphores and Priority Inheritance
3+
=============================================
4+
5+
.. warning:: Migrated from
6+
https://cwiki.apache.org/confluence/display/NUTTX/Signaling+Semaphores+and+Priority+Inheritance
7+
8+
Locking vs Signaling Semaphores
9+
===============================
10+
11+
Locking Semaphores
12+
------------------
13+
POSIX counting semaphores have multiple uses. The typical usage is where
14+
the semaphore is used as lock on one or more resources. In this typical
15+
case, priority inheritance works perfectly: The holder of a semaphore
16+
count must be remembered so that its priority can be boosted if a higher
17+
priority task requires a count from the semaphore. It remains the
18+
holder until the same task calls ``sem_post()`` to release the count on
19+
the semaphore.
20+
21+
Mutual Exclusion Example
22+
------------------------
23+
This usage is very common for providing mutual exclusion. The semaphore
24+
is initialized to a value of one. The first task to take the semaphore
25+
has access; additional tasks that need access will then block until
26+
the first holder calls ``sem_post()`` to relinquish access:
27+
28+
+---------------------+--------------------+
29+
| **TASK A** | **TASK B** |
30+
+=====================+====================+
31+
| `have access` | |
32+
+---------------------+--------------------+
33+
| `priority boost` | **sem_wait(sem);** |
34+
+---------------------+--------------------+
35+
| `priority restored` | `have access` |
36+
+---------------------+--------------------+
37+
| **sem_post(sem);** | |
38+
+---------------------+--------------------+
39+
| **sem_wait(sem);** | |
40+
+---------------------+--------------------+
41+
| | `blocked` |
42+
+---------------------+--------------------+
43+
44+
The important thing to note is that ``sem_wait()`` and ``sem_post()`` both
45+
called on the same thread, TASK A. When ``sem_wait()`` succeeds, TASK
46+
A becomes the holder of the semaphore and, while it is the holder
47+
of the semaphore (1) other threads, such as TASK B, cannot access
48+
the protected resource and (2) the priority of TASK A may be modified
49+
by the priority inheritance logic. TASK A remains the holder until
50+
is calls ``sem_post()`` on the `same thread`. At that time, (1) its
51+
priority may be restored and (2) TASK B has access to the resource.
52+
53+
Signaling Semaphores
54+
--------------------
55+
But a very different usage model for semaphores is for signaling
56+
events. In this case, the semaphore count is initialized to
57+
zero and the receiving task calls ``sem_wait()`` to wait for the
58+
next event of interest to occur. When an event of interest is
59+
detected by another task (or even an interrupt handler),
60+
``sem_post()`` is called which increments the count to 1 and
61+
wakes up the receiving task.
62+
63+
Signaling Semaphores and Priority Inheritance details
64+
=====================================================
65+
66+
Example
67+
-------
68+
For example, in the following TASK A waits on a semaphore
69+
for events and TASK B (or perhaps an interrupt handler)
70+
signals task A of the occurrence of the events by posting
71+
to that semaphore:
72+
73+
+--------------------------+--------------------+
74+
| **TASK A** | **TASK B** |
75+
+==========================+====================+
76+
| **sem_init(sem, 0, 0);** | |
77+
+--------------------------+--------------------+
78+
| **sem_wait(sem);** | |
79+
+--------------------------+--------------------+
80+
| `blocked` | |
81+
+--------------------------+--------------------+
82+
| | **sem_post(sem);** |
83+
+--------------------------+--------------------+
84+
| `Awakens as holder` | |
85+
+--------------------------+--------------------+
86+
87+
Notice that unlike the mutual exclusion case above,
88+
``sem_wait()`` and ``sem_post()`` are called on `different`
89+
threads.
90+
91+
Usage in Drivers
92+
----------------
93+
94+
This usage case is used often within drivers, for example,
95+
when the user calls the ``read()`` method and there is no data
96+
available. ``sem_wait()`` is called to wait for new data to be
97+
received; ``sem_post()`` is called when the new data arrives
98+
and the user task is re-awakened.
99+
100+
Priority Inheritance Fails
101+
--------------------------
102+
103+
These two usage models, the locking modeling and the
104+
signaling model, are really very different and priority
105+
inheritance simply does not apply when the semaphore is
106+
used for signalling rather than locking. In this signaling
107+
case priority inheritance can interfere with the operation
108+
of the semaphore. The problem is that when TASK A is
109+
awakened it is a holder of the semaphore. Normally, a
110+
task is removed from the holder list when it finally
111+
releases the semaphore via ``sem_post()``.
112+
113+
In this case, TASK B calls ``sem_post(sem)`` but TASK B is
114+
not the holder of the semaphore. Since TASK A never
115+
calls ``sem_post(sem)`` it becomes a permanently a holder
116+
of the semaphore and may have its priority boosted at
117+
any time when any other task tries to acquire the
118+
semaphore.
119+
120+
Who's to Blame
121+
--------------
122+
123+
In the POSIX case, priority inheritance is specified only
124+
in the pthread mutex layer. In NuttX, on the other hand,
125+
pthread mutexes are simply built on top of binary locking
126+
semaphores. Hence, in NuttX, priority inheritance is
127+
implemented in the semaphore layer.
128+
129+
In the case of a mutex this could be simply resolved since
130+
there is only one holder but for the case of counting
131+
semaphores, there may be many holders and if the holder
132+
is not the thread that calls ``sem_post()``, then it is not
133+
possible to know which thread/holder should be released.
134+
135+
Selecting the Semaphore Protocol
136+
================================
137+
138+
``sem_setprotocol()``
139+
---------------------
140+
141+
The fix is to call non-standard NuttX function
142+
``sem_setprotocol(SEM_PRIO_NONE)`` immediately after the
143+
``sem_init()``. The effect of this function call is to
144+
disable priority inheritance for that specific
145+
semaphore. There should then be no priority inheritance
146+
operations on this semaphore that is used for signalling.
147+
148+
.. code-block:: C
149+
150+
sem_t sem
151+
// ...
152+
sem_init(&sem, 0, 0);
153+
sem_setprotocol(&sem, SEM_PRIO_NONE);
154+
155+
Here is the rule: If you have priority inheritance
156+
enabled and you use semaphores for signaling events,
157+
then you `must` call ``sem_setprotocol(SEM_PRIO_NONE)``
158+
immediately after initializing the semaphore.
159+
160+
161+
Why Another Non-Standard OS Interface?
162+
--------------------------------------
163+
164+
The non-standard ``sem_setprotocol()`` is the `moral`
165+
`equivalent` of the POSIX ``pthread_mutexattr_setprotocol()``
166+
and its naming reflects that relationship. In most
167+
implementations, priority inheritance is implemented
168+
only in the pthread mutex layer. In NuttX, on the
169+
other hand, pthread mutexes are simply built on top
170+
of binary locking semaphores. Hence, in NuttX,
171+
priority inheritance is implemented in the semaphore
172+
layer. This architecture then requires an interface
173+
like ``sem_setprotocol()`` in order to manage the protocol
174+
of the underlying semaphore.
175+
176+
177+
``pthread_mutexattr_setprotocol()``
178+
-----------------------------------
179+
180+
Since NuttX implements pthread mutexes on top of
181+
binary semaphores, the above recommendation also
182+
applies when pthread mutexes are used for inter-thread
183+
signaling. That is, a mutex that is used for
184+
signaling should be initialize like this (simplified,
185+
no error checking here):
186+
187+
.. code-block:: c
188+
189+
pthread_mutexattr_t attr;
190+
pthread_mutex_t mutex;
191+
// ...
192+
pthread_mutexattr_init(&attr);
193+
pthread_mutexattr_settype(&attr, PTHREAD_PRIO_NONE);
194+
pthread_mutex_init(&mutex, &attr);
195+
196+
Is this Always a Problem?
197+
=========================
198+
199+
Ideally ``sem_setprotocol(SEM_PRIO_NONE)`` should be
200+
called for all signaling semaphores. But, no,
201+
often the use of a signaling semaphore with priority
202+
inversion is not a problem. It is not a problem
203+
if the signaling semaphore is always taken on
204+
the same thread. For example:
205+
206+
* If the driver is used by only a single task, or
207+
* If the semaphore is only taken on the worker thread.
208+
209+
But this can be a serious problem if multiple tasks
210+
ever wait on the signaling semaphore. Drivers like
211+
the serial driver, for example, have many user
212+
threads that may call into the driver.

0 commit comments

Comments
 (0)