Echtzeitbetriebssysteme – Einführung und Konzepte

Autor / Redakteur: David Kalinsky * / Sebastian Gerstl

Echtzeitbetriebssysteme arbeitem unter knallharten Bedingungen: Begrenzte Ressourcen, untypische Schnittstellen, strikte Anforderungen an die Bereitstellung von Tasks. Diese Einführung erklärt die wesentlichen Konzepte des Herzstücks vieler Embedded-Systeme.

Anbieter zum Thema

In einem Embedded-System muss das verwendete Betriebssystem unter knappen Ressourcen harte Echtzeit-Anforderungen erfüllen können, Ein "normales" OS reicht hier oft aus diversen Gründen nicht aus.
In einem Embedded-System muss das verwendete Betriebssystem unter knappen Ressourcen harte Echtzeit-Anforderungen erfüllen können, Ein "normales" OS reicht hier oft aus diversen Gründen nicht aus.
(Bild: Clipdealer)

Echtzeit- und Embedded-Systeme laufen in Umgebungen, in denen nur begrenzte Rechenspeicherkapazität und Verarbeitungsressourcen zur Verfügung stehen. Ihre Dienste müssen sie oft innerhalb einer streng definierten Zeitspanne (Deadline) erbringen. Diese Einschränkung hinsichtlich der Speicherkapazität und die Anforderungen an Geschwindigkeit und Zeit machen den Einsatz von Echtzeitbetriebssystemen (engl. Real-time Operating System, RTOS) in Embedded-Software erforderlich.

Bei Embedded-Systemen ist oft nicht direkt ersichtlich, dass es sich um Rechner handelt. Sie verrichten ihre Arbeit verborgen im Inneren der verschiedensten Gegenstände um uns herum, die uns den Alltag erleichtern. Mit der Außenwelt sind Embedded-Systeme meist nicht über gängige Rechnerschnittstellen, sprich, Maus, Tastatur oder Benutzeroberfläche verbunden, sondern über eher untypische Schnittstellen, wie Sensoren, Aktuatoren und spezielle Kommunikationslinks.

Grundlegende Kernel-Services von Echtzeitbetriebssystemen

Nachfolgend betrachten wir den Kernel (Kern), also den Teil eines Betriebssystems, der die wichtigsten Dienste für die Applikationssoftware auf einem Prozessor erbringt.

Der Kernel eines Echtzeitbetriebssystems bildet eine „Abstraktionsschicht“, die die Hardwaredetails des Prozessors (bzw. der Prozessoren), auf denen die Applikationssoftware ausgeführt wird, vor dieser Software verbirgt, wie in Bild 1 gezeigt. Der RTOS-Kernel stellt diese „Abstraktionsschicht“ bereit und bietet der Applikationssoftware wichtige Services an. Diese lassen sich in fünf Hauptkategorien einteilen, wie sie in Bild 2 zu sehen sind.

Die wichtigste Kernel-Service-Kategorie, im Bild 2 mittig dargestellt, ist das Task-Management. Diese Services erlauben es dem Software-Entwickler, seine Software als verschiedene „Einheiten“ zu entwerfen. Jede Einheit ist für einen bestimmten Bereich bzw. ein bestimmtes Ergebnis zuständig und hat u.U. eine eigene Echtzeit-Deadline. Diese Einheiten sind die sogenannten „Tasks“ (Aufgaben). Über die Services dieser Kategorie lassen sich Tasks starten und mit einer Priorität versehen. Der wichtigste RTOS-Service ist dabei das Scheduling von Tasks, während das Embedded-System in Betrieb ist. Der Task-Scheduler steuert die rechtzeitige und korrekte Ausführung der Applikationssoftware-Tasks. Wie das genau funktioniert, wird später erklärt.

Die zweite Kategorie der Kernel-Services, im Bild 2 oben dargestellt, ist die Intertask-Kommunikation und -Synchronisation. Damit können Tasks untereinander Daten weiterleiten. Zudem sorgen diese Services dafür, dass sich die Tasks untereinander abstimmen und effizient zusammenwirken. Auch verhindern sie, dass die Daten, die die Tasks austauschen, beschädigt werden oder dass sich die Tasks gegenseitig behindern.

Bild 2: Ein RTOS- Kernel stellt diese wesentlichen Services bereit.
Bild 2: Ein RTOS- Kernel stellt diese wesentlichen Services bereit.
(Bild: Kalinsky Associates)

Für viele Embedded-Systeme gelten strenge Timing-Anforderungen, daher bieten die meisten RTOS-Kernels wichtige Timer-Services an, z.B. Task-Delay und Timeouts; diese sind im Bild 2 rechts dargestellt.

Viele RTOS-Kernels (nicht alle) stellen auch Services wie dynamische Speicherallokation bereit. In dieser Service-Kategorie können die Tasks vorübergehend bestimmte RAM-Bereiche in der Applikationssoftware nutzen. Diese Speicherbereiche werden auch von Task zu Task weitergegeben, so dass sich große Datenmengen zwischen den Tasks kommunizieren lassen. Manche sehr kleine RTOS-Kernels für extrem speicherlimitierte Umgebungen bieten die dynamische Speicherallokation nicht an.

Viele RTOS-Kernels haben darüber hinaus eine Servicekategorie “Device I/O Supervisor”. Diese Services stellen ein einheitliches Framework für die Organisation und den Zugriff auf die Hardwaredevice-Treiber bereit, die in den meisten Embedded-Systemen zum Einsatz kommen.

Zusätzlich zu den Kernel-Services bieten viele RTOS-Systeme optionale Add-on-Betriebssystemkomponenten für komplexere Services wie Filesystem-Organisation, Netzwerk-Kommunikation, Datenbankmanagement, Oberflächengrafik etc. an. Diese Add-on-Komponenten sind zwar oft viel größer und komplexer als der RTOS-Kernel, sind aber dennoch vom RTOS-Kernel abhängig und machen sich dessen wesentliche Services zunutze. Eine solche Komponente kommt nur dann in einem Embedded-System zum Einsatz, wenn seine Services erforderlich sind, um eine Embedded-Applikation zu implementieren, so dass möglichst wenig Programmspeicher verbraucht wird.

Dieser Beitrag beleuchtet im weiteren die wesentlichen RTOS-Kernel-Services für das Task-Management, die Intertask-Kommunikation und -Synchronisation sowie die dynamischen Speicherallokation.

Echtzeit- vs. "Standard"-Betriebssystem: Determinismus

Auch viele „normale“ Betriebssysteme (die allerdings keine Echtzeitbetriebssysteme sind) bieten solche Kernel-Services an. Der wesentliche Unterschied zwischen normalen Betriebssystemen und Echtzeitbetriebssystemen liegt darin, dass letztere ein deterministisches Zeitverhalten aufweisen müssen.

Deterministisch bedeutet, dass die Betriebssystem-Dienste jeweils nur eine bekannte und erwartete Zeit in Anspruch nehmen. Diese Dienste ließen sich theoretisch als mathematische Formeln darstellen, die strikt algebraisch sein müssen und keine zufälligen Timingkomponenten enthalten dürfen. Zufallselemente im Service-Timing verursachen u.U. Zufallsverzögerungen in der Applikationssoftware; die Applikation würde dann die Echtzeit-Anforderungen nicht erfüllen. In Embedded-Echtzeit-Systemen ist ein solches Szenario nicht akzeptabel.

Normale Betriebssysteme sind oft nicht deterministisch. Ihre Services können Zufallsverzögerungen in der Applikationssoftware verursachen, und die Reaktionen dieser Applikation sind dann langsamer und zeitlich nicht präzise. Wenn Sie den Entwickler eines normalen Betriebssystems (z.B. Windows, Unix oder Linux) nach der algebraischen Formel fragen, die das Zeitverhalten einer Betriebssystem-Komponente beschreibt (z.B. das Senden einer Message von einer Task zur anderen), wird er ihnen diese Formel nicht nennen können - er wirft Ihnen höchstens einen fragenden Blick zu. Deterministisches Zeitverhalten ist bei normalen Betriebssystemen nun mal kein Designziel.

Echtzeitbetriebssysteme bieten häufig mehr als nur einfachen Determinismus, sondern ermöglichen den meisten Kernel-Services ein konstantes, lastenunabhängiges Timing. Die algebraische Formel ist entsprechend einfach: T(message_send) = constant, unabhängig von der Länge der zu sendenden Message oder anderen Faktoren, z.B. der Anzahl an Tasks, Queues und Messages, die das RTOS verwaltet.

Bild 3: Zeitachse für prioritätsbasiertes präemptives Scheduling anhand von Beispielen.
Bild 3: Zeitachse für prioritätsbasiertes präemptives Scheduling anhand von Beispielen.
(Bild: Kalinsky Associates)

Task Scheduling in Echtzeitbetriebssystemen

Bei den meisten RTOS-Systemen basiert das Task-Scheduling auf dem sogenannten prioritätsbasierten präemptiven Scheduling. Jeder Task in einer Software-Applikation ist eine Priorität zuzuweisen. Eine höhere Priorität bedeutet, dass eine schnellere Reaktion erforderlich ist. Durch das präemptive Task-Scheduling wird eine sehr schnelle Reaktion sichergestellt.

Präemptiv bedeutet, dass der Scheduler eine gerade laufende Task an jeder Stelle anhalten kann, wenn er erkennt, dass eine andere Task sofort ausgeführt werden muss.

Die Grundregel, auf der das prioritätsbasierte präemptive Scheduling basiert, besagt, dass die ausführbereite Task mit der höchsten Priorität immer die Task ist, die ausgeführt werden muss. Wenn also sowohl eine Task mit niedriger als auch eine Task mit höherer Priorität ausführbereit sind, sorgt der Scheduler dafür, dass zuerst die Task mit der höheren Priorität läuft. Die Task mit niedrigerer Priorität wird erst dann ausgeführt, wenn die höherpriore Task verarbeitet wurde.

Wie wird vorgegangen, wenn eine höherpriore Task bereit wird, die Ausführung einer niederprioren Task aber schon begonnen hat? Dieser Fall könnte z.B. eintreten, wenn ein externer Schalter geschlossen wird. Ein prioritätsbasierter präemptiver Scheduler gestattet in diesem Fall der niederprioren Task die Ausführung des aktuellen Assemblerbefehls (jedoch nicht die Fertigstellung einer ganzen Hochsprachencodezeile oder das Weiterlaufen bis zum nächsten Timer Tick). Danach hält der Scheduler sofort die Ausführung der niederprioren Task an und lässt die höherpriore Task ablaufen.

Wenn die höherpriore Task fertig abgelaufen ist, kann die niederpriore Task weiter ausgeführt werden. Dies wird in Bild 3 dargestellt; die höherpriore Task wird hier als „Mid-Priority Task“ (Task mit mittlerer Priorität) bezeichnet.

Es kann natürlich auch vorkommen, dass die mittelpriore Task noch abläuft und dabei eine Task mit noch höherer Priorität bereit wird. Im Bild 3 ist dies dargestellt als „Trigger_2“, aufgrund dessen die hochpriore Task bereit wird. In diesem Fall würde die gerade laufende Task (also die "Mid-Priority Task") verdrängt, und die Ausführung der hochprioren Task wird ermöglicht. Nach Beendigung der hochprioren Task kann die mittelpriore Task weiter ausgeführt werden. Dieses Szenario wird auch als „verschachtelte Präemption“ bezeichnet.

Wenn der prioritätsbasierte präemptive Scheduler über einen externen Trigger (z.B. Schalter, der geschlossen wird) oder Software-Trigger aktiviert wird, muss er die folgenden 5 Schritte ausführen:

  • Feststellen, ob die gerade ablaufende Task weiterlaufen soll; andernfalls …
  • Feststellen, welche Task als nächstes ausgeführt werden soll
  • Umgebung der Task speichern, die angehalten wurde (damit sie später weiter ausgeführt werden kann)
  • Ablaufumgebung für die als nächstes auszuführende Task bereitstellen
  • Ausführen dieser Task

Dieses Vorgehen wird auch als Task Switching bezeichnet.

(ID:44941011)