"Realtime Unix" verbindet die Vorteile von Unix und Echtzeit:

Echtzeit-Rechensysteme verlangen Unix einiges ab

16.12.1988

Unix ist ursprünglich nur für Anwendungen im Bereich Multitasking und Timesharing entwickelt worden. Als Betriebssystem sollte es möglichst viele Benutzer gleichzeitig optimal bedienen. Ganz andere Ziele werden jedoch von Echtzeit-Rechensystemen verfolgt.

Welche Anforderungen dabei erfüllt werden müssen, beschreibt Erhard Pompe im folgenden Beitrag.

Optimierungsziele bei Unix waren die bestmögliche Auslastung aller vorhandenen Betriebsmittel und eine gerechte Verteilung dieser Ressourcen an alle Benutzer entsprechend ihren Anforderungen.

Zur Lösung dieser Aufgabe bedienen sich moderne Systeme komplexer Algorithmen. Grundlage der meisten Methoden ist die Vergabe des knappsten Betriebsmittels, der CPU. Jeder Anwenderprozeß erhält einen bestimmten Anteil der verfügbaren CPU-Zeit am Stück - Timesharing. Die Zeitscheibe wird so gewählt, daß die Unterbrechungen für den Anwender möglichst nicht sichtbar werden (kleine Zeitscheibe), aber die Anzahl der Prozeßwechsel niedrig bleibt (große Zeitscheibe), um den

Overhead zu minimieren. Da die Anforderungen der Anwenderprogramme (benötigte CPU-Zeit, maximale Wartezeit, Plattenzugriffe und ähnliches) sehr verschieden sind, können

die Zeitscheiben pro Programm gedehnt oder verkürzt werden. Um interaktive Programme nicht warten zu lassen, wird das System Prioritäten für Programme und Anwender

vergeben. Ein gutes Timesharing-System berechnet die Priorität und Zeitscheibe pro Programm dynamisch als Funktion des Anforderungsprofils (CPU-Zeit, E/A-Verhalten) dieses Programmes in den letzten Sekunden. Der Systemoverhead für die optimale Verwaltung der Ressourcen im Multi-User-Betrieb kann bei bis zu 50 Prozent der verfügbaren CPU-Kapazität liegen, mit der unangenehmen Tendenz, bei wachsender Systembelastung überproportional zu steigen.

Definition eines Echtzeit-Rechnersystems

Echzeitfähig ist ein Rechner wenn er in der Lage ist, unabhängig von der aktuellen Systembelastung, in vorhersehbarer Weise rechtzeitig auf Ereignisse aus der realen Welt zu reagieren. Um diese Anforderung zu erfüllen, müssen je nach Anwendung mehrere oder alle der folgenden Voraussetzungen erfüllt werden:

- Ereignisse (Überschreiten eines Grenzwertes) müssen innerhalb einer vorgegebenen Zeitspanne erkannt werden und der zugeordnete Prozeß die Kontrolle über die CPU erhalten (Reaktionzeit).

- Daten, zum Beispiel Meßwerte müssen mit einer vorgegebenen Frequenz aufgenommen, in digitale Daten umgewandelt, verarbeitet und gespeichert werden, ohne ein Datum zu verlieren (schritthaltende Verarbeitung, Datendurchsatz).

- Innerhalb einer vorgegebenen Zeitspanne muß die Ereignisbearbeitung abgeschlossen sein, beispielsweise müssen Daten zur Steuerung des realen Prozesses ausgegeben werden (deterministisches Verhalten).

- Alle drei Forderungen müssen unabhängig von der aktuellen Systembelastung erfüllt werden.

Diese Forderungen können nur erfüllt werden, wenn eine geeignete Hardwareumgebung geschaffen wird und ein Echtzeitbetriebssystem implementiert ist. Concurrent Computer bietet mit seiner Rechnerfamilie 6000 (MASSCOMP) und dem Betriebssystem RTU (RealTime Unix) erstmals ein Echtzeitbetriebssystem an, das für Unix die oben genannten Forderungen erfüllt. Im folgenden werden wichtige der wesentlichen Hardware- und Betriebssystem-Voraussetzungen vorgestellt, die zum Erreichen der Echtzeitfähigkeit erforderlich waren.

Das Betriebssystem Unix stellt dem Anwender eine Vielzahl von Diensten und Dienstprogrammen sowie eine zigtausendfach bewährte Teilnehmerumgebung zur Verfügung. Unix ist ursprünglich nur für Anwendungen im Bereich Multitasking und Timesharing entwickelt worden. Dadurch hat das Betriebssystem in seiner Standardimplementierung weder die Reaktionszeit noch den Datendurchsatz, die bei Echtzeitanwendungen benötigt werden. Die Strategien der Betriebsmittelvergabe schließen sich für Teilnehmer- und Echtzeitbetrieb meist wechselseitig aus. Beide Strategien können aber in einem Betriebssystem koexistieren, wenn der Echtzeitverarbeitung absoluter Vorrang gegeben wird. Die Unix-Implementierung RTU wurde modifiziert, ergänzt und erweitert. Sie unterstützt sowohl Multiprozessor- als auch Echtzeitanwendungen. Dabei sind die Vorteile der Unix-Standardisierung und Programmportabilität nicht verlorengegangen.

Laut Prüfungsergebnis der System V Verifikation Suite (SVVS) von AT&T hält sich das Betriebssystem RTU (Release 4. 0) genau an die System V Interface Definition (SVID) Darüber hinaus unterstützt RTU viele Funktionen des 4.2 BSD der University of California, Berkeley, umfaßt separate AT&T und Berkeley-Entwicklungsumgebungen. AT&T-SVID-kompatible Programme werden in der AT&T-Umgebung übersetzt, 4.2-BSD-kompatible Programme in der Berkeley-Umgebung. Der generierte Code kann in beiden Umgebungen ausgeführt werden.

Echtzeiterweiterungen des Betriebssystems RTU

Um die Reaktionszeit zu verkürzen, wurden folgende Fähigkeiten im RTU implementiert:

- Prozessorvergabe nach festen Prioritäten,

- Speicherresidente Prozesse,

- Process Preemtion und

- Kernel Preemtion.

Prozessorvergabe nach festen Prioritäten

Im Standard-Unix ändert der Kernel 15mal pro Sekunde die Prioritäten aller Prozesse, dabei werden E/A-intensive Prozesse im Vergleich zu rechenintensiven begünstigt. Der Benutzer kann die Priorität mit dem Nice-Kommando beeinflussen. Der Nice-Wert ist jedoch nur einer unter vielen Faktoren, die zusammen das Scheduling bestimmen. Da dies für Echtzeitanwendungen nicht akzeptabel ist, wurde die Verwaltung der Prioritäten in RTU erweitert (Bild 1). Die Prioritäten -10 bis -20 sind als Echtzeit-Prioritäten definiert und werden vom Kernel nicht dynamisch verändert. Einem Echtzeit-Prozeß kann die CPU nur durch einen Echtzeit-Prozeß höherer Priorität entzogen werden, nicht aber aufgrund des Ablaufs einer Zeitscheibe.

RTU unterstützt ein virtuelles Speicherkonzept. Der virtuelle Speicher beziehungsweise Adreßraum erleichtert das Programmieren, die Ausführungszeiten werden jedoch durch den Paging- und Swappingbetrieb unvorhersagbar. In einer Echtzeitumgebung ist es nicht möglich, nach dem Auftreten eines externen Ereignisses das zugeordnete Programm oder den zugeordneten Programmteil in den Hauptspeicher zu laden. Es ist daher erforderlich, Echtzeitprozesse, oder zumindest Teile davon, im Hauptspeicher festzusetzen. Mit dem PLOCK-System Call von System V kann man das Text-Segment und/oder das Data-Segment im Hauptspeicher festsetzen. Mit dem PLOCKIN-System Call des RTU kann man individuelle Seiten eines Prozesses als hauptspeicherresident vereinbaren. Dies erlaubt eine feinere und gezieltere Kontrolle sowie eine optimale Ausnutzung des physikalischen Speichers.

In Unix wird einem laufenden Prozeß die CPU erst nach Ablauf seiner Zeitscheibe, oder wenn er einen Wartezustand (E/A) erreicht, entzogen. Ein Echtzeit-Verhalten des Betriebssystems kann nur erreicht werden, wenn ein Prozeß höherer Priorität, sobald er rechenbereit ist, vorgezogen wird. Einem laufenden Prozeß niedriger Priorität muß die CPU vor Ablauf der Zeitscheibe (Preemtion) entzogen werden. In RTU wird diese Preemtion sofort vollzogen, es sei denn, der Prozeß niedriger Priorität führt einen System Call aus. In diesem Fall muß der Prozeß höherer Priorität bis zum Ende des System Calls oder bis zum Erreichen eines Kernel Preemtion Point warten.

Beim Standard-Unix darf keinem Prozeß, der einen System Call ausführt, der Prozessor entzogen werden. Selbst wenn ein System Call niedriger Priorität aufgerufen wurde, läuft er, bis er beendet ist oder sich selbständig blockiert. In einigen Fällen kann dies bis zu einer Sekunde dauern. Dies ist in einer Echtzeitumgebung nicht tragbar. In RTU wurden in den Kernel-Routinen über 1000 Preemtion Points, an denen ein System Call abgegeben werden kann, ohne bis zum Ende laufen zu müssen, eingebaut. Die Zeit bis zum Erreichen eines Kernel Preemtion Point wird Kernel Preemtion Latency genannt. In RTU 4.0 beträgt diese Verzögerung maximal drei Millisekunden. Diese Erweiterungen sind erforderlich, um eine determinierte Antwortzeit zu erhalten.

Eine weitere wichtige Eigenschaft von Echtzeitsystemen sind die Systemdienste zur Interprozeßkommunikation. RTU enthält die Funktionen des Systems V - einschließlich der Signale, shared memory, Semaphoren, messages und named pipes. Zusätzlich sind Kommunikationsfunktionen aus 4.2 BSD - zum Beispiel Sockets - in RTU implementiert. Alle Funktionen lassen jedoch eine gewünschte schnelle und effektive Interprozeßkommunikation für Echtzeit-Anwendungen nicht im gewünschten Umfang zu. Dazu wurden die Asynchronous System Traps (AST) geschaffen.

Funktionen zum Datenaustausch zwischen Prozessen

þnamed pipes: Zwischen beliebigen Prozessen können Daten ausgetauscht werden. Die Daten werden nach dem FIFO-Prinzip verwaltet. Pipes sind in RTU im Hauptspeicher statt auf der Platte implementiert, um den Datendurchsatz wesentlich zu erhöhen.

þmessages: Im Gegensatz zu den Daten in pipes werden Nachrichten mit Prioritäten versehen und entsprechend ihrer Priorität in einer Warteschlange bereitgestellt. Der Empfängerprozeß wird nicht informiert - etwa durch ein Signal -, wenn ihn eine Nachricht erreicht.

þshared memory: Mehrere Prozesse können das gleiche physikalische Speichersegment in ihren logischen Adreßraum einbinden. Jeder Prozeß kann auf diesen Speicher zugreifen - shared memory -, und die Prozesse können Daten ohne zusätzlichen Speichertransfer eintauschen.

Funktionen zur Synchronisation von Prozessen:

Signale

Ein Signal ist eine simulierte Unterbrechung (Betriebssystemsoftware). Ein Prozeß kann jedem Signal eine spezifische Service-Routine zuordnen. Wird das Signal empfangen, unterbricht Unix die aktuelle Befehlssequenz, führt die zugeordnete Signal-Service-Routine aus und setzt die unterbrochene Befehlssequenz fort. Signale bezeichnen im allgemeinen Ereignisse oder Alarme. Ein Prozeß schickt einem anderen Prozeß ein Signal, um ein Ereignis anzuzeigen. Unix schaltet den anderen Prozeß zu seiner Signalservice-Routine, um auf dieses Ereignis angemessen zu reagieren. In Unix haben Signale jedoch wesentliche Einschränkungen:

- Sie liefern keine zusätzliche Information, nicht einmal, von welchem Prozeß das Signal abgeschickt wurde.

- Sie werden nicht in einer Warteschlange verwaltet. Treffen mehrere Signale in schneller Folge ein, können sie verlorengehen.

- Sie sind in ihrer Anzahl begrenzt (32).

Asynchronous System Traps (ASTs)

ASTs sind, wie Signale, Software-Unterbrechungen. Sie erfüllen jedoch die Anforderungen, die an Signale in einer Echzeitumgebung gestellt werden:

- Ihre Anzahl ist nicht begrenzt.

- Ihnen können verschiedene Prioritäten zugewiesen werden.

- ASTs werden in einer Warteschlange verwaltet und nach Priorität abgearbeitet.

- ASTs können auch von Gerätetreibern erzeugt werden, um den Abschluß einer asynchronen (no wait) E/A anzuzeigen.

- ASTs gehen nicht verloren. Wird während der Bedienung eines ASTs ein weiterer AST empfangen, wird entweder (neuer AST hat höhere Priorität) die aktuelle Service-Routine unterbrochen, die zugeordnete Service-Routine ausgeführt und anschließend die unterbrochene Service-Routine fortgesetzt oder (neuer AST hat niedrigere Priorität) der AST in die Warteschlange einsortiert.

- Ein AST liefert ein 32-Bit-Wort als zusätzliche Information. Die Interpretation dieses Wertes wird zwischen Sender und Empfänger frei vereinbart. Es kann zum Beispiel die Adresse eines Parameterblocks im Shared Memory sein, um auf diese Art ein Nachrichtensystem mit asynchroner Notifikation zu implementieren.

Neben einer optimalen Interprozeßkommunikation erfordern Echtzeitanwendungen eine sehr schnelle Ein-/Ausgabe, insbesondere zu den Plattenlaufwerken.

Echtzeitanwendungen, die kontinuierlich Meßdaten erfassen und auf Platte speichern, erfordern einen sehr hohen Datendurchsatz. RTU bietet Contigous Files (zusammenhängende Plattensegmente) für schnellstmöglichen Datenzugriff auf die Platte an. Das Unix-File-System teilt die Platte in eine Vielzahl gleich großer Blöcke (meist 512 KByte) auf. Üblicherweise wird Plattenspeicher erst vergeben, wenn Datenblöcke geschrieben werden.

Die Daten kommen in getrennte Blöcke, die über die ganze Platte verstreut sind. Daraus ergeben sich auch bei sequentiellem Zugriff hohe Suchzeiten. Darüber hinaus werden die meisten Datenblöcke mittels Zeiger gelesen und geschrieben, weshalb mehrere Zugriffe benötigt werden, um einen Datenblock zu lesen oder zu schreiben.

Nur die Zeiger für die ersten zehn Blöcke befinden sich im Inode. Die Zeiger für die nachfolgenden 128 Datenblöcke werden in einem Block auf der Platte gespeichert, dessen Zeiger sich im lnode befindet. Für die nächsten 16 385 Blöcke sind die Zeiger in 128er Blöcken, deren Zeiger in einem Block sind, von dem sich der Zeiger im Inode befindet. Falls eine Datei über 16 522 Blöcke (etwa 8 MByte) hinauswächst, gibt es eine dritte Ebene von indirekten Zeigern, die eine Ausdehnung bis zu einem Gigabyte benötigt. Um Daten in den Blöcken 139 bis 15 522 zu erreichen, sind vier, darüber hinaus fünf Plattenzugriffe erforderlich. Obwohl das Unix-Puffersystem (Disc-Cache) die Datei-Verzeichnis-Blöcke üblicherweise im Hauptspeicher hält, sind Verwaltungsaufwand und vor allem zeitliche Unbestimmtheit immer noch für einige Echtzeitanwendungen nicht akzeptabel.

In RTU sind deshalb physikalisch zusammenhängende Dateien (Contigous Files) implementiert. Sie belegen ausschließlich Plattenblöcke in sequentieller Folge, wodurch Kopfbewegungen und Suchzeiten auf ein Minimum reduziert werden. Es wird nur ein Zeiger auf den ersten Datenblock, nicht aber auf alle Datenblöcke benötigt. Mehrfache Plattenzugriffe, um einen Datenblock zu erreichen, entfallen. Beim Lesen oder Schreiben größerer Datenmengen umgeht RTU das Puffersystem, so daß zeitraubende Speicher-zu-Speicher-Transfers nicht stattfinden. Datentransfers können bei Contigous Files bis zu zehnmal schneller sein als bei normalen Unix-Dateien. Die Transfer-Rate ist auch absolut voraussagbar und ändert sich nicht als Funktion der Größe der Datei. Sind an einem System mehrere Plattenlaufwerke angeschlossen, kann eine Platte exklusiv von einem Prozeß belegt werden. Die Plattenzugriffe verhalten sich jetzt deterministisch.

Diese Eigenschaften des Betriebssystems müssen durch eine geeignete Hardware-Architektur ergänzt werden, um ein Echtzeit-Rechnersystem zu erhalten.

Um die Forderung nach einem deterministischen Verhalten erfüllen zu können, haben die Rechnersysteme von Concurrent folgende zentrale Hardwaremerkmale (Bild 2):

þMultiple-Bus-Architektur,

þmehrere Hauptprozessoren,

þmehrere spezielle Prozessoren (Koprozessoren) für:

- Datenerfassung,

- Signalverarbeitung,

- Graphik,

- Vektorverarbeitung.

RTU unterstützt einen effizienten Mehrprozessorbetrieb mit hohem Durchsatz. Die lauffähigen Benutzer-Prozesse mit höchster Priorität werden aus einer einzigen Run-Queue an alle erlaubten Prozessoren dynamisch aufgeteilt. Die Prozessor-Vergabe kann entweder völlig automatisch oder unter Benutzerkontrolle erfolgen. RTU läuft parallel auf allen Prozessoren. Die Interrupt-Verarbeitung erfolgt ausschließlich auf CPU 1. Der Anwender hat die Möglichkeit, seine Echtzeitanwendung auf mehrere CPUs und die speziellen Prozessoren zu verteilen. Das Zusammenspiel mehrerer CPUs und die Vorteile von Koprozessoren werden im folgenden anhand eines Beispiels erläutert:

Aufgabe:

Es soll eine analoge Signalspannung mit einer Frequenz von 200 Kilohertz abgetastet werden. Nach Überschreiten eines Schwellwertes sollen die Meßdaten über einen Zeitraum von 30 Sekunden auf Platte abgespeichert werden. Gleichzeitig soll eine Fourier-Transformation durchgeführt und die Ergebnisse in Echtzeit graphisch dargestellt werden.

Problemlösung:

Die Kommunikation über Ereignisse in der realen Welt findet über den Data Acquisition Control Processor (DACP) statt. Der DACP ist ein programmierbarer Koprozessor mit 8 MIPS Leistung, der die Datenerfassung und Echtzeitperipherie verwaltet. Der DACP steuert die Digitalisierung der Meßdaten und transferiert die Daten via DMA in den Hauptspeicher. Ihm wird auch die Aufgabe der Schwellwerterkennung übertragen, die jetzt ohne Unix Overhead in Echtzeit durchgeführt wird. Zusätzlich wird die CPU von dieser Aufgabe befreit (Offloading der CPU). Nach Erreichen des Schwellwertes beginnt der DACP mit dem Datentransfer (DMA). Ist ein Datenpuffer gefüllt, erzeugt der DACP ein Unterbrechungssignal auf Ebene 6 (höchste Priorität!) Bei Concurrent-Serie-6000-Rechnern gibt es sieben Unterbrechungsebenen (Bild 1), die alle eine höhere Priorität als Benutzer-Prozesse oder Kernel-Routinen haben. Deswegen wird die Unterbrechung bedient, sobald diese Unterbrechungsebene frei ist. Da auf Ebene 6 nur noch die Service-Routine des Taktgebers arbeitet, ist dies spätestens nach 1 Millisekunde der Fall. Die DACP-Service-Routine führt verschiedene Aufgaben aus. Unter anderem erzeugt sie einen Asynchronous System Trap (AST) für den zugeordneten Echtzeitprozeß. Die Zeit, die die Service-Routine hierfür in Anspruch nimmt, ist die Unterbrechungs-Bedienzeit. Sie beträgt maximal 2,5 Millisekunden. Von diesem Zeitpunkt an weiß RTU, daß eine Prozeßumschaltung durchgeführt werden muß. Beim Betrieb mit einer CPU muß diese zunächst alle anstehenden Unterbrechungen bearbeiten. Dies führt zu einer zusätzlichen Wartezeit des Echtzeitprozessors, für die keine obere Schranke angegeben werden kann (Bild 3).

Anders im Mehrprozessor-Betrieb. Da bei RTU die ganze Unterbrechungsbehandlung - bis auf die Behandlung der Taktgeber-Unterbrechung - auf der CPU 1 ausgeführt wird, können diese Unsicherheiten auf weiteren Prozessoren (etwa CPU 2) ausgeschaltet werden. Nachdem die DACP-Service-Routine auf CPU 1 den AST für den Echtzeitprozeß erzeugt hat, wird auf die CPU 2 ohne weitere Verzögerung die Prozeß-Umschaltung eingeleitet. Auf CPU 2 treten noch weitere Wartezeiten (Bild 4) auf. Es kann aber immer ein Maximalwert angegeben werden. Das Zeitintervall von der Generierung der DACP-Unterbrechung bis zum 1. Befehl in der AST-Service-Routine des Benutzerprozesses beträgt maximal 9 Millisekunden. Wird CPU 2 ausschließlich für diesen Echtzeitprozeß reserviert, verkürzt sich die Process Dispatch Latency (Bild 5). Da CPU 2 keine Unterbrechungssignale bearbeitet, ist auch die Ausführungszeit des Anwenderprozesses absolut vorhersehbar. Im Mehrprozessorbetrieb kann ein deterministisches Verhalten des Systems gewährleistet werden.

Zurück zu unserer Aufgabe: Unser Prozeß hat jetzt einen Datenblock im Hauptspeicher und muß die Daten auf Platte speichern und eine Fourier-Transformation durchführen. Die Fourier-Transformation wird auf einem Vektor-Prozessor berechnet. Unser Prozeß stößt die Berechnung der Fourier-Transformation nur an und die CPU 2 bleibt frei, um den Plattentransfer zu starten. Sobald der Vektor-Prozessor seine Berechnung beendet hat, wird unser Prozeß über einen AST informiert, und der Graphik-Koprozessor kann mit der Ausgabe der Daten beauftragt werden. An der Lösung der Aufgabe sind jetzt vier Prozessoren beteiligt, wobei die CPU 2 hauptsächlich Steuerungs- und Synchronisationsfunktionen durchführt und die anderen Aufgaben an Koprozessoren delegiert (offloading der CPU). Die CPU 2 bleibt frei und kann schnell auf die ASTs reagieren.