Systemplattformen Betriebssystem: mit oder ohne?
Jede Laufzeitarchitektur enthält eine Art von „Betriebssystem“, auch wenn es nur der Kernteil zur Ablaufsteuerung ist – der Scheduler. Somit ist die Antwort auf die obige Frage klar. Dieser Beitrag geht einen Schritt weiter und beantwortet die Frage: Welche Art von Laufzeitarchitektur ist für meine Applikation am besten geeignet?
Anbieter zum Thema

Dazu stellt dieser Beitrag verschiedene Laufzeitarchitekturen mit ihren Designaspekten vor und zeigt Applikationskriterien, die zur richtigen Auswahl führen. Am Ende steht die Eingliederung der Laufzeitarchitektur in die gesamte Softwarearchitektur im Mittelpunkt.
Die komplette und aktuelle Dokumentation zu diesem Thema ist unter [1] für Sie bereitgestellt.
Sequentielles Endless-Loop Scheduling
Die einfachste Laufzeitarchitektur basiert auf einer Endlosschleife, die der Rechenknoten kontinuierlich ausgeführt (Bild 1 in der Bildergalerie). Darin enthalten sind die Aufrufe der für den Ablauf erforderlichen Funktionen. Jede Funktion hat eine Ausführungszeit texe. Aus der Summe aller Ausführungszeiten ergibt sich bei exklusiver unterbrechungsloser Funktionsausführung die Schleifenzeit tloop, die der Aufrufperiodizität tp der einzelnen Funktionen entspricht. Der Rechenknoten führt die Funktionen in der Ausführungsebene 0 (Applikation) aus. Zum genaueren Architekturdesign sind Aspekte aus Tabelle 1 (vgl. Bildergalerie) interessant.
Unter Berücksichtigung der in Tabelle 1 genannten Designaspekte kann eine Funktion zur Laufzeit die im Bild 2 (vgl. Bildergalerie) dargestellten Zustände annehmen und Transitionen durchlaufen. READY: Dieser Zustand ist für das Endless-Loop Scheduling optional. Die Funktionen sind zur Ausführung vorbereitet (z.B. in einer Ausführungsliste eingetragen) oder während der Ausführung unterbrochen worden, z.B. bei zusätzlicher Zeitsteuerung. Aus dieser Zustandsbetrachtung könnte sich der in Bild 3 (vgl. Bildergalerie) gezeigte exemplarische Ablauf über der Zeit ergeben.
In diesem Ausführungsbeispiel sind verschachtelte Interrupts zulässig, und es ist keine Zeitsteuerung (Time-Slicing) enthalten. Bis zum Zeitpunkt t1 führt der Rechenknoten die Function-1 aus. Die Zeit von t1 bis t4 ist die Ausführungszeit der Function-2, wobei die Interrupt-Service-Routine-1 diese einmal unterbricht. In der Zeit von t4 bis t9 führt der Rechenknoten die Function-3 aus. Dabei erfolgt eine Unterbrechung durch die Interrupt-Service-Routine-2, die selbst nochmals durch die höher priorisierte Interrupt-Service-Routine-3 unterbrochen wird. Ab dem Zeitpunkt t9 beginnt ein neuer Schleifendurchlauf.
Applikationen, für die das Endless-Loop-Scheduling eine geeignete Laufzeitarchitektur ist, sollten die folgenden wichtigen Kriterien erfüllen:
- Einfachheit
- Kontinuierliche Funktionsausführung ist sinnvoll
- Funktionen haben wenige Abhängigkeiten untereinander
- (Quasi) parallele Funktionsausführung bringt keine Vorteile
- Wiederausführungsfrequenz einzelner Funktionen ist unkritisch
- Nur synchrone Funktionen
- Keine zukünftig geplanten Software-Erweiterungen
Time-Triggered Scheduling
Beim Time-Triggered Scheduling führt der Rechenknoten zeitgesteuert periodisch einzelne Tasks und darin enthaltenen Funktionen aus. Dabei sind unterschiedliche Task-Periodizitäten tp möglich, denen die entsprechenden Funktionen zugeordnet sind. Die Task mit der höheren Periodizität kann die mit einer niederen unterbrechen. Durch ergibt sich bereits eine Art Priorisierung (vgl. Bild 4 in der Bildergalerie).
Wenn die Rechenauslastung < 100% ist und alle anderen Tasks bereits abgearbeitet sind, führt der Rechenknoten die Idle-Task aus. Die Idle-Task kann nahezu beliebigen Programmcode enthalten. Zum genaueren Architekturdesign sind Aspekte aus Tabelle 2 (vgl. Bildergalerie) interessant. Unter Berücksichtigung der in Tabelle 2 genannten Designaspekte kann eine Time-Triggered Task zur Laufzeit die im Bild 5 (vgl. Bildergalerie) dargestellten Zustände annehmen und Transitionen durchlaufen.
NOT CREATED: Eine oder mehrere Tasks sind lediglich im Programmspeicher enthalten.
RUNNING: Der Rechenknoten führt aktuell genau eine Task aus.
READY: Die Tasks sind zur Ausführung vorbereitet oder während der Ausführung durch eine andere Task unterbrochen worden.
Für den koordinierten Ressourcenzugriff kann der Priority Ceiling Algorithmus zum Einsatz kommen. Dieser verhindert, dass eine Task auf eine nicht verfügbare Ressource warten muss. Aus dieser Zustandsbetrachtung könnte sich der in Bild 6 (vgl. Bildergalerie) gezeigte exemplarische Ablauf über der Zeit ergeben.
Die Zeitsteuerung erfolgt typischerweise getriggert durch Hardware-Timer und deren Interrupts. Der Rechenknoten führt Task-A alle 50µs erneut aus. Das Zeitraster ist fix. Dabei kann die Task-A die Task-B (fixes Zeitraster von 200µs) in ihrer Ausführung unterbrechen. Sobald ein Interrupt mit einer höheren Priorität als die Timer-Interrupts auftritt, kann dieser die Tasks jederzeit unterbrechen.
Applikationen, für die das Time-Triggered Scheduling eine geeignete Laufzeitarchitektur ist, sollten die folgenden wichtigen Kriterien erfüllen:
- Unterschiedliche Aufgaben mit derselben Periodizität
- Unterschiedliche Aufgaben mit unterschiedlicher Periodizität
- Kontinuierliche Aufgabenausführung ist sinnvoll
- Aufgaben haben wenige Abhängigkeiten untereinander
- Nur synchrone Funktionen
- Genaue Zeitanforderungen
- Hohe Performance-Anforderungen
- Hoher Grad an Vorhersagbarkeit / Berechenbarkeit
Priority Scheduling
Der Scheduler entscheidet anhand der den Tasks zugeordneten Prioritäten p, welche Task der Rechenknoten ausführt. Er stellt für die Task-Priorisierung den Prioritätsbereich priority_range mit einem minimalen und maximalen Wert bereit. Tasks sind entweder präemptiv (verdrängbar), d.h. der Scheduler kann sie jederzeit unterbrechen und eine Task mit höherer Priorität ausführen.
In Ausnahmefällen kann eine Task eventuell auch als nicht-verdrängbar definiert werden, wodurch sie in eine Art Exklusivbetrieb kommt. Wenn die Rechenauslastung < 100% ist und alle anderen Tasks bereits abgearbeitet sind, führt der Rechenknoten die Idle-Task aus. Die Idle-Task hat damit die niedrigste Priorität und kann nahezu beliebigen Programmcode enthalten (vgl. Bild 7 in der Bildergalerie).
Zum genaueren Architekturdesign sind Aspekte aus Tabelle 3 (vgl. Bildergalerie) interessant. Unter Berücksichtigung der in Tabelle 3 genannten Designaspekte kann eine Task zur Laufzeit die im Bild 8 (vgl. Bildergalerie) dargestellten Zustände annehmen und Transitionen durchlaufen.
NOT CREATED: Eine oder mehrere Tasks sind lediglich im Programmspeicher enthalten.
RUNNING: Der Rechenknoten führt aktuell genau eine Task aus.
READY: Die Tasks sind zur Ausführung vorbereitet oder während der Ausführung durch eine andere, höher priorisierte Task unterbrochen worden. Die Tasks warten hier auf die Zuteilung der Rechenzeit durch den Scheduler.
WAITING: Abhängig vom tatsächlichen Laufzeitsystem sind verschiedene wartende Mechanismen erlaubt, die das Laufzeitsystem selbst überwacht. Damit kann der Scheduler während der Wartezeit andere Tasks ausführen. Bei Eintritt dessen, worauf eine Task wartet, führt der Scheduler unmittelbar automatisch die Task-Transition nach READY durch, bewertet die Tasks im READY Zustand neu und führt bei Erfordernis ein einen Task-Wechsel im RUNNING Zustand durch.
Manche Scheduler bieten weitere Zustände (z.B. SUSPENDEND, DORMANT) mit den entsprechenden Transitionen, und teilweise haben gleichbenannte Zustände in verschiedenen Laufzeitsystemen unterschiedliche Bedeutungen. Aus dieser Zustandsbetrachtung könnte sich der in Bild 9 (vgl. Bildergalerie) gezeigte exemplarische Ablauf über der Zeit ergeben.
Bis zum Zeitpunkt t1 führt der Rechenknoten die Task-A mit der höchsten Priorität aus. Ab dem Zeitpunkt t1 muss die Task-A auf etwas noch nicht Verfügbares warten. Der Scheduler transferiert die Task-B aus dem READY-Zustand nach RUNNING. Zwischen t2 und t3 warten Task-A und Task-B. In dieser Zeit führt der Rechenknoten die Idle-Task aus. In der Zeit zwischen t4 und t5 unterbricht die Interrupt-Service-Routine-1 das Scheduling komplett. Task-A beendet sich zum Zeitpunkt t6 und der Scheduler transferiert die Idle-Task wieder in den RUNNING-Zustand, bis der Scheduler zum Zeitpunkt t7 die Task-B von WAITING nach READY und damit unmittelbar nach RUNNING transferiert.
Applikationen, für die das Priority Scheduling eine geeignete Laufzeitarchitektur ist, sollten die folgenden wichtigen Kriterien erfüllen:
- Komplexe Aufgaben
- Hoher Grad an Aufgaben-Abhängigkeiten und Interaktionen
- Asynchrone Aufgaben
- (Quasi) parallele Aufgabenausführung bringt Vorteile
- Wenige Zeitanforderungen
- Zukünftige Erweiterbarkeit
- Integration von anderen / weiteren Software-Komponenten
Priority und Time-Slice Scheduling
Das Priority Scheduling ist für den Fall der gleichpriorisierten Tasks mit einem Time-Slice-/ Round-Robin-Verfahren erweiterbar. Dabei führt der Scheduler die gleich priorisierten Tasks zeitgesteuert aus. Diese Zeitsteuerung basiert auf dem Systemtick.
Dieser ist durch einen in der Zeit konfigurierbaren Hardware-Timer und dessen Interrupt realisiert. Typische Konfigurationswerte des Systemticks liegen zwischen 1ms und 500ms. Alle gleichpriorisierten Tasks könnten per Konfiguration die gleiche Ausführungszeit tSlice (z.B. einen Systemtick) zugeordnet bekommen. Alternativ dazu könnte jede Task mit einer individuellen Ausführungszeit tSlice als Einfaches oder Vielfaches des Systemticks konfiguriert werden.
Zum genaueren Architekturdesign sind Aspekte aus Tabelle 4 (vgl. Bildergalerie) interessant. Die in Bild 8 (vgl. Bildergalerie) für das Priority Scheduling bereits dargestellten Zustände und Transitionen gelten auch für die Erweiterung um das Time-Slice Scheduling. Aus dieser Zustandsbetrachtung könnte sich der in Bild 11 (vgl. Bildergalerie) gezeigte exemplarische Ablauf über der Zeit ergeben.
Bis zum Zeitpunkt t4 wartet die höchst priorisierte Task-A auf etwas noch nicht Vorhandenes. Der Rechenknoten führt die gleichpriorisierten Tasks B-D mit dem Time-Slice-Verfahren aus. Die Task-B war zeitlich gesehen zuerst READY, damit transferiert der Scheduler diese zuerst in den RUNNING-Zustand (FIFO-Prinzip). Die Laufzeit beträgt für alle Tasks einen Systemtick. Mit jedem Systemtick findet ein Re-Scheduling statt. Zum Zeitpunkt t4 tritt das ein, worauf die Task-A wartet. Der Scheduler unterbricht das Time-Slice-Verfahren und transferiert die Task-A in den RUNNING-Zustand, bis sich diese zum Zeitpunkt t5 beendet. Der Scheduler nimmt das Time-Slice-Verfahren dort wieder auf, wo er es zuvor unterbrochen hatte.
Applikationen, für die das Priority und Time-Slice Scheduling eine geeignete Laufzeitarchitektur ist, sollten die folgenden wichtigen Kriterien erfüllen:
- Komplexe Aufgaben
- Hoher Grad an Aufgaben-Abhängigkeiten und Interkationen
- Asynchrone Aufgaben
- (Quasi) parallele Aufgabenausführung bringt Vorteile
- Wenige Zeitanforderungen
- Zukünftige Erweiterbarkeit
- Integration von anderen / weiteren Software-Komponenten
- Zeitgesteuerte Aufgaben
Time-Triggered und Priority Scheduling
Die bereits getrennt vorgestellten Time-Triggered und Priority Scheduling Verfahren sind kombinierbar. Das Laufzeitsystem führt das Time-Triggered Scheduling dominierend aus. Gibt es hierbei keine Time-Triggered Tasks mehr zur Ausführung, führt der Rechenknoten anstatt der bisherigen Idle-Task das Priority Scheduling für die Prioritäten basierenden Tasks aus. Mit dem Priority Scheduling gibt es nun auch wieder die Idle-Task. Alternativ dazu könnte der Scheduler in einer speziellen Time-Triggered Task das Priority Scheduling ausführen.
Zum genaueren Architekturdesign sind die bereits in Tabelle 2 und 3 aufgeführten Aspekte interessant. Unter Berücksichtigung dieser Designaspekte kann eine Time-Triggered und eine priorisierte Task zur Laufzeit die im Bild 13 (vgl. Bildergalerie) dargestellten Zustände annehmen und Transitionen durchlaufen.
Die Zustände und Transitionen sind durch vorangehende Erklärungen bereits bekannt. Die Time-Triggered Task nimmt keinen WAITING-Zustand an. Aus dieser Zustandsbetrachtung könnte sich der in Bild 14 (vgl. Bildergalerie) gezeigte exemplarische Ablauf über der Zeit ergeben.
Der Scheduler führt die Task-A mit einer Periodizität tp von 100ms zyklisch aus. In der verbleibenden Zeit führt der Rechenknoten das Priority Scheduling aus.
Applikationen, für die das Time-Triggered und Priority Scheduling eine geeignete Laufzeitarchitektur ist, sollten die folgenden wichtigen Kriterien erfüllen:
- Die Aufgaben sind in harte und weiche Echtzeit unterteilbar. Harte Echtzeit bedeutet, dass in 100% aller Fälle die Zeitvorgaben einzuhalten sind; bei weicher Echtzeit sind es < 100%.
- Für die Aufgaben mit harten Echtzeitanforderungen kommt das Time-Triggered Scheduling zum Einsatz.
- Für die Aufgaben mit weichen Echtzeitanforderungen kommt das Priority Scheduling zum Einsatz.
Earliest-Deadline-First Scheduling
Beim Earliest-Deadline-First Scheduling kennt der Scheduler die Deadlines aller Tasks. Darauf basierend entscheidet er zur Laufzeit, welche der Tasks der Rechenknoten als nächstes ausführt. Die Deadlines jeder Task sind statisch, beispielsweise über eine Tabelle, dem Scheduler vorgegeben (vgl. Bild 15 in der Bildergalerie).
Theoretisch wäre eine Berechnung der Deadlines zur Laufzeit denkbar, aber praktisch ist der Rechenaufwand dazu zu hoch. Für den Fall, dass der Scheduler keine weiteren Tasks zur Ausführung bereit hat, führt der Rechenknoten die Idle-Task aus.
Zum genaueren Architekturdesign sind Aspekte aus Tabelle 5 (vgl. Bildergalerie) interessant. Unter Berücksichtigung der in Tabelle 5 genannten Designaspekte kann eine Task zur Laufzeit die im Bild 16 (vgl. Bildergalerie) dargestellten Zustände annehmen und Transitionen durchlaufen.
Der WAITING-Zustand macht das Scheduling wesentlich komplexer und erschwert oder verhindert im schlimmsten Fall das Einhalten der Deadlines. Somit ist der WAITING-Zustand eingeschränkt (nur wenige Wartefunktionalitäten) oder nicht verfügbar.
Für den koordinierten Ressourcenzugriff vermeidet der Priority Ceiling Algorithmus, dass eine Task auf eine nicht verfügbare Ressource warten muss.
Aus der Betrachtung der Zustände könnte sich der in Bild 17 (vgl. Bildergalerie) gezeigte exemplarische Ablauf über der Zeit ergeben.
Zum Zeitpunkt t1 kreiert die aktuell ausgeführte Task-A die Task-B. Da die Task-B zum Zeitpunkt t4 die nächste Deadline hat, verdrängt der Scheduler die aktuell ausgeführte Task-A in den READY-Zustand und bringt die Task-B in den RUNNING-Zustand, bis sie sich selbst bei t2 beendet. Somit hält der Scheduler für die Task-B die vordefinierte Deadline tB ein.
Nachdem die Task-A wieder im RUNNING-Zustand war, muss diese auf etwas noch nicht Vorhandenes warten (z.B. Semaphore), was die folgende Interrupt-Service-Routine liefern wird. Während dieser Wartezeit führt der Rechenknoten die Idle-Task aus. Nach der Abarbeitung des Interrupts in der Zeit von t5 bis t6 führt der Rechenknoten wieder die Task-A aus, bis sie sich selbst zum Zeitpunkt t7 beendet. Die Deadline ist erst bei t8, somit hält auch Task-A ihre vordefinierte Deadline tA ein.
Applikationen, für die das Earliest-Deadline-First-Scheduling eine geeignete Laufzeitarchitektur ist, sollten die folgenden wichtigen Kriterien erfüllen:
- Kleine und wenige Aufgaben
- Für jede Aufgaben definierbare Deadlines
- Die Deadlines müssen eingehalten werden
- Wenige Abhängigkeiten und damit Interaktionen zwischen den Aufgaben
- Identifizierbarer (statischer) Ablaufplan mit Einhaltung aller Deadlines
Embedded-Softwarearchitektur
Das Software-Schichtenmodell einer Embedded-Softwarearchitektur könnte beispielsweise wie die in Bild 18 (vgl. Bildergalerie) aussehen.
Unten über der physikalischen Hardware sind die Peripherietreiber mit der darüber liegenden Geräte-Abstraktion enthalten. In Softwaresystemen mit einem ausgeprägten Mensch-Maschine-Interface (MMI) könnten die obersten Schichten, die MMI-Schicht und darunterliegend mindestens eine Applikationsschicht enthalten sein.
In der hier dargestellten Architektur ist nun weiterführend und herausfordernd die Idee, jede mögliche Art von Laufzeitarchitektur (Laufzeitsystem) mit der Schicht „Run-Time Environment Abstraction“ für die darüber liegenden Schichten zu abstrahieren.
Diese Schicht ist mit der klassischen Schicht OSAL (Operating System Abstraction Layer) vergleichbar. Hierin kann viel Intelligenz enthalten sein, um die Nutzung der Laufzeit-Systems möglichst einfach zu halten. Zusammen mit C++ Applikationen dient diese Schicht gleichzeitig als Wrapper zwischen C nach unten und C++ nach oben hin [2].
Somit könnte die Schicht „Run-Time Environment“ im Austausch alle hier vorgestellten Scheduler und andere Mechanismen enthalten, eventuell auch als Zukaufprodukte verschiedenster Hersteller. Die Applikation ist so auf verschiedensten Laufzeitarchitekturen ausführbar.
Resümee
Es gibt nicht die beste Laufzeitarchitektur, sondern nur die, die bestmöglich die Software-Anforderungen erfüllt.
Referenzierte und weiterführende Links:
[1] MicroConsult-Download für komplette und aktuelle Dokumentation ^
http://download.microconsult.net/ese2015/laufzeitarchitektur.zip
[2] MicroConsult Download: OSAL mit C++
http://download.microconsult.net/ese2014/osal.zip
[3] MicroConsult-Trainings:
„Moderne Software-Architekturen für Embedded- und Echtzeitsysteme“
http://www.microconsult.de
„RTOS-Mechanismen und deren Anwendungen in Laufzeitarchitekturen von Embedded- und Echtzeitsoftware“
http://www.microconsult.de
* Dipl.-Ing. (FH) Thomas Batt arbeitet seit 1994 kontinuierlich in verschiedenen Branchen im Bereich Embedded-/Real-Time Systementwicklung. 1999 wechselte Thomas Batt zur MicroConsult GmbH. Dort verantwortet er heute als zertifizierter Trainer und Coach die Themenbereiche Systems /Software Engineering für Embedded-/Real-Time-Systeme sowie Entwicklungsprozess-Beratung.
(ID:44188767)