Embedded & Mikroprozessoren Timing ist alles

26.03.2013

Trotz hochleistungsfähiger Prozessoren sowie der Verfügbarkeit von Echtzeit-Versionen und Patches für universell einsetzbare Standard-Betriebssystemen können Embedded-Systeme nach wie vor nicht auf Echtzeitbetriebssysteme (RTOS) verzichten. Denn bestimmte Verhaltensweisen kann, unabhängig vom Prozessor, nur ein echtes RTOS garantieren.

In Embedded-Systemen sind die kurzen, genau definierbaren Reaktionszeiten eines Echtzeitbetriebssystems (RTOS, Real-Time Operating Systems) wichtiger als Schnelligkeit. Daneben vereinfacht ein RTOS auch viele technische Herausforderungen, speziell wenn mehrere Abläufe im System gleichzeitig um Ressourcen konkurrieren. Das kann etwa dann der Fall sein, wenn ein System in jedem Fall unmittelbar auf Benutzereingaben reagieren muss. Mit einem RTOS kann der Entwickler garantieren, dass die Reaktionen auf Eingaben vor allen anderen Aktivitäten durchgeführt werden - es sei denn, eine wichtigere Aktion muss (beispielsweise aus Sicherheitsgründen) noch früher ausgeführt werden. Ein Standard-OS teilt Threads und Prozesse der CPU im Normalfall „fair“ zu, um die Gesamtleistung des Systems zu optimieren. Die Priorität eines wichtigen Threads kann gegenüber anderen Threads im System herabgesetzt oder dynamisch angepasst werden. Dieser wird dann von niedriger priorisierten Threads unterbrochen.

Scheduling

Hingegen werden in einem RTOS die Threads streng nach Priorität ausgeführt. Wird ein High-Priority-Thread ausführbereit, übernimmt er innerhalb eines kleinen, begrenzten Zeitintervalls die CPU von einem niedriger priorisierten Thread. Der wichtigere Thread darf ohne Unterbrechung laufen, bis er seine Aufgabe erfüllt hat - es sei denn, er wird seinerseits von einem noch höher eingestuften Thread unterbrochen. Dieses „prioritätengesteuerte präemptive Scheduling“ stellt sicher, dass wichtige Threads ihre Deadlines hundertprozentig einhalten, auch wenn viele Threads um CPU-Zeit konkurrieren. Standard-OS haben meist keinen unterbrechbaren OS-Kernel: Selbst ein Thread mit hoher Priorität kann einen laufenden Systemaufruf (Kernel Call), mag er noch so unwichtig sein, unterbrechen, sondern muss auf dessen Beendigung warten. Das führt zu unkalkulierbaren Verzögerungen, und kritische Aktivitäten werden gegebenenfalls nicht rechtzeitig abgeschlossen. In einem RTOS hingegen können auch Kerneloperationen unterbrochen werden. Wie bei jedem Standard-OS gibt es Zeitfenster, in denen es keine Unterbrechung geben darf, doch die sind in einem RTOS extrem kurz (einige 100 ns). Durch Obergrenzen für nicht unterbrechbare Bereiche, in denen Interrupts gesperrt sind, können die Entwickler eine maximal mögliche Verzögerung bestimmen.

Zeitpartitionierung

In vielen Systemen ist die garantierte Verfügbarkeit von Ressourcen unabdingbar. Wenn einem kritischen Subsystem beispielsweise die CPU entzogen wird, ist dessen Funktionalität für den Anwender plötzlich nicht mehr verfügbar. Bei einer DoS(Denial-Of-Service-)Attacke könnte ein Angreifer ein System so lange mit Anfragen überschütten, die mit hoher Priorität behandelt werden müssen, bis das System unter der CPU-Last zusammenbricht und für den Anwender unbenutzbar wird. Aber nicht nur Sicherheitslücken können zur Überlastung führen. Häufig liegt es an zusätzlich implementierter Funktionalität, dass ein System überlastet wird und bestehende Software-Komponenten unzureichend CPU-Zeit erhalten. Bislang problemlos laufende Anwendungen und Services reagieren nach der Erweiterung dann nicht mehr oder nicht wie erwartet. Als Lösung brauchen Systemdesigner ein Partitionierungsschema, das mittels Hardware oder Software CPU-Zeitbudgets überwacht. Damit können einzelne Prozesse oder Threads nicht mehr alle CPU-Zyklen für sich monopolisieren. Das RTOS, das Prozessor, Speicher undandere Ressourcen zentral verwaltet, übernimmt diese Aufgabe. Manche Echtzeitbetriebssysteme bieten eine statische Zeitpartitionierung. Bei so einem Scheduler kann man die Tasks in Gruppen oder Partitionen einteilen und jeder Partition einen festen Prozentsatz der CPU-Zeit zuteilen. Bei diesem Ansatz kann kein Task in irgendeiner Partition mehr CPU-Zeit konsumieren als der Partition fest zugeteilt wurde. DoS-Angriffe wirken beschränkt, die anderen Prozesse können weiterhin ihre Aufgaben erfüllen. Das User-Interface bleibt verfügbar, und der Operator kann das Problem beheben, ohne das System komplett neu starten zu müssen. Der Nachteil dieser festen Zuteilung: Es wird Leistung verschwendet, und das System kann Peaks in der CPU-Nutzung nur gebremst abarbeiten. Das vermeidet die adaptive Zeitpartitionierung durch einen dynamischen Algorithmus. Wie beim statischen Partitionieren kann man hier CPU-Zeit für einen Prozess oder eine Gruppe von Prozessen reservieren. Somit kann man sicher sein, dass die Last auf einem Subsystem nicht die Verfügbarkeit des Gesamtsystems beeinträchtigt. Im Gegensatz zu statischen Ansätzen können hier aber dynamisch CPU-Zyklen von Partitionen mit wenig Last abgezogen werden, um sie solchen zuzuweisen, die mehr Rechenzeit benötigen. Zuteilungsbudgets werden nur dann erzwungen, wenn die CPU komplett ausgelastet ist. Somit kann man zum einen Höchstlasten bewältigen und 100 Prozent Ausnutzung erreichen, zum anderen hat man die Sicherheit garantiert zugeteilter CPU-Ressourcen. Von immensem Vorteil ist darüber hinaus, dass die adaptive Zeitpartitionierung ohne Codeänderungen auf ein bestehendes System aufgesetzt werden kann.

Zweiter Kernel

Standard-Betriebssysteme wie Windows, Linux und verschiedene UNIX-Systeme verfügen üblicherweise über keinen der bislang diskutierten Mechanismen. Deshalb haben Anbieter Echtzeiterweiterungen und Patches entwickelt, zum Beispiel ein Design mit zwei Kerneln, bei dem der Standard-Kernel als Task auf dem RTOS-Kernel läuft. Alle Tasks, die ein deterministisches Scheduling brauchen, laufen ebenfalls auf diesem Kernel, aber mit höherer Priorität als der Kernel des Standard-OS. Somit können diese Tasks den Standard-OS-Kernel jederzeit unterbrechen und sicherstellen, dass die CPU erst dann wieder freigegeben wird, wenn sie ihre Arbeit erledigt haben. Leider können die im Echtzeit-Kernel laufenden Tasks Services des Standard-OS, etwa Filesystem oder Netzwerk, nur eingeschränkt nutzen: Wenn ein im Echtzeit-Kernel laufender Task irgendeinen Service des Standard-OS aufruft, treten wieder genau die gleichen Probleme auf, die dafür sorgen, dass ein Standard-OS sich nicht deterministisch verhält. Somit müsste man speziell für den RTOS-Kernel neue Treiber und Services implementieren, auch wenn es diese Komponenten für das Standard-OS bereits gibt. Im Echtzeit-Kernel laufende Tasks profitieren auch nicht von der Robustheit einer MMU-Speicherverwaltung des Standard-OS. Somit kann ein Programmfehler in einem Echtzeit-Task, beispielweise ein falsch gesetzter C-Pointer, zu einem fatalen Absturz führen. Und meist sind Systeme, die Echtzeit benötigen, auf hohe Zuverlässigkeit angewiesen. Um es noch ein wenig komplizierter zu machen, verwenden unterschiedliche Implementierungen dieser „Zwei-Kernel-Lösung“ unterschiedliche APIs. In den meisten Fällen kann ein für das Standard-OS geschriebener Service nicht einfach auf den Echtzeit-Kernel portiert werden. Erneut zeigt sich: Ein um Echtzeitfähigkeiten erweitertes Standard-OS kommt an seine Grenzen, wenn es in deterministischen Umgebungen eingesetzt wird.

Erweiterung und Anpassung

Auch die Architektur eines RTOS spielt eine wichtige Rolle: Basiert das RTOS auf einem Mikrokernel-Design, werden Anpassung und Erweiterung von Betriebssystem-Funktionalität wesentlich einfacher als bei anderen Architekturen. Bei einem Mikrokernel liegt nur ein kleiner Teil der allerwichtigsten Services im Kernel (Signale, Timer, Scheduler). Alle anderen Komponenten, wie Treiber, Dateisystem, Netzwerkprotokolle, Anwendungen laufen außerhalb des Kernels als separate, speichergeschützte Prozesse. Somit benötigt man für die Entwicklung eigener Treiber oder sonstiger Betriebssystem-Erweiterungen weder Kernel-Debugger noch spezifisches Wissen über den Kernel. Tatsächlich lassen sich solche Erweiterungen genau so einfach wie normale Anwendungen entwickeln, da sie mit normalen Source-Level Debuggern und Werkzeugen entwickelt werden können.

Verwandte Artikel