RT Counting Semaphores

RT implements a semaphore variant known as Counting Semaphores.

The counting semaphores have been first formalized by the Dutch computer scientist Edsger W. Dijkstra in 1965. Counting semaphores have an internal signed counter variable, the value of the variable is the semaphore internal state. The meaning of the counter is:

  • N < 0. The semaphore is taken and there are -N threads queued.
  • N == 0. The semaphore is taken but there are no threads queued.
  • N > 0. The semaphore is not taken and can be taken N times.

Basically the counter of a semaphore is meant to guard a resource available in finite quantity “N”.

Counting semaphores are regulated by the following state diagram:

Global Settings

Semaphores are affected by the following global settings:

CH_CFG_USE_SEMAPHORES This switch enables the semaphores API in the kernel (both counting and binary semaphores).
CH_CFG_USE_SEMAPHORES_PRIORITY If enabled then threads are queued by priority rather than in FIFO order on semaphores. The default is disabled.

Extensions

ChibiOS/RT implements an extended version of the Dijkstra semaphores, there are several enhancements over the initial definition:

  • Reset Operation. In addition to the classic Wait and Signal operations a new Reset operation has been added. This operation is able to reset a semaphore counter to any non-negative value, all waiting threads are dequeued, if any.
  • Timeouts. The Wait operation has an optional timeout parameter, a queued thread is able to be dequeued if a Signal or Reset is not performed within the specified time interval.
  • Message. The Wait operation returns a message code indicating the way the thread has been signaled:
    • MSG_OK. The thread has taken the resource normally.
    • MSG_RESET. The thread was queued and a Reset operation has been performed on the semaphore. This is the default message, a variant of the reset operation can send any message code.
    • MSG_TIMEOUT. The thread was queued and a timeout occurred.
  • Atomic Signal and Wait. A Signal operation is performed on a semaphore and a Wait operation is performed on another semaphore atomically.

Counter Semaphores API

semaphore_t Type of a counter semaphore object.
SEMAPHORE_DECL() Semaphore static initializer.
chSemObjectInit() Initializes a semaphore object of type semaphore_t.
chSemWait() Performs a Wait operation on the semaphore.
chSemWaitS() Performs a Wait operation on the semaphore (S-Class variant).
chSemWaitTimeout() Performs a Wait operation on the semaphore with timeout specification.
chSemWaitTimeoutS() Performs a Wait operation on the semaphore with timeout specification (S-Class variant).
chSemSignal() Performs a Signal operation on the semaphore.
chSemSignalI() Performs a Signal operation on the semaphore (I-Class variant).
chSemReset() Performs a Reset operation on the semaphore.
chSemResetI() Performs a Reset operation on the semaphore (I-Class variant).
chSemResetWithMessage() Performs a Reset operation on the semaphore sending a custom message.
chSemResetWithMessageI() Performs a Reset operation on the semaphore sending a custom message (I-Class variant).
chSemResetI() Performs a Reset operation on the semaphore (I-Class variant).
chSemAddCounterI() Adds a constant to the semaphore counter, threads are dequeued as required (I-Class variant).
chSemSignalWait() Atomically performs a Signal on a semaphore and a Wait on another semaphore.
chSemGetCounterI() Returns the current value of the semaphore counter (I-Class variant).
chSemFastWaitI() Faster version of Wait usable in those conditions where the counter is known to be greater than zero, it is a pure decrement (I-Class variant).
chSemFastSignalI() Faster version of Signal usable in those conditions where the counter is known to be non-negative, it is a pure increment (I-Class variant).

Examples

Resources Allocator

Imagine having a finite set of resources, DMA channels for example, and wanting to implement an allocator of said DMA channels for use by device drivers. Each driver would:

  • Allocate a channel.
  • Perform the operation.
  • Release the channel.

The problem is that there are more driver than channel and we don't want the operation to fail, so drivers will have to queue if a channel is not immediately available.

#define DMA_NUM_CHANNELS 16
 
static semaphore_t dmasem;
 
/*
 * Allocates a DMA channel. Users are supposed to use the channel and
 * then release it in a finite time.
 */
channel_t dmaAllocateChannel(void) {
 
  msg_t msg = chSemWaitTimeout(&dmasem, MS2ST(500));
  if (msg == MSG_TIMEOUT)
    panic("unreleased DMA channels";
 
  /* Get a channel from the pool, the semaphores guarantees there is at
     least one.*/
  return dma_get_channel();
}
 
/*
 * Returns a channel to the pool after use.
 */
void dmaReleaseChannel(channel_t channel) {
 
  /* Returning the channel to the pool.*/
  dma_return_channel(channel);
 
  /* Then signaling the sempaphore.*/
  chSemSignal(&dmasem);
}
 
/*
 * Initialization.
 */
void main(void) {
 
  /*
   * System initializations.
   * - Kernel initialization, the main() function becomes a thread and the
   *   RTOS is active.
   */
  chSysInit();
 
  /* Initializing the DMA semaphore to allow up to DMA_NUM_CHANNELS
     allocations.*/
  chSemObjectInit(&dmasem, DMA_CHANNELS);
 
  /* Continuing.*/
  ...;
}