A semaphore is a counter that takes positive integer values called units. Threads release units to and wait for units from the semaphore. A semaphore can:
You specify these parameters when creating a high-level semaphore using CreateRtSemaphore:
Create low-level semaphores with knCreateRtSemaphore. To create a low-level semaphore specify:
To provide additional units to a low-level semaphore after creation, use knReleaseRtSemaphore once for each additional unit you need. Low-level region semaphores cannot accept more than one unit.
Use a priority-based queue so high-priority threads do not wait behind lower-priority threads in the queue. Within a priority-based queue, threads of equal priority are queued in FIFO order.
For more information, see Priority bottlenecks and blocking.
When you use DeleteRtSemaphore, the RT kernel awakens any threads waiting for units at the semaphore with an E_EXIST condition code.
Delete low-level semaphores with knDeleteRtSemaphore. If a low-level semaphore is deleted, all threads in the semaphore's thread queue are awakened with an E_KN_NONEXIST status code.
If a thread waits for a unit from a binary (single-unit) semaphore to gain access to a resource and a unit is not available, it means some other thread is using the resource. The requesting thread cannot access the resource until the unit is released.
The following figure illustrates a binary semaphore guarding a resource. Threads queue up for access to the resource.
Create the semaphore with one initial unit and a maximum of one unit, using CreateRtSemaphore.
You may encounter several problems when you use semaphores for mutual exclusion of shared data. To eliminate the problems, use regions rather than semaphores to control shared resources.
The first bottleneck is a high-priority ready thread blocked by a lower-priority running thread. This occurs if the lower-priority thread obtained the required units before the higher-priority thread became ready. The running thread, regardless of priority, controls the resource until it releases the units to the semaphore.
The second bottleneck, priority inversion, occurs when a low-priority thread obtains the required units to access a resource, then is preempted by a medium-priority thread, which is then preempted by a high-priority thread that needs to access the resource. This shows what could happen:
The third bottleneck occurs when a thread holding a semaphore unit and using shared data is suspended or deleted, and no other thread can gain access to the shared data. Only after the suspended thread resumes and releases the unit to the semaphore can the other threads use the data. In the case of a deleted thread, the semaphore prevents any other threads from ever using the shared data.
The same priority bottleneck and inversion problems may arise when using non-region low-level semaphores. Low-level region semaphores, like RT regions, provide dynamic priority adjustment to avoid blocking a high priority thread.
You typically use a multi-unit semaphore as a counter, for example, managing the available space in a circular buffer. A thread can wait for more than one unit from a multi-unit semaphore and the semaphore tries to satisfy the request.
The semaphore either sends all the requested units or none at all. So, a multi-unit semaphore might have threads waiting for units and also have units available, but not enough to satisfy the thread at the head of the thread queue. This is a possible scenario:
Two queued threads wait for units:
The semaphore has zero units available when the requests are made.
These are possible outcomes for a FIFO queue:
These are possible outcomes for a priority queue, with thread B having a higher priority than A:
The next figure shows how threads can share a fixed-length list of buffers using two semaphores: one binary and one multi-unit counting semaphore.
Create a binary semaphore B that provides mutually-exclusive access to the buffer list using CreateRtSemaphore.
Create a counting semaphore C that tracks the number of available buffers, eight in this example, using CreateRtSemaphore. Set the initial units and maximum units equal to the number of buffers: eight.
All threads should return their units to C as soon as possible to free resources for other threads.
Wait for a unit from a semaphore with knWaitForRtSemaphore. You must repeat the call for each unit you need. If the semaphore contains units, the unit count decrements by one and the thread proceeds. If the semaphore has no units and the thread can wait, the thread goes to sleep in the semaphore's thread queue. Release a unit to a semaphore with knReleaseRtSemaphore. If threads wait at the semaphore, the thread at the head of the queue awakens.
A low-level region semaphore can provide mutual exclusion and synchronization. If a thread must get a unit from a region before entering a critical section, and if it returns the unit when leaving the area, only one thread ever executes in the critical section at a time. Low-level region semaphores contain a maximum of one unit and support priority adjustment.
Low-level region semaphores are similar to RT regions. RT regions additionally protect a thread inside the region from suspension or deletion.
This lists common operations on semaphores and the semaphore system calls that do the operations:
To . . . | Use this system call . . . |
---|---|
Create a semaphore | ntxCreateRtSemaphore CreateRtSemaphore knCreateRtSemaphore |
Delete a semaphore | ntxDeleteRtSemaphore DeleteRtSemaphore knDeleteRtSemaphore |
Release units | ntxReleaseRtSemaphore ReleaseRtSemaphore knReleaseRtSemaphore |
Wait for units from a semaphore | ntxWaitForRtSemaphore WaitForRtSemaphore knWaitForRtSemaphore |
Follow these semaphore rules:
This figure shows the order in which you make semaphore system calls.
This shows the order to make semaphore system calls and lists calls that semaphores frequently use: