|
| 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