ChibiOS/RT implements Posix-inspired Mutexes and Condition Variables. Together the two constructs form an high level construct known as Monitor.
Mutexes and Condition Variablesare affected by the following global settings:
CH_CFG_USE_MUTEXES | This switch enables the mutexes API in the kernel. |
CH_CFG_USE_MUTEXES_RECURSIVE | If enabled then mutexes can be taken recursively. |
CH_CFG_USE_CONDVARS | This switch enables the condition variables API in the kernel. |
CH_CFG_USE_CONDVARS_TIMEOUT | Enables the timeout support for condition variables. |
Mutexes are the mechanism meant to implement mutual exclusion in the most general way. There is often confusion between Mutexes and Binary Semaphores, both are apparently able to solve the same problem but there are important differences:
Mutexes are regulated by the following state diagram:
There is a strict relationship between mutexes and threads:
Each mutex has a reference to the owner thread and a queue of waiting threads, on the other side, threads have a stack of owned mutexes. The field cnt
counts how many times the owner has taken the mutex, this is present only if the recursive mode is enabled.
MUTEX_DECL() | Mutexes static initializer. |
chMtxObjectInit() | Initializes a mutex object of type mutex_t . |
chMtxLock() | Locks the specified mutex. |
chMtxLockS() | Locks the specified mutex (S-Class variant). |
chMtxTryLock() | Tries to lock a mutex, if the mutex is already taken by another thread then the function exits without waiting. |
chMtxTryLockS() | Tries to lock a mutex, if the mutex is already taken by another thread then the function exits without waiting (S-Class variant). |
chMtxUnlock() | Unlocks the next owned mutex in reverse lock order. |
chMtxUnlockS() | Unlocks the next owned mutex in reverse lock order (S-Class variant). |
chMtxUnlockAll() | Unlocks all the mutexes owned by the invoking thread. |
Mutexes have one single use, mutual exclusion.
static mutex_t mtx1; static THD_WORKING_AREA(waThread1, 128); static THD_FUNCTION(Thread1, arg) { while (true) { /* Normal thread activity.*/ ...; /* Requesting access to protected resource.*/ chMtxLock(&mtx1); /* Accessing shared resource.*/ ...; /* Releasing the resource.*/ chMtxUnlock(&mtx1); /* The thread continues.*/ ...; } } static THD_WORKING_AREA(waThread2, 128); static THD_FUNCTION(Thread2, arg) { while (true) { /* Normal thread activity.*/ ...; /* Requesting access to protected resource.*/ chMtxLock(&mtx1); /* Accessing shared resource.*/ ...; /* Releasing the resource.*/ chMtxUnlock(&mtx1); /* The thread continues.*/ ...; } } /* * Initialization. */ void main(void) { /* * System initializations. * - Kernel initialization, the main() function becomes a thread and the * RTOS is active. */ chSysInit(); /* Initializing the mutex sharing the resource.*/ chMtxObjectInit(&mtx1); /* Starting threads.*/ chThdCreateStatic(waThread1, sizeof(waThread1), NORMALPRIO + 1, Thread1, NULL); chThdCreateStatic(waThread2, sizeof(waThread2), NORMALPRIO + 1, Thread2, NULL); /* Continuing.*/ ...; }
In all those situations where access to the shared resources can be postponed then it is recommended to use chMtxTryLock()
instead of chMtxLock()
, the former is much more efficient not having to enter the wait state.
The function chMtxUnlockAll()
releases all mutexes owned by a thread and, for reasons related to the Priority Inheritance algorithm, releasing all mutexes is much faster than releasing just one. In those situations where a thread is known to own only one mutex the use of chMtxUnlockAll()
can improve performance.
Condition variables are an additional construct working with mutexes in order to form Monitor constructs much similar, in behaviour, to the Java synchronized
constructs.
A condition variable is a queue of threads temporarily releasing a mutex an waiting for an event, once the event is received then the thread automatically re-acquires the mutex, waiting for its turn if necessary.
Condition variables and mutexes, together, are regulated by the following state diagrams:
Note how that the chCondWait()
function implicitly operates on the last taken mutexes.
A less formal, but probably easier to understand, way to explain how a monitor works is imagining it as a house with three rooms.
The house has:
Threads enter the atrium and wait there their turn to enter the main room. When in the main room threads can either decide to leave the building or start waiting for an external signal without keeping the main room occupied.
Note that monitors could have more than one conditional variable queues, in that case there would be multiple corridors bringing back to the atrium.
Monitors should always written following this template:
void synchronized(void) { /* Entering the monitor.*/ chMtxLock(&mtx); /* Protected code before the condition check, optional.*/ ...; /* Checking the condition and keep waiting until it is satisfied, the part related to the timeout could be omitted by using chCondWait() instead.*/ while (!condition) { msg_t msg = chCondWaitTimeout(&cond1, MS2ST(500)); if (msg == MSG_TIMEOUT) return; } /* Protected code with condition satisfied.*/ ...; /* Leaving the monitor.*/ chMtxUnlock(); }
Condition variables objects are simply thread queues, the queue is called a “condition variable” because entering the queue is usually done after checking a condition that is not part of the object itself. The above “while” construct it its entirety is the condition variable.
CONDVAR_DECL() | Condition variables static initializer. |
chCondObjectInit() | Initializes a condition variable object of type condition_variable_t . |
chCondSignal() | Signals a condition variable. |
chCondSignalI() | Signals a condition variable (I-Class variant). |
chCondBroadcast() | Broadcasts a condition variable. |
chCondBroadcastI() | Broadcasts a condition variable (I-Class variant). |
chCondWait() | Makes the invoking thread release the latest owned mutex and enter the condition variable wait queue. |
chCondWaitS() | Makes the invoking thread release the latest owned mutex and enter the condition variable wait queue (S-Class variant). |
chCondWaitTimeout() | Makes the invoking thread release the latest owned mutex and enter the condition variable wait queue with a timeout specification. |
chCondWaitTimeoutS() | Makes the invoking thread release the latest owned mutex and enter the condition variable wait queue with a timeout specification (S-Class variant). |
In this example we have a producer synchronized function and a consumer synchronized function both operating on a circular queue of messages.
#define QUEUE_SIZE 128 static msg_t queue[QUEUE_SIZE], *rdp, *wrp; static size_t qsize; static mutex_t qmtx; static condition_variable_t qempty; static condition_variable_t qfull; /* * Synchronized queue initialization. */ void qInit(void) { chMtxObjectInit(&qmtx); chCondObjectInit(&qempty); chCondObjectInit(&qfull); rdp = wrp = &queue[0]; qsize = 0; } /* * Writes a message into the queue, if the queue is full waits * for a free slot. */ void qProduce(msg_t msg) { /* Entering monitor.*/ chMtxLock(&qmtx); /* Waiting for space in the queue.*/ while (qsize >= QUEUE_SIZE) chCondWait(&qfull); /* Writing the message in the queue.*/ *wr = msg; if (++wr >= &queue[QUEUE_SIZE]) wr = &queue[0]; qsize++; /* Signaling that there is at least a message.*/ chCondSignal(&qempty); /* Leaving monitor.*/ chMtxUnlock(&qmtx); } /* * Reads a message from the queue, if the queue is empty waits * for a message. */ msg_t qConsume(void) { msg_t msg; /* Entering monitor.*/ chMtxLock(&qmtx); /* Waiting for messages in the queue.*/ while (qsize == 0) chCondWait(&qempty); /* Reading the message from the queue.*/ msg = *rd if (++rd >= &queue[QUEUE_SIZE]) rd = &queue[0]; qsize--; /* Signaling that there is at least free slot.*/ chCondSignal(&qfull); /* Leaving monitor.*/ chMtxUnlock(&qmtx); return msg; }
It is the same problem of the previous example but messages are assumed to be originated from ISR contex. We will mix a monitor with a critical section in order to maintain atomicity. Note that the producer is not able to wait in this case, excess messages are lost.
#define QUEUE_SIZE 128 static msg_t queue[QUEUE_SIZE], *rdp, *wrp; static size_t qsize; static mutex_t qmtx; static condition_variable_t qempty; /* * Synchronized queue initialization. */ void qInit(void) { chMtxObjectInit(&qmtx); chCondObjectInit(&qempty); rdp = wrp = &queue[0]; qsize = 0; } /* * Writes a message into the queue, if the queue is full waits * for a free slot. Note that I-Class functions are used from within * the critical zone. */ void qProduceFromISR(msg_t msg) { /* Entering monitor.*/ chSysLockFromISR(); /* Checking for space in the queue.*/ if (qsize < QUEUE_SIZE) { /* Writing the message in the queue.*/ *wr = msg; if (++wr >= &queue[QUEUE_SIZE]; wr = &queue[0]; qsize++; /* Signaling that there is at least a message.*/ chCondSignalI(&qempty); } /* Leaving monitor.*/ chSysUnlockFromISR(); } /* * Reads a message from the queue, if the queue is empty waits * for a message. Note that S-Class functions are used from within * the critical zone. */ msg_t qConsume(void) { msg_t msg; /* Entering monitor, using both the mutex and a critical zone.*/ chSysLock(); chMtxLockS(&qmtx); /* Waiting for messages in the queue.*/ while (qsize == 0) chCondWaitS(&qempty); /* Reading the message from the queue.*/ msg = *rd if (++rd >= &queue[QUEUE_SIZE]; rd = &queue[0]; qsize--; /* Leaving monitor.*/ chMtxUnlockS(&qmtx); chSysUnlock(); return msg; }
ChibiOS - Copyright © 2006..2017 Giovanni Di Sirio.