Oft laufen bei Echtzeitsystemen mehrere Prozesse oder Threads gleichzeitig ab. Durch deren parallele oder zeitlich verzahnte Ausführung können implizite oder explizite Wechselwirkungen auftreten. Eine explizite Wechselwirkung besteht beispielsweise, wenn Prozesse in Konkurrenz zueinander stehen, weil sie gleichzeitig auf ein Betriebsmittel (z.B. Speicherstruktur, Verbindung, Gerät) zugreifen, das nur in beschränkter Anzahl zur Verfügung steht und dessen Nutzung nur exklusiv durch einen Prozess möglich ist. Andernfalls kommt es zu fehlerhaften Ergebnissen oder inkonsistenten Zuständen. Bei einer solchen Wechselwirkung ist es nötig, die zeitliche Ausführung der Prozessaktionen abzustimmen. Dazu eignen sich Semaphore. Mithilfe einer solchen Datenstruktur ist es möglich, ein Ereignis zwischen verschiedenen Tasks oder zwischen einer Interrupt-Serviceroutine und einem Task zu signalisieren. Ein Semaphor lässt sich in einer Zähler-Betriebsart einsetzen und ermöglicht die Verwaltung von Ressourcen in einem System.
Ein Mutex (mutual exclusion, wechselseitiger Ausschluss) ist ein sich gegenseitig ausschließender Semaphor. Er eignet sich bestens zum Schutz gemeinsam genutzter Ressourcen oder eines kritischen Codeabschnitts in einem System.
Ein Beispiel: Ein System hat ein LCD, auf das von mehr als einem Task geschrieben werden soll. Angenommen, Task A gibt Daten zum Display aus und wird dann von Task B verdrängt. Wenn nun Task B ebenfalls Daten zum Display sendet, dann ist die Datenanzeige am Bildschirm höchstwahrscheinlich gestört.
Eine solche Situation lässt sich mit einem Mutex vermeiden, der den LCD-Treibercode schützt. In diesem Beispiel würde Task A den LCD-Treibercode aufrufen und den Mutex anfordern. Task B würde zwar die Priorität über Task A gewinnen, wenn dieser den LCD-Treiber aufruft. Er wäre aber nicht in der Lage, den Mutex zu erhalten, da dieser bereits von Task A belegt ist. Task B würde dadurch blockiert, und Task A würde erneut ausgeführt, bis dieser alle seine Daten am Display ausgegeben hat und den Mutex danach freigibt. Wenn allerdings wie in diesem Beispiel Task B eine höhere Priorität als Task A besitzt, dann liegt eine Prioritätsinversion vor (Bild 3): Task B wurde blockiert und wartet auf den Mutex, der durch den Task A mit niedrigerer Priorität belegt ist. Damit wurde die Priorität von Task B effektiv unter die von Task A abgesenkt, und dies entspricht nicht der beabsichtigten Systemfunktion.
Die meisten Mutex-Implementationen enthalten Funktionen, um solche Prioritätsinversionen zu vermeiden. Dies lässt sich durch eine vorübergehende Änderung der Priorität des Tasks erreichen, der den Mutex belegt. Dazu erhöht man die Priorität des betreffenden Tasks auf einen Wert, der höher ist als der aller anderen Tasks im System, welche die betreffende Ressource ebenfalls nutzen. In oben genanntem Beispiel würde dies bedeuten, dass Task A den Mutex belegt und zugleich vorübergehend eine höhere Priorität erhält, damit er so schnell wie möglich abgearbeitet wird und anschließend den Mutex freigeben kann. Task B würde dadurch so wenig wie möglich verzögert. Das wahrscheinlich berühmteste Beispiel einer Prioritätsinversion ereignete sich während der »Mars Pathfinder«-Mission der NASA, wodurch die Sonde beinahe verloren ging.
Ähnlich wie ein Semaphor signalisiert eine Mailbox das Eintreten eines Ereignisses. Allerdings enthält eine Mailbox zusätzlich Nutzdaten. Meist bestehen diese aus einem Zeiger (Pointer), sodass sie sich als Rohdaten oder einen Verweis auf eine größere Datenstruktur interpretieren lassen. Dies hängt natürlich von der entsprechenden Anwendung ab.
Eine »Message Queue« entspricht einem Array von Mailboxen. Eine Message-Queue ist sinnvoll, wenn mehrere Meldungen an einen Task geschickt und zwischengespeichert werden müssen, zum Beispiel Daten, die ein serieller Port empfangen und über eine ISR in einer Queue platzieren muss. Normalerweise werden diese Daten aus der Queue in der Reihenfolge ihres Eintreffens (FIFO, first in - first out) extrahiert, manche Betriebssysteme unterstützen aber auch LIFO (last in - first out) zusammen mit der Fähigkeit, eine Vorschau auf die Datennutzlast (data payload) zu erhalten, ohne diese aus der Queue herauszunehmen.
Die oben beschriebenen Funktionen eines Echtzeitbetriebssystems eignen sich, um zu signalisieren, dass ein einzelnes Ereignis aufgetreten ist. Manchmal ist auch eine Signalisierung sinnvoll, wenn mehrere Ereignisse aufgetreten sind. Für solche Situationen kommen Ereignis-Flags zum Einsatz. Ereignis-Flags nutzt man in der Regel in einer Oder-Verknüpfung (disjunktive Synchronisation, Bild 4 oben), wobei ein beliebiges ausgesendetes Ereignis-Flag den zu signalisierenden Task auslöst. Eine Und-Verknüpfung (konjunktive Synchronisation, Bild 4 unten) lässt sich ebenfalls nutzen, wenn alle Ereignis-Flags in einer Gruppe ausgelöst werden müssen, bevor ein Task signalisiert wird.