home *** CD-ROM | disk | FTP | other *** search
Text File | 1988-09-14 | 76.5 KB | 1,372 lines |
-
- TOOLBOX-Spezial III
-
-
- Echtzeitprogrammierung in Pascal
-
- von Thomas Lang
-
-
- Einleitung
-
- In PASCAL sind die Grundzüge und einige allgemeine Ausblicke auf die
- Echtzeitprogrammierung schon vorgestellt worden (Ausgabe 2/1988).
- Um einen etwas tieferen Einblick in diese Materie zu vermitteln
- und die Aussage zu bekräftigen, daß ein Realtime System in jeder Pro-
- grammiersprache erstellt werden kann, soll jetzt ein System zur Echt-
- zeitprogrammierung in Pascal vorgestellt werden. Dieses System ist eine
- reine Software-Lösung, die unter dem normalen Betriebssystem als Anwen-
- derprogramm läuft. Es wurde ein kompatibler PC mit MS-DOS und Turbo
- Pascal verwendet, aber auf eventuelle Anpassungen wird oft hingewiesen,
- auch mit Tips, wie sie zu machen sind.
-
- Die Zielrichtung bei der Entwicklung dieses Systems war nicht, ein aus-
- gefeiltes und möglichst 'trickreiches' System zu entwickeln, sondern
- eines, das auf fast jedem Computer mit sehr wenig Anpassungen unter
- Pascal realisiert werden kann. Außerdem habe ich versucht, sehr 'ein-
- fach' zu programmieren, in der Hoffnung, daß es dadurch vielen Lesern
- leichter fällt, die einzelnen Teile nachzuvollziehen. Deshalb mögen alle
- Freaks, die jedes Bit am liebsten mehrfach benutzen, Nachsicht üben,
- wenn auf ihrer (und evtl. auch meiner) Maschine die eine oder andere
- Realisierung schneller und mit weniger Speicherbedarf möglich ist; es
- steht jedem frei, alle Teile zu verändern, ich will sogar dazu ermuti-
- gen, damit zu experimentieren, die vorgestellte Version also nur als
- Anleitung zu nehmen.
-
- Dieses System ist ferner nicht geeignet, sehr komplexe Echtzeitaufgaben
- zu erfüllen, aber es kann für einfache Steuerungen oder ähnliches als
- Basis genommen werden, in die dann die Anwendung (also die Steuerung) in
- derselben Sprache (hier also Pascal) als Prozeduren eingefügt wird. Au-
- ßerdem gibt es natürlich sehr viel komplexere Systeme, die beispiels-
- weise 'nur bei Bedarf' Echtzeitaufgaben wahrnehmen, sonst aber von vie-
- len Benutzern im Timesharing gebraucht werden. Mit solchen kann und will
- dieses nicht konkurrieren, es sollen nur die Ansätze und deren Lösung am
- konkreten Beispiel aufgezeigt werden, ohne jeden Anspruch auf Allein-
- gültigkeit (aber doch mit dem, daß es so möglich ist!). Bei Aufgaben,
- die in einem eigenen System ohne Betriebssystem ablaufen sollen, kann
- das vorgestellte ebenfalls als Basis dienen; beispielsweise auf Einpro-
- zessorplatinen zur Verarbeitung von Meßwerten.
-
-
- Allgemeine Anmerkungen
-
-
- Wie sieht nun ein solches System zur Echtzeitprogrammierung in Pascal
- aus? Dazu wird ein sogenannter 'Scheduler' benötigt, wie er schon in dem
- allgemeinen Artikel in PASCAL angedeutet wurde. Daneben werden einige
- Hilfsroutinen und ein Debugger, der speziell auf den Scheduler zuge-
- schnitten ist, vorgestellt.
-
- Bevor die ersten Leser jetzt schon das Handtuch werfen und meinen, daß
- dies alles auf ihrer Maschine XY nicht in allgemeiner Form zu verwirk-
- lichen ist, sei hier darauf hingewiesen, daß dem nicht so ist! Falls Ihr
- Compiler (oder evtl. auch Interpreter) den von mir oft benutzten Typ
- Byte beispielsweise nicht kennt, so ändern Sie diese einfach in Integer,
- und es müßte alles genauso, wenn auch vielleicht etwas langsamer, funk-
- tionieren. Eines sollte aber doch möglich sein, nämlich andere Quell-
- texte einzufügen, was in Turbo mit der Compileranweisung $I gemacht
- wird. Falls das bei Ihnen nicht geht, so sollten Sie zumindest einen
- sehr guten Editor haben, damit Sie alle Files wie vorgestellt verwenden
- können und die Einfügung mit dem Editor selbst machen, also 'von Hand'.
-
- Damit bin ich schon bei der Erklärung, wie der Aufbau des Systems gene-
- rell aussieht. Turbo Pascal bot bis vor kurzem nicht die Möglichkeit der
- echten Modularisierung, das heißt, daß nur im Quelltext einzelne Module
- möglich waren, die aber bei jedem Compilerlauf wieder compiliert wurden.
- Da Pascal von Haus aus keine echte Modularisierung vorsieht, habe ich
- ebenfalls darauf verzichtet und nur die schon erwähnte Einfügung von
- Quelltexten verwendet. Damit kann ein relativ einfacher 'Trick' eine
- noch bessere Modularisierung vortäuschen: Man teilt jeden Modul in zwei
- auf, einer enthält die Deklarationen und alle Prozeduren als sogenannte
- Forwards (d. h., daß die Prozedur bekannt gemacht wird, sie aber erst
- später wirklich im Quelltext vorkommt), der andere enthält die Prozedu-
- ren und Funktionen. Mit dieser Methode können auch alle Prozeduren in
- dem Modul enthalten sein, zu dem sie logisch gehören; sie müssen nicht
- unbedingt vor einer anderen Prozedur auftauchen, nur weil diese evtl.
- davon Gebrauch macht.
-
- Eine weitere, positive Eigenart von Turbo-Pascal (und den meisten, mir
- bekannten Compilern) wird verwendet: Man kann überall Typen, Konstanten
- und Variablen definieren. Falls Ihr Compiler dies nicht zuläßt, so soll-
- te entweder nur ein Deklarationsfile erstellt werden, in dem neue Defi-
- nitionen dann an der entsprechenden Stelle eingefügt werden, oder jedes
- Deklarationsfile in mehrere aufgeteilt werden, die nur Typen, nur Va-
- riablen, u.s.w. enthält. Das Einfügen dieser mehrfachen Files gestaltet
- sich dann nicht sehr schwierig, es müßen zuerst alle Konstantenfiles,
- dann alle Typenfiles und zum Schluß alle Variablenfiles eingefügt wer-
- den.
-
-
- Scheduler - Einführung
-
-
- Doch nun werden die 'Wenn und Aber' zunächst beendet, wir wenden uns der
- ersten Aufgabe zu, einen Scheduler zu erstellen. Was ist ein Scheduler?
- Das ist der Teil, der die Aufgaben entsprechend ihrer Priorität oder
- sonstiger, vorher festgelegter Eigenschaften, aufruft. Ein allgemeines
- Konzept, wie sowas bewerkstelligt wird, ist schon vorgestellt worden,
- hier noch einmal kurz zur Erinnerung:
-
- Aufgabe: Wie sieht prinzipiell eine Warteschlange mit 3 Prioritäten
- und jeweils maximal X Aufgaben, die warten, aus?
-
- Lösung: Priorität=(hoch,mittel,niedrig)
- Schlange=ARRAY [1..X] of Aufgabe
- Warteschlange=((hoch,Schlange),
- (mittel,Schlange),
- (niedrig,Schlange))
-
- Die Aufgabe wird jeweils von einer Prozedur durchgeführt.
-
- REPEAT
- IF Warteschlange_hoch enthält Aufgabe THEN
- führe Aufgabe aus
- lösche diese Aufgabe aus der Schlange
- ELSEIF Warteschlange_mittel enthält Aufgabe THEN
- führe Aufgabe aus
- lösche diese Aufgabe aus der Schlange
- ELSEIF Warteschlange_niedrig enthält Aufgabe THEN
- führe Aufgabe aus
- lösche diese Aufgabe aus der Schlange
- UNTIL Ende erreicht
-
- So ein ähnliches Verfahren soll hier angewendet werden; die Vorausset-
- zungen zur Erfüllung der Aufgaben sind:
-
- Es sind 3 Prioritäten gegeben, wobei die höchste Priorität 5 ver-
- schiedene Aufgaben enthalten kann, aber jede dieser Aufgaben nur
- einmal. Die mittlere Priorität sei ein sogenannter Fifo, also eine
- echte Warteschlange, in der gilt, daß die Aufgaben in der Reihen-
- folge bearbeitet werden, in der sie in die Warteschlange hineinge-
- schoben worden sind (Fifo = first in first out); die Aufgaben, die
- damit aufgerufen werden, bekommen noch einen Parameter mit. Die
- niedrigste Priorität sei von der aktuellen Zeit abhängig, d. h.,
- daß Aufgaben erst abgearbeitet werden, wenn eine bestimmte Zeit
- erreicht ist.
-
- Diese an sich sehr einfache Aufgabenstellung sieht in der (Programmier-)
- Praxis aber schon gar nicht mehr so einfach aus. Die hohe Priorität ist
- noch einfach, aber schon beim Fifo fangen die Schwierigkeiten an; es ist
- zu beachten, daß die Anzahl der Aufgaben, die im Fifo gehalten werden
- können, nicht gleich der verschiedenen Aufgaben sein muß, die vom Fifo
- aufgerufen werden können. Die Aufgabe der niedrigsten Priorität wird
- allgemein als Periodic bezeichnet, auch wenn im allgemeinen Sprachge-
- brauch unter periodisch ein sich in bestimmtem Zeitabstand wiederholen-
- des Ereignis gemeint ist, hier genügt hingegen schon ein einmaliger
- Aufruf (ich beschränke mich sogar auf einmalige, aber nur, um den
- Scheduler einfacher zu halten). Die nächste Schwierigkeit scheint die
- aktuelle Zeit zu sein, aber MS-Dos bietet einen Systemaufruf, mit dem
- diese abgefragt werden kann, und, falls bei einer speziellen Maschine
- diese Abfrage nicht oder nur sehr umständlich zu realisieren ist, so
- kann man sich mit einem Zähler ganz gut helfen.
-
- Die nächste, noch viel größere Schwierigkeit liegt darin, daß es in
- Pascal normalerweise keinen Prozedurswitch oder Array von Prozeduren
- gibt. In real existierenden Echtzeitsystemen kommen diese sehr häufig
- vor, und evtl. Verzweigungen gehen über den Inhalt des Switches, der
- (fast schon natürlich) dynamisch verändert wird. Damit kann eine Aufgabe
- einfach dadurch in die Folge der folgenden Aufgaben eingetragen werden,
- indem die Adresse der Prozedur, durch die sie bearbeitet wird, in den
- Switch eingetragen wird. Dieses Verfahren ist bei Compiler XY natürlich
- auch möglich, aber schon bei YZ ganz anders zu realisieren. Deswegen
- wird hier ein wenig eleganter Kunstgriff angewendet; alle Prozeduren,
- die Aufgaben bearbeiten, müssen deklariert sein, wenn auch nur als Dummy
- (das ist ein Platzhalter, der nichts tut). Auch hierbei wird wieder das
- Verfahren angewendet, die notwendigen Prozeduren zuerst als Forwards zu
- deklarieren (siehe Listing, AUFGABEN.DCL), und in einem extra File, das
- alle Prozeduren als Dummys enthält (siehe Listing, SONSTIGE.PAS) erhal-
- ten sie ihren Inhalt (Verarbeitungsteil, also nichts). Wenn in einer
- konkreten Anwendung nun einige dieser Prozeduren verwendet werden, so
- können sie darin entweder gelöscht werden, oder noch einfacher, als
- Kommentar gekennzeichnet werden.
-
-
- Scheduler - Details
-
-
- Nach diesen etwas allgemeineren Überlegungen zum Scheduler und dabei zu
- erwartenden Problemchen folgt nun eine Erklärung des realisierten
- Schedulers. Die notwendigen Deklarationen (siehe Listing, SCHEDUL.DCL)
- sind wieder getrennt vom Verarbeitungsteil (siehe Listing, SCHEDUL.PAS).
-
- Initialisierung:
-
- Die erste Aufgabe ist es, den Scheduler zu initialisieren, d. h., daß
- eine Variable, die angibt, ob der Scheduler aufhören soll, zu falsch
- gesetzt wird. Die nächste ist, alle Aufgaben, die evtl. in den Arrays
- für anstehende Aufgaben enthalten sind, zu löschen. Beim Fifo geschieht
- das nicht durch explizites Löschen sondern dadurch, daß die Zeiger, mit
- denen der Fifo als Ringbuffer verwaltet wird, initialisiert werden. Danach
- folgt, alle Zähler für evtl. auftretenden Overflow auf 0 zu setzen und
- den Belastunngszähler ebenfalls. Die Behandlung von Overflow wird bei
- den Utilities beschrieben, die des Belastungs-zählers (vor allem seine
- Auswertung) beim Debugger.
-
- Als Belastungszähler wurde hier eine künstliche 'große Integer' gewählt,
- da bei einer einfachen Integer sehr schnell ein Overflow auftritt, und
- eine Real-Zahl in der Verarbeitung langsamer ist, wie folgendes zeigt:
-
- Die folgende Liste wurde von einer Zeitmessung mit der Funktion TimeNow
- (wird bei den Utilities besprochen) erstellt, wobei untersucht wurde, ob
- das Hochzählen eines Doppelwortes mit der Prozedur PIncrDW schneller als
- das Hochzählen einer RealZahl ist:
-
- Zeitmessung für Doppelwort um 1 erhöhen für 100 000 mal
-
- BeginZeit:7753936.0000
- EndeZeit:7754924.0000
- Differenz: 988.0000
-
-
- Zeitmessung für RealZahl um 1 erhöhen für 100 000 mal
-
- BeginZeit:7754935.0000
- EndeZeit:7757220.0000
- Differenz: 2285.0000
-
- Das Ergebnis zeigt deutlich, daß dies tatsächlich der Fall ist! Somit
- ist es günstiger, für Belastungsmessungen das gewählte Verfahren beizu-
- behalten, als auf Real-Zahlen umzusteigen. Ich glaube, daß bei jedem
- Pascal Compiler Reals länger brauchen, als die gewählte 'künstliche'
- Erweiterung des Integerbereichs, wobei aber sogenannte LongInteger vor-
- zuziehen sind, falls der Compiler diese bietet.
-
- Verzweigung:
-
- Die Verzweigung des Schedulers zu einer anstehenden Aufgabe wird für
- jede Priorität mit einer eigenen Funktion durchgeführt, die als Resultat
- angibt, ob verzweigt wurde oder nicht. Dabei ist bei jeder Priorität
- darauf zu achten, daß die zu bearbeitende Aufgabe aus der Liste der
- Aufgaben gelöscht wird, bevor sie wirklich aufgerufen wird, da sonst
- diese Aufgabe als 'Leiche' zumindest eine Zeit lang mitgeführt wird,
- obwohl es gar nicht nötig ist.
-
- - Hohe Priorität
-
- Diese Verzweigung sieht auf den ersten Blick sehr eigenwillig
- aus, denn es wird zuerst nachgeprüft, ob überhaupt eine Auf-
- gabe mit hoher Priorität zu erledigen ist, und dann wird,
- wieder mit einer seltsamen Konstruktion, zu dieser Aufgabe
- verzweigt. Tatsächlich könnte dies sehr viel einfacher durch
- ein CASE oder nur die verschachtelten IF's gemacht werden,
- aber dann müßte jedesmal die Prozedur PLogHoch (Beschreibung
- folgt beim Debugger) mit angegeben werden, und diese ver-
- schachtelten IF's sind nach meiner Meinung ebenso deutlich wie
- ein CASE; zumindest werden hier die folgenden Indizes nicht
- mehr überprüft oder angesprungen, falls schon einer gefunden
- worden ist, was bei CASE evtl. möglich wäre (zumindest der
- Sprung dorthin). Ein weitere Grund für diese Realisierung ist,
- daß ein ähnliches Verfahren in exisitierenden Systemen sehr
- oft angewendet wird, statt eines Array of Boolean wird aber
- eine (oder mehrere) normale Zahlen oder Bitfelder benutzt, die
- dann mit einem oder zumindest sehr wenigen Befehlen abgefragt
- werden können, ob sie überhaupt etwas enthalten; und erst dann
- wird die eigentliche Aufgabe herausgesucht und aufgerufen.
- Dies ist in vielen Pascalversionen auch möglich, aber leider
- fast überall ein wenig anders, weshalb hier absichtlich eine
- umständlicher, aber überall möglicher Weg gewählt wurde.
-
- - Fifo
-
- Die Verzweigung innerhalb des Fifos ist am schwierigsten zu
- verstehen, weil der Fifo als Ringbuffer aufgebaut ist, d. h.,
- daß bei einem Überlauf wieder von vorne angefangen wird. In
- der Literatur tauchen diese Warteschlangen öfters auf, und es
- gibt auch ausgefeilte Verfahren zu ihrer Verwaltung, die nur
- mit zwei Zeigern auskommen, einer der angibt, an welche Stelle
- das nächste Element reingeschrieben wird, und einer, der an-
- gibt, an welcher Stelle das nächste Element herausgeholt wird.
- Hier ist noch zusätzlich eine Variable mit benutzt, die an-
- gibt, ob der Fifo leer ist oder nicht. Dies ist deswegen nö-
- tig, da in dieser Version beide Zeiger (für Input und Output)
- nur im Bereich der Elemente des Fifos sind, es muß also eine
- zusätzliche Angabe gemacht werden, falls beide gleich sind.
- Damit kann der Fifo komplett ausgenutzt werden, und man kommt
- mit addieren oder subtrahieren und seltenen Vergleichen auf
- Maximalgröße aus. Die eigentliche Verzweigung findet dann nach
- einem recht eigentümlichen Verfahren statt; alle Informatiker
- (oder mit etwas theoretischer Informatikausbildung behaftete
- Personen) kennen dieses Verfahren, das durch laufende Halbie-
- rung des verbeibenden Bereiches besteht. So etwas wird im
- Prinzip bei Baumstrukturen angewandt, genauso wie beim inzwi-
- schen wohl bekannten Quicksort. Hier wird es deswegen benutzt,
- weil es im Durchschnitt sehr schnell ist, egal welche Aufgabe
- aufgerufen werden soll; bei anderen Verfahren sind die Abfra-
- gen, die zuletzt drankommen, relativ langsam, je nach Compiler
- schätzungsweise um den Faktor 2 bis 4 langsamer. Der Nachteil
- bei diesem Verfahren ist aber, daß bei einer anderen Anzahl
- verschiedener Aufgaben, die durch den Fifo bearbeitet werden
- können, der Quelltext verändert werden muß.
-
- - Periodic
-
- Die Verzweigung innerhalb der periodischen Aufgaben erfolgt
- nach einem relativ einfachen Grundprinzip; es wird die klein-
- ste Zeit aller Aufgaben, die erledigt werden sollen, heraus-
- gesucht. Damit nicht immer von der gleichen Position an ge-
- sucht wird, was natürlich auch denkbar wäre, wird der Start-
- index, ab dem gesucht wird, immer um 1 erhöht, bzw. auf den
- Anfang gesetzt, falls das Ende erreicht war. Mit diesem Ver-
- fahren kommen alle Aufgaben, die nach einem gewissen Zeitpunkt
- zu erledigen sind, immer mal als erste in der Abfrage dran,
- womit unter anderem verhindert wird, daß immer nur die gleiche
- Aufgabe vor einer anderen kommt, falls sie zur selben Zeit
- bearbeitet werden sollen. Die eigentliche Verzweigung ge-
- schieht wieder mit verschachtelten IF's wie bei den Aufgaben
- hoher Priorität, aber natürlich nur, falls die aktuelle Zeit
- kleiner oder gleich der herausgefundenen kleinsten Zeit ist,
- zu der eine Aufgabe erledigt werden soll.
-
- Zusammenfassend ist festzustellen, daß die einzelnen Verzweigungen doch
- noch recht einfach zu verstehen sind (hoffentlich), aber in wirklich
- existierenden Echtzeitsystemen wird dieser Teil meistens sehr optimiert
- und auf die jeweilige Zielmaschine angepasst, da dieser Teil derjenige
- ist, der am häufigsten durchlaufen wird, und die hierfür benötigte Zeit
- den eigentlichen Aufgaben, die zu erledigen sind, nicht zur Verfügung
- steht. Auf der anderen Seite ist aber zu bedenken, daß ein so einfaches
- System zumindest als Basis, an der dann optimiert werden kann, auch
- seine Vorteile hat, denn allzu komplexe Verfahren zur Aufgabensteuerung
- sind leider nur sehr schwer zu verstehen und sie tendieren mit zuneh-
- mendem Komfort danach, mit ihrer eigenen Verwaltung so beschäftigt zu
- sein, daß die eigentlichen Aufgaben nur am Rande manchmal mit ausgeführt
- werden.
-
- Scheduling:
-
- Nach diesen Vorbereitungen ist der eigentliche Scheduler sehr einfach.
- Zu Beginn wird die aktuelle Zeit gerettet, da sie für die Belastung
- (oder vielleicht besser Auslastung) des Systems mit den eigentlichen
- Aufgaben benötigt wird; dies wird später bei Debug mit beschrieben. Da-
- nach geht eine Schleife solange, bis Ende erreicht wird, in der die
- Verzweigung der einzelnen Prioritäten mit den vorher angegebenen Funk-
- tionen erfolgt, und die Verzweigungen der nächst niederen Priorität
- werden nur ausgeführt, falls noch keine Aufgabe in einer höheren auszu-
- führen war. Falls gar keine Aufgabe gefunden worden ist, wird zuletzt
- der Belastungszähler um 1 erhöht. Nachdem Ende erreicht ist, wird die
- aktuelle Zeit gerettet und mit PLogSchedul eine Statistik und Be-
- lastungsauswertung erstellt.
-
- Anwendungshinweise:
-
- Der Scheduler in der vorgestellten Form ist leider nicht sofort anwend-
- bar, da alle Log-Prozeduren erst im Debugger vorgestellt werden und aus
- den Utilities die Funktion TimeNow und PIncrDW benötigt werden. Falls
- alle diese Aufrufe vorläufig entfernt werden, dann kann er aber schon
- notdürftig angewendet werden; nur die Zeit sollte noch nicht benutzt
- werden (also Periodic weglassen). Anstatt der Zeitabfrage mit TimeNow
- kann zur Not auch eine Abfrage auf einen Zähler erfolgen, das bedeutet,
- daß dieser Zähler einen bestimmten Wert erreicht haben muß, bevor die
- Aufgabe aufgerufen wird. In Systemen, in denen eine Uhr nicht oder nur
- sehr umständlich abzufragen ist, sollte dies zumindest als Einführung in
- Echtzeitprogrammierung genügen. Der Zähler muß aber ein Doppelwort oder
- eine Real-Zahl sein, damit nicht sehr schnell ein Overflow auftritt. Um
- gewisse Zeitrelationen doch noch einigermaßen nachbilden zu können,
- sollte der Zähler am Beginn der Abfragen, ob eine Aufgabe zu tun ist,
- erhöht werden, und dann entweder bei jeder Aufgabe nochmal mit einem
- geschätzten Wert, oder pauschal für alle Aufgaben mit demselben Wert
- nochmals. Damit überhaupt eine Aufgabe erledigt werden kann, muß zuerst
- die Prozedur InitSchedul aufgerufen werden, dann muß (mindestens) eine
- Aufgabe in die Liste der zu bearbeitenden eingetragen werden, und dann
- kann Schedul aufgerufen werden. Wie Aufgaben eingetragen werden ist un-
- ter anderem Aufgabe der Utilities, die ebenso wie der Debugger noch
- vorgestellt werden.
-
-
- Debugger - Einführung
-
-
- In diesem Teil wird ein Debugger für das verwendete Echtzeitsystem vor-
- gestellt und zuerst erklärt, wozu er nötig ist. Dieser Debugger ist auf
- gar keinen Fall zu vernachlässigen, geschweige denn durch einen 'ganz
- normalen', der auf Pascal zugeschnitten ist, zu ersetzen, da er ganz
- andere Aufgaben hat! In diesem Zusammenhang ist es noch nützlich, die
- unterschiedlichen Begriffe, die verwendet werden, kurz zu erläutern;
- 'Logging' bedeutet in diesem Zusammenhang aufzeichnen oder protokollie-
- ren, 'Debugging' bedeutet Fehlersuche, die in Echtzeitsystemen aber fast
- nur an Hand von Protokollen erfolgen kann.
-
- Wenn dieser Debugger, oder vielleicht besser ausgedrückt Logger (aber
- wer kann sich darunter schon was vorstellen, ohne Assoziationen mit He-
- ringen? ), allein vom Umfang mit den anderen Grundteilen des Echtzeit-
- systems Scheduler und Utility verglichen wird, so zeigt sich schon da-
- ran, daß er sehr wichtig ist. Es ist nochmals extra zu betonen, daß das
- schönste und am besten durchdachte Echtzeitsystem ohne einen darauf zu-
- geschnittenen Debugger fast wertlos ist, da in diesen Systemen ohne
- Protokollierung und etwaiger dynamischer Änderung von Variablen 'von
- außen' (die aber hier nicht realisiert ist) keine Fehlersuche möglich
- ist.
-
- Die Aufgabe dieses Debuggers besteht hauptsächlich darin, den Zustand
- der Aufgabenlisten bzw. das Einschreiben oder Ausführen von solchen
- Echt-zeitaufgaben zu protokollieren. Dies ist gänzlich anders, als in
- 'fast Pascal' einzelne Schritte zu verfolgen. Das verfolgen oder
- nachvollziehen der Echtzeitaufgaben ist deshalb besonders wichtig, weil
- die meisten Fehler darin bestehen, daß eine oder mehrere Aufgaben nicht
- oder in der falschen Reihenfolge (oder so ähnlich) in die Liste
- eingetragen oder ausgeführt wird. Ein sehr schwieriges Problem liegt
- auch in sogenannten 'Deadlocks', das sind Zustände, in denen eine
- Aufgabe auf eine Nachricht von einer anderen wartet, diese aber
- ebenfalls wartet, im schlimmsten Fall auf eine Nachricht der Aufgabe, an
- die eine Nachricht geschickt werden soll. Ein 'fast Deadlock' tritt dann
- auf, wenn ein externes Gerät nicht reagiert, und dies nicht oder mit zu
- großer Wartezeit abgefangen wird. Diese Zustände können fast nur an
- Protokollen von ausgeführten Echtzeitaufgaben nachvollzogen werden, aber
- leider sind die beschriebenen (und auch noch andere) Fehler gar nicht so
- selten, ja man kann sagen, daß ein etwas größeres System solche Fehler
- am Anfang immer hat.
-
- In der aktuell vorliegenden Version werden die Zustände oder Aufrufe,
- die aufgezeichnet werden, mit den normalen Write-Prozeduren von Pascal
- erledigt, aber es wäre generell möglich, dafür eigene Aufgaben in Echt-
- zeit zu schreiben, beispielsweise Aufgaben des Fifos zu verwenden.
-
- Debugger - Details
-
- Der Debugger ist nach dem schon im ersten Teil angegeben Schema aufge-
- baut; also erst die Deklarationen (siehe Listing, DEBUG.DCL) in einem
- extra File und dann genauso die Verarbeitung (siehe Listing, DEBUG.PAS).
-
- Deklarationen:
-
- Die Deklarationen sind sehr einfach, sie enthalten zuerst Zustandsvari-
- able (sogenannte Flags), die angeben, ob Logging auf Bildschirm, Drucker
- oder eine Datei erfolgen soll oder nicht. Danach wird ein etwaiger
- Dateiname deklariert und das File, in das evtl. aufgezeichnet wird, als
- Text (die eine Standard Deklaration von Pascal ist).
-
- Für die einzelnen Echtzeitaufgaben werden Arrays mit Flags deklariert,
- die angeben, ob bei den entsprechenden Put... -Prozeduren (Aufgabe in
- Liste eintragen) bzw. den entsprechenden Aufrufen die jeweilige Aufgabe
- geloggt werden soll. Dabei ist zu beachten, daß für den Fifo die Anzahl
- der verschiedenen Aufgaben, die er aufrufen kann, wichtig ist, und nicht
- die Anzahl der Aufgaben, die er speichern kann. Diese Flags könnten
- theoretisch in der Deklaration der Aufgabenliste im Scheduler mit auf-
- genommen werden, aber sie gehören eindeutig mit zum Debugger und werden
- deswegen auch getrennt deklariert.
-
- Verarbeitung:
-
- Die Prozedur InitDebug initialisiert den Debugger, sie muß vor der
- ersten Verarbeitung in Echtzeit (also vor der Prozedur SCHEDUL) aufge-
- rufen werden. Mit der Variablen Frage wird angegeben, ob die Initiali-
- sierungsdaten interaktiv abgefragt werden sollen, oder ob sie aus den
- weiteren Parametern übernommen werden sollen. Dieses Verfahren hat den
- Vorteil, daß während der Entwicklung oder auch in der Testphase abge-
- fragt werden kann, danach ist nur dieser Parameter auf FALSE zu setzen,
- und die Abfrage wird nicht ausgeführt.
-
- Bei Abfragen werden zuerst die Flags abgefragt, ob Logging auf Bild-
- schirm, auf Drucker oder in eine Datei gehen soll. Wenn in eine Datei
- aufgezeichnet werden soll, wird der Name dieser Datei abgefragt, es ist
- also möglich, mehrere Tests jeweils mit Protokollen in eine Datei aus-
- zuführen, und diese dann später auszuwerten; bei jeweils dem gleichen
- Da-teinamen würde die zuerst erstellte jedesmal wieder überschrieben.
-
- Danach wird abgefragt, ob die Voreinstellung 'alle Aufgaben aufzeichnen'
- oder 'keine Aufgaben aufzeichnen' sein soll. Diese Voreinstellung wird
- dann in die FlagListen eingetragen. Danach wird für hohe Priorität, Fifo
- und Periodic jeweils abgefragt, für welche Aufgaben diese Voreinstellung
- geändert werden soll; das bedeutet bei Voreinstellung 'alle aufzeich-
- nen', welche nicht aufgezeichnet werden, bei Voreinstellung 'keine auf-
- zeichnen' hingegen welche doch aufgezeichnet werden. Damit ist es bei-
- spielsweise möglich, mit relativ wenig Eingaben alle Aufgaben aufzu-
- zeichnen oder nur eine einzige.
-
- Wenn nicht abgefragt wird, so werden die Daten für Logging auf Bild-
- schirm, Drucker oder in Datei übernommen. Dann wird auch vorausgesetzt,
- daß das aufrufende Programm angibt, welche Aufgaben geloggt werden sol-
- len oder nicht. Dieses Verfahren ist sicher nicht sehr elegant, man
- könnte alles als Parameter übergeben, aber dann wird dieser Teil, der
- die übergebenen Parameter auswertet und übernimmt, ziemlich unüber-
- sichtlich.
-
- Falls in eine Datei aufgezeichnet werden soll, so wird diese dann der
- Textdatei zugewiesen (evtl. Anpassung bei anderen Systemen nötig!) und
- geöffnet. Hierbei wird keine Überprüfung vorgenommen, ob der Dateiname
- gültig ist, ob das File schon exisiert, also keinerlei Fehler werden
- abgefangen.
-
- Die Prozedur PLogLn gibt ein WriteLn ohne jegliche Parameter auf Bild-
- schirm, Drucker (=Lst) oder in Datei (=LogFile) aus, je nachdem, ob das
- entsprechende Flag gesetzt ist oder nicht. Dieser Teil könnte sicher
- wieder um einiges eleganter gelöst werden, aber in dieser Version ist
- keine oder nur eine geringe Anpassung an andere System nötig, was bei
- einer trickreichen Lösung nicht der Fall wäre.
-
- Die Prozedur PLogText gibt einen Text aus, analog zu PLogLn.
-
- Die Prozedur PLogInteger gibt eine Integer aus, analog zu PLogLn.
-
- Die Prozedur PLogReal gibt eine Real-Zahl aus, analog zu PLogLn. Hier
- wird eine Formatierung mit den vom Standard gegebenen Möglichkeiten
- vorgenommen, insgesamt 7 Stellen Ausgabe, mit keiner Stelle hinter dem
- Komma. Dies ergibt eine erweiterte Integer, aber da diese Prozedur fast
- nur für die Zeit verwendet wird, genügt es vollkommen. Falls keine
- Formatierung gewählt wird, so werden die Protokolle für Real-Zahlen sehr
- schwer lesbar, bei Bedarf kann aber jederzeit eine andere Formatierung
- gewählt werden oder noch mehrere zusätzlich, am einfachsten mit extra
- Prozeduren.
-
- Die Prozedur PLogHoch loggt den Aufruf einer Aufgabe mit hoher Priori-
- tät, deren Nummer als Parameter übergeben wird.
-
- Die Prozedur PLogFifo loggt den Aufruf einer Aufgabe des Fifos, deren
- Nummer und Aufrufparameter übergeben werden.
-
- Die Prozedur PLogPeriodic loggt den Aufruf einer periodischen Aufgabe,
- deren Nummer als Parameter übergeben wird. Zusätzlich wird die Zeit, zu
- der sie aufgerufen werden sollte, und die aktuelle Zeit ausgegeben.
-
- Die Prozedur PLogSchedul gibt eine Art Statistik über den gesamten Zeit-
- raum der Echtzeitverarbeitung aus. Dabei wird die BeginnZeit, die Ende-
- Zeit und die daraus berechnete Dauer ausgegeben. Die Anzahl der Durch-
- läufe des Schedulers ist der Wert des Belastungszählers, dessen Ver-
- hältnis zu seinem maximal erreichbaren Wert, der natürlich von der Dauer
- und dem verwendeten System abhängig ist (Systemkonstante NichtBelastung
- wird experimentell ermittelt, siehe Listing TEST02.PAS, PERIOD1.PAS und
- Log-File TEST_02.LOG). Diese relative Belastung wird in Promill ausgege-
- ben, wobei 0 keine Belastung bedeutet und 1000 der maximale Wert ist.
- Falls ein negativer Wert (so etwa -3 Promill) erreicht wird, so ist das
- wie 0 anzusehen, also nicht sklavisch auf die letzte Stelle in den Pro-
- millen schauen, da erstens Rundungsfehler nicht abgefangen werden, und
- zweitens ist die Aussage über die Belastung auch nur relativ zu werten.
- Es gibt keine allgemein gültigen Grundsätze, die angeben, daß eine Be-
- lastung von XY noch tragbar ist, eine darüber aber nicht mehr. Die Er-
- fahrung zeigt, daß so gegen 500 Promille im allgemeinen keine Über-
- lastung darstellt, bei Werten größer als 750 wird es aber öfters kri-
- tisch. Meistens ist eine subjektive Beurteilung, ob die Reaktionszeiten
- noch ausreichen oder nicht, genauso aussagekräftig wie die Angabe der
- Belastung. Das Problem, an welcher Stelle eine Optimierung dann sinnvoll
- ist, wird in den Anwen-dungshinweisen kurz angesprochen. Danach wird noch
- angegeben, wieviel OverFlows bei Aufgaben hoher Priorität, beim Fifo und
- bei periodischen Aufgaben aufgetreten sind. Diese Angaben sind bei
- weitem wichtiger, da eigentlich gar kein Überlauf (d. h., daß eine Auf-
- gabe in die Liste eingetragen werden sollte, aber kein Platz vorhanden
- war) auftreten sollte. Bei Fifo ist dieses Problem noch relativ leicht
- zu lösen, indem einfach die Größe des Fifos heraufgesetzt wird (
- AnzahlFifo) , aber bei periodischen Aufgaben oder denen mit hoher Prio-
- rität wird es schwieriger; hier könnte evtl. auch der Buffer (mit
- AnzahlHoch bzw. AnzahlPeriodic) größer gemacht werden, aber dann muß
- zusätzlich noch eine Aufteilung der Aufgaben vorgenommen werden, bei-
- spielsweise wird Periodic1 aufgeteilt in 3 Komponten, Periodic1 und
- Periodic7 und Periodic8, die dann aber auch nur Teilaufgaben der bishe-
- rigen Periodic1 bearbeiten.
-
- Die Prozedur PLogPutHoch loggt die Tatsache, daß eine Aufgabe mit hoher
- Priorität in die Aufgabenliste eingetragen werden soll und ihre Nummer.
- Falls dies nicht möglich ist, wird ein Hinweis auf OverFlow mit ausge-
- geben.
-
- Die Prozedur PLogPutFifo loggt die Tatsache, daß eine Aufgabe des Fifos
- in die Aufgabenliste eingetragen werden soll und ihre Nummer und den
- Parameter. Falls dies nicht möglich ist, wird ein Hinweis auf OverFlow
- mit ausgegeben.
-
- Die Prozedur PLogPutPerAbs loggt die Tatsache, daß eine periodische Auf-
- gabe mit absoluter Zeit in die Aufgabenliste eingetragen werden soll und
- ihre Nummer und absolute Zeit. Falls dies nicht möglich ist, wird ein
- Hinweis auf OverFlow mit ausgegeben.
-
- Die Prozedur PLogPutPerDelta loggt die Tatsache, daß eine periodische
- Aufgabe mit DeltaZeit in die Aufgabenliste eingetragen werden soll und
- ihre Nummer, die DeltaZeit und die berechnete absolute Zeit. Falls dies
- nicht möglich ist, wird ein Hinweis auf OverFlow mit ausgegeben.
-
- Die Prozedur PLogInhaltHoch loggt den angegebenen Text und den Inhalt
- der Aufgabenliste mit hoher Priorität, wobei nur T (für Aufgabe steht
- an) oder F (für Aufgabe steht nicht an) ausgegeben wird.
-
- Die Prozedur PLogInhaltFifo loggt den angegebenen Text, die Zeiger für
- die Fifoverwaltung (FifoIndexIn, FifoIndexOut) und ob der Fifo als leer
- gekennzeichnet ist oder nicht, und den Inhalt, bestehend aus IndexNum-
- mer, eingetragener Aufgabennummer (gibt an, welche FifoAufgabe darin
- steht) und eingetragenem Parameter. Bei Aufruf dieser Prozedur ist deut-
- lich zu sehen, daß die alten, schon abgearbeiteten Aufgaben nicht ge-
- löscht werden, sondern nur durch ändern der Zeiger dieser Platz als frei
- gekennzeichnet ist. Die verwendete Turbo-Prozedur Str(I,T) weist dem
- String T den Inhalt der Integer I als Text, also Character, zu. Dies
- kann entfallen oder durch ähnliche Prozeduren ersetzt werden, es ergibt
- nur ein besseres Layout.
-
- Die Prozedur PLogInhaltPer loggt den angegebenen Text, den Zeiger für
- die Verwaltung der periodischen Aufgabenliste (PeriodicIndexOut) und die
- aktuelle Zeit. Danach wird der Inhalt dieser Aufgabenliste geloggt, be-
- stehend aus Index, ob die Aufgabe ansteht (wieder nur T oder F) und,
- falls die Aufgabe ansteht, der Sollzeit, zu der die Aufgabe zu
- bearbeiten ist.
-
- Anwendungshinweise:
-
- Wie schon angesprochen, können die Prozeduren, die direkt aufzeichnen
- (PLog... mit Ln, Text, Integer und Real) in späteren Versionen dahinge-
- hend abgeändert werden, daß das aufzeichnen auch mit Echtzeitaufgaben
- vorgenommen wird. In erster Näherung genügen die angegebenen Prozeduren
- den Anforderungen, auch wenn sie nicht in Echtzeit verarbeitet werden
- und keine Fehler abfangen. Bei Logging auf Drucker sollte dieser einen
- genügend großen internen Buffer haben und es sollte auch nicht allzu
- viel geloggt werden. Bei Dateien ist ein Schwachpunkt, daß keine oder
- nur sehr schwer Aussagen darüber zu machen sind, wie lange die Positio-
- nierung des Schreibkopfes dauert und wann sie stattfindet. Wenn eine
- Festplatte zur Verfügung steht, sollten wenig Probleme auftreten, bei
- Diskettenlaufwerken wird es schon kritischer. Dann ist darauf zu achten,
- daß das Laufwerk nicht 'steht', also lieber etwas mehr aufzeichnen,
- falls es wirklich einmal stehenbleiben sollte, eine elegantere und
- schnellere Lösung ist aber die Einrichtung eines virtuellen Laufwerks
- (auch RAMDISK genannt), wozu es auch schon Programme in Public Domain
- gibt.
-
- Wie kann eine Anwendung optimiert werden, falls die Angabe der Belastung
- sehr hoch ist, oder zumindest der subjektive Eindruck entsteht, daß
- manchmal die Reaktionszeiten zu groß werden? Dieses nicht allgemein
- lösbare Problem kann durch diesen Debugger soweit eingegrenzt werden,
- daß festgestellt werden kann, welche Aufgabe sehr lange braucht, bis sie
- ausgeführt wird, und welche Aufgaben dazwischen an der Reihe waren,
- ausgeführt zu werden. Wenn das immer die gleichen sind, so ist zu über-
- prüfen, ob diese vielleicht zurückgestellt werden können (etwa durch
- niedrigere Priorität) oder die kritische vorgezogen (durch höhere Priori
- tät oder bei hoher Priorität mit einem kleineren Index, der ja zuerst
- bearbeitet wird). Falls dies nichts hilft, so kann entweder im Scheduler
- (aber Vorsicht bitte!) oder in Aufgaben, die oft drankommen, eine Opti-
- mierung in Bezug auf die Laufzeit erfolgen, die im Extremfall darin be-
- stehen kann, daß einzelne Routinen in Assembler umgeschrieben werden.
- Falls dies alles nichts hilft, so bleibt als letzter Ausweg (vor dem
- aufgeben) die Verwendung eines schnelleren, aber ansonsten kompatiblen,
- Systems oder ein teilweises Neudesign, also neue Zuordnung der Aufgaben,
- evtl. unter Verzicht auf einige, die nur der Kosmetik dienen, aber nicht
- unbedingt erforderlich sind.
-
- Die Prozeduren PlogInhaltHoch, PLogInhaltFifo und PLogInhaltPer werden
- im aktuellen System nicht mehr verwendet, sie wurden aber während der
- Testphase oft gebraucht. Sie können an jeder Stelle aufgerufen werden,
- am sinnvollsten aber im Scheduler selbst vor und nach den Verzweigungs-
- routinen oder in ihnen am Beginn und am Ende. Damit kann auch sehr ein-
- fach nachvollzogen werden, welche Aufgabe zur Verarbeitung drankommt und
- wie die Verwaltung und Aufrufe der Aufgaben vonstatten geht. Speziell
- beim Fifo ist es zu empfehlen, damit einige Kontrollaufzeichnungen zu
- machen, mit denen die Verwaltung von Hand nachträglich kontrolliert
- werden kann. Die anderen Prozeduren können jederzeit an kritischen
- Stellen in eigenen Programmen bei der Entwicklung mit eingebunden wer-
- den, um beispielsweise erklärende Texte und Variable in dasselbe LogFile
- zu be-kommen, in dem auch die Aufrufe aufgezeichnet sind.
-
-
- Nach diesen Hinweisen ist zu hoffen, daß die meisten Leser die Grund-
- strukturen des vorgestellten Echtzeitsystems anwenden können. Ich ermu-
- tige nochmal zu eigenen Experimenten damit, aber bitte nur mit Ar-
- beitsdisketten und nicht an den bestehenden 'Originalen' herumändern,
- sonst könnte es schwierig werden, den bestehenden Zustand wieder herzu-
- stellen.
-
- Nachdem dann hoffentlich alles gut gegangen ist, kann man darangehen,
- zuerst den Debugger zu erweitern. Eine erste Ergänzung bestünde zum
- Beispiel darin, ihn auf eine bestimmte Tastenkombination hin zu akti-
- vieren oder in interaktiven Modus umzuschalten, wobei aber der Rest
- durchaus in Echtzeit ablaufen könnte. Dazu wäre es nötig, in Echtzeit
- Leseprozeduren zu haben, die auch wissen, an welche Aufgabe Eingaben
- geschickt werden sollen. Danach kann dann dynamisch eine oder mehrere
- Variablen ausgelesen und evtl. verändert werden, oder eine Aufgabe in
- die Liste der Aufgaben eingetragen oder (vielleicht sogar selektiv) ge-
- löscht werden. Mit diesen Ergänzungen hätte man schon ein ganz passables
- Echtzeitsystem, das sich auch vor wirklich eingesetzten nicht zu ver-
- stecken braucht, auch wenn es auf einer kleinen Demonstrationsversion
- basiert.
-
- Nachdem schon neben einer ganz kurzen Einführung in die Echtzeitprog-
- rammierung der eigentliche 'Kern', der Scheduler, sowie der Debugger
- vorgestellt worden sind, kommen jetzt sogenannte Utilities an die Reihe.
- Jeder hat diesen Ausdruck im Zusammenhang mit Programmierung sicher
- schon öfters gehört oder gelesen, da es Utilities für die verschieden-
- sten Programmiersprachen und Anwendungen gibt. Verwirrend ist aber, daß
- darunter fast immer etwas anderes zu verstehen ist, manchmal eine Er-
- weiterung oder Ergänzung des Compilers, manchmal Hilfsprogramme für eine
- bestimmte Anwendung, u.s.w. Doch genau das sind eigentlich Utilities,
- sie sind nichts anderes als eine Sammlung von Routinen, die ganz be-
- stimmte (aber im Einzelfall jeweils unterschiedliche) Aufgaben erfüllen
- sollen. Übrigens ist unter Tools meistens das gleiche wie unter
- Utilities zu verstehen, aber diese haben meistens noch einen oder meh-
- rere 'Vornamen', der dann angibt, wozu sie gedacht sind.
-
- Frei ins deutsche übersetzt, sind Utilities ein Sammelsurium von Hilfs-
- programmen, die entweder in eigene Programme eingebunden werden können
- oder bei einer bestimmten Aufgabe, die zu lösen ist, Hilfestellung ge-
- ben. Und genau das machen die jetzt vorzustellenden auch; sie können
- (und müßen sogar) bei diesem kleinen Echtzeitsystem in eigene Programme
- mit eingebunden werden und haben allerlei Aufgaben, die mit ihnen
- leichter zu lösen sind. Eine weitere, meistens sogar mit die wichtigste
- Aufgabe, ist darin zu sehen, daß durch die Verwendung von Utilities be-
- stimmte, mehrfach vorkommende Teilaufgaben immer auf die gleiche Art und
- Weise gelöst werden; also immer richtig (oder machmal leider auch immer
- falsch) bzw. immer mit den gleichen Grundvoraussetzungen.
-
-
- Utilities - Details
-
-
- Die Utilities sind nach dem schon im ersten Teil angegeben Schema auf-
- gebaut; also erst die Deklarationen (siehe Listing, UTILITY.DCL) in ei-
- nem extra File und dann genauso die Verarbeitung (siehe Listing,
- UTILITY. PAS).
-
- Deklarationen:
-
- Die Deklarationen enthalten zuerst eine Konstante, die angibt, bis zu
- welcher größten (Integer-)Zahl der niedrige Anteil von DoppelWort (siehe
- Scheduler) hochgezählt wird, bevor der höhere Anteil erhöht wird. Die
- Wahl von 9999 ist rein willkürlich, es hätte genauso gut die größte po-
- sitive Integer sein können. Ich habe diese genommen, da damit einfacher
- die Umwandlungroutinen mit Reals überprüft werden können; der niedrige
- Anteil sind einfach die 4 Ziffern rechts, der höhere Anteil die linken
- Ziffern.
-
- Danach werden die Register des 8086 Prozessors deklariert, die bisher
- aber nur bei dem Funktionsaufruf für die Uhrzeit Verwendung finden. Die
- Definitionen der Strings sind abhängig vom verwendeten Compiler, aber
- hier könnten auch eigene Deklarationen und danach, in der Verarbeitung,
- die zugehörigen Prozeduren für die Stringverarbeitung kommen. Zum Schluß
- der Deklarationen werden wieder die Routinen als Forwards deklariert.
-
- Verarbeitung:
-
- Der Verarbeitungsteil der Utilities ist sehr einfach, da er nur aus
- einzelnen Prozeduren besteht, die untereinander nicht oder zumindest
- fast nicht zusammenhängen. Dies ist aber nicht notwendigerweise so, es
- können auch sehr komplexe Unterprogramme implementiert werden, aber in
- diesem kleinen System zur Echtzeitprogrammierung ist es bisher noch
- nicht nötig.
-
- Die Prozedur PIncrDW erhöht ein Doppelwort um 1, wobei aber davon aus-
- gegangen wird, daß dieses größer oder gleich 0 ist (dies wird auch bei
- den beiden folgenden Routinen angenommen).
-
- Die Funktion DWinReal gibt als Ergebnis eine RealZahl, die gleich dem
- Doppelwort ist, das als Parameter übergeben wurde. Dabei ist zu beach-
- ten, daß der niedrige Anteil 1 Element mehr enthält, als durch die max-
- imale Anzahl gegeben ist, da bei jedem Rücksetzen auf 0 zurückgesetzt
- wird, und diese als ein Element mitzählt.
-
- Die Prozedur RealinDW wandelt eine RealZahl in ein Doppelwort. Die Er-
- höhung des maximalen niedrigen Anteils um 1 ist wieder zu beachten.
-
- Die Funktion TimeNow benutzt eine DOS-Funktion, mit der die aktuelle
- Uhrzeit geholt wird. Diese Funktion ist bei anderen Systemen anzupassen,
- es kann aber zur Not auch, wie schon beim Scheduler angedeutet, einfach
- ein Zähler genommen werden. Als Besonderheit ist hier zu beachten, daß
- als kleinste Zeiteinheit 1/100 Sekunden gewählt worden sind. Dies er-
- scheint auf den ersten Blick etwas seltsam, aber es ist die kleinste
- Einheit, die der Funktionsaufruf liefert, wenn auch nicht bei jedem auf
- 1/100 Sekunden genau, und somit wird eine Division vermieden, was bei
- ganzen Sekunden nötig wäre. Falls auf Millisekunden ausgewichen wird, so
- täuscht diese Funktion eine größere Genauigkeit vor, als tatsächlich
- geliefert wird. Das Verfahren, solch generelle Funktionen in Utilities
- zu realisieren, zeigt hier seinen entscheidenden Vorteil: wenn die Ein-
- heit geändert werden soll, so genügt eine Änderung an dieser Stelle, und
- alles andere greift auf die aktuelle Einheit zu!
-
- Die Prozedur PutHoch reiht eine Aufgabe mit der angegebenen Nummer in
- die Aufgaben der hohen Priorität ein, falls diese Aufgabe noch nicht
- belegt ist. Falls diese Aufgabe aber schon in der Aufgabenliste steht,
- so wird ein Zähler für Overflow erhöht.
-
- Die Prozedur PutFifo reiht eine Aufgabe mit der angegebenen Aufgaben-
- nummer und dem Parameter in den Fifo ein, falls noch ein Platz darin
- ist. Falls der Fifo schon voll ist, so wird wieder ein Zähler für Over-
- flow erhöht.
-
- Die Prozedur PutPerAbs reiht eine Aufgabe mit der angegebenen Nummer in
- die Liste der periodischen Aufgaben ein, falls diese Aufgabe noch nicht
- belegt ist, andernfalls wird wieder ein Zähler für Overflow erhöht. Die
- anzugebende Zeit ist die absolute Zeit, zu der die Aufgabe bearbeitet
- werden soll.
-
- Ein einfaches Beispiel dazu: 12 Uhr 35 Minuten 20,57 Sekunden ist
- ((((12*60)+35)*60)+20)*100+57 = 4 532 057
-
- Die Prozedur PutPerDelta ist die Parallele zu PutPerAbs, aber mit einer
- sogenannten Deltazeit. Eine Aufgabe, die in 1 Sekunde starten soll, be-
- nötigt als Parameter 100.
-
- Die Funktion FrageJN gibt den zu übergebenden String aus und wartet auf
- die Eingabe von J oder N. Bei einer anderen Eingabe wird ein Hinweis
- ausgegeben, und so lange wiederholt, bis korrekt eingegeben wird. Bei J
- wird der Funktionswert True, bei N False übergeben. Bei der Eingabe
- könnte genauso gut ein einfaches Lesen auf String oder Charakter erfol-
- gen, falls eine Anpassung an andere Systeme zu umständlich wird.
-
- Die Funktion FrageInteger gibt den übergebenen String aus und liest so-
- lange eine Integer ein, bis sie im angegebenen Wertebereich liegt. Der
- eingelesene Wert wird dann als Funktionswert übergeben. Hier könnte
- beispielsweise eine andere Routine das Einlesen übernehmen, die nur
- Integerwerte annimmmt, da bei dem angegebenen Verfahren ein Runtime-
- Error entsteht, falls sonstige Charakter oder Reals eingegeben werden.
-
- Solche abgesicherten Routinen sind aber schon häufig veröffentlicht
- worden und sollen nicht Bestandteil dieses Echtzeitsystems sein, unter
- anderem auch deshalb, weil hier echt gewartet wird, bis eine korrekte
- Eingabe erfolgt, also von Echtzeit keine Spur. Benötigt werden diese
- Routinen in dem vorgestellten System für den Debugger bei der Initiali-
- sierung, also bevor Echtzeitaufgaben laufen.
-
- Anwendungshinweise:
-
- Die Routinen der Utilities stellen bis auf die Funktionen zur Abfrage
- (die selbst aber nicht in Echtzeit ablaufen!) das absolute Minimum dar,
- was für Echtzeitsysteme notwendig ist. Bei Bedarf können sie jederzeit
- erweitert werden, es ist sogar sinnvoll, alle Erweiterungen, die in
- Echtzeit zu verarbeiten sind, in den Utilities unterzubringen, da dann
- eventuell anfallende Korrekturen oder Ergänzungen an einer Stelle er-
- folgen.
-
- Alle Echtzeitaufgaben sollten mit den angegebenen Put... Prozeduren in
- die entsprechenden Listen eingetragen und nicht 'von Hand' nachcodiert
- werden. Die Log... Prozeduren können im Notfall wieder entfallen, es ist
- aber wirklich nicht zu empfehlen, auf sie zu verzichten.
-
- Die Prozeduren, die Aufgaben eintragen, haben aber noch einen 'Haken',
- sie dürfen nämlich nicht so ohne weiteres von Interruptprozeduren auf-
- gerufen werden! Wa-rum denn das? Man stelle sich vor, daß gerade eine der
- Put... Prozeduren bearbeitet wird, und dann kommt ein Interrupt. Dieser
- unterbricht die Prozedur, und verzweigt zu der entsprechenden Interrupt-
- verarbeitung. Wenn dann in dieser dieselbe Put... Prozedur aufgerufen
- wird, so kann das System unkontrolliert reagieren, da teilweise auf
- globale Variablen (beispielsweise Zeiger bei Fifo, Booleans, die ange-
- ben, ob eine Aufgabe zu erledigen ist bei hoher Priorität und Periodic)
- zugegriffen wird. Dies gilt nicht nur bei Unterbrechungen der Put...
- Prozeduren, sondern auch bei den entsprechenden Teilen der Verzweigungen
- im Scheduler.
-
- Wie wäre dies zu lösen? Man kann jeweils während der 'kritischen' Teile
- Interrupts sperren und danach wieder freigeben. Dies ist nicht allzu
- schwierig, aber leider so systemabhängig, daß es hier nicht implemen-
- tiert ist. Genauso ist es, wenn jeweils eigene Put... Prozeduren für
- Interrupts verwendet werden, die nachschauen, ob ein kritischer Teil
- bearbeitet wird oder nicht. Falls dies nicht der Fall ist, so kann die
- normale Put... Prozedur aufgerufen werden, falls aber doch, so könnte
- zurückgesprungen werden, zuvor aber das Ende des kritischen Bereichs
- dynamisch so verändert, daß danach die weitere Interruptverarbeitung er-
- folgt. Eine andere Möglichkeit wäre, alle Aufgaben mit Fifos zu ver-
- walten, und ein ausgeklügeltes System zu verwenden, das jeweils beim
- Verzweigen bzw. Eintragen nur einen Index verändert (hier wird bei-
- spielsweise FifoLeer bei beiden verändert!). Diese Systeme sind schwerer
- zu verstehen und benötigen mehrfach Divisionen, die auf den meisten
- Rechnern sehr viel Zeit in Anspruch nehmen.
-
- Die einfachste Möglichkeit ist aber, auf Interrupts von außen so weit
- wie möglich zu verzichten und, falls externe Ge-räte mit diesem Echtzeit-
- system bedient werden sollen, den jeweiligen Status periodisch mit einer
- der Periodicaufgaben abzufragen. Dieses Verfahren nennt man Polling, und
- es ist gar nicht mal so selten anzutreffen. Manche Geräte liefern auch
- gar keinen Interrupt, sondern nur einen Status auf Anforderung, wie etwa
- viele Drucker. In diesem Fall wird dann aber normalerweise eine Art von
- Periodic als Interrupt vorgeschaltet, etwa indem ein interner Timer-
- interrupt angezapft wird, der einen Zähler bedient und in einer eigenen
- Liste nachschaut, ob etwas und was (aber nur sehr wenig!) damit zu tun
- ist. Dabei wird dann höchstens eine Aufgabe mit hoher Priorität initi-
- iert und, um diese Liste nicht durcheinander zu bringen, im Scheduler
- bei VerzweigHoch dieser periodische Interrupt gesperrt, solange nach
- einer Aufgabe gesucht wird. Diese Aufgabe wird dann noch während Inter-
- rupts gesperrt sind aus der Liste gelöscht; im verwendeten System wird
- kein Interrupt gesperrt (siehe vorne) und das Löschen erst danach vor-
- genommen. Trotzdem kann bei entsprechender Disziplin auch von Interrupts
- eine Aufgabe hoher Priorität in die Liste der Aufgaben eingetragen wer-
- den; wenn nämlich eine bestimmte Aufgabe hoher Priorität nur von einem
- Interrupt (immer demselben!) angestossen wird, geht es trotzdem gut,
- sofern kein Logging für diese Aufgabe vorgenommen wird. Das Protokol-
- lieren von Interrupts ist sowieso ein eigenes Thema, das im allgemeinen
- nur dadurch zu realisieren ist, daß im Systemdesign eigene Aufgaben da-
- für vorgesehen werden, die dann bei auftreten des Interrupts nur initi-
- iert werden.
-
- Ist aber dieses System für Echtzeitaufgaben zu gebrauchen? Frei nach
- Radio Eriwan könnte die Antwort lauten: "Im Prinzip ja, aber ohne dis-
- ziplinierte Anwenderprogramme nicht." Dies liegt daran, daß eine gerade
- zu bearbeitende Aufgabe nicht unterbrochen wird, wenn eine mit höherer
- Priorität in die Liste der Aufgaben eingetragen wird, sondern die näch-
- ste Aufgabe vom Scheduler erst aufgerufen wird, nachdem die gerade be-
- arbeitete die Kontrolle zurückgegeben hat. Dieser kleine (vielleicht
- auch große) Nachteil kann in Kauf genommen werden, wenn bei jeder (aber
- wirklich auch jeder) Aufgabe daran gedacht wird, die Kontrolle alsbald
- wieder zurückzugeben; eine typische Aufgabe für die Design-Phase, die
- längste Zeit, die eine Aufgabe brauchen darf, festzulegen. Es ist also
- nicht möglich, eine 'normale' Read-Anweisung in Pascal durchzuführen, da
- diese einfach wartet, bis Zeichen anstehen. Es ist aber möglich, dies zu
- umgehen, indem zuerst abgefragt wird, ob ein Zeichen ansteht, dieses
- dann eingelesen wird, aber solange kein Zeichen ansteht, einfach
- periodisch abgefragt wird (beispielsweise alle 200 Millisekunden), ob
- ein Zeichen ansteht. Erst wenn alles eingelesen ist (aber über
- Periodic!) wird dann eine Meldung an die Aufgabe geschickt, die dies
- angefordert hat.
-
- Eine weitere mögliche Ergänzung besteht darin, bei jeder Aufgabe zu pro-
- tokollieren, wie lange sie braucht und wie lange sie in der Liste der
- Aufgaben gewartet hat, bis sie bearbeitet wurde. Dies ist eine Mischung
- zwischen Debug und Utilities, je nach (selbst festzulegendem) Schwer-
- punkt oder 'frei nach Gefühl' kann dies einem der beiden Module zuge-
- ordnet werden.
-
- Als letzten Tip zu den Utilities ist noch anzumerken, daß eine peri-
- odisch wiederkehrende Aufgabe in erster Näherung einfach dadurch zu
- realisieren ist, daß diese Aufgabe sich selbst wieder in die Liste der
- Aufgaben reinschreibt (mit PutPerDelta), aber vorhandene Verzögerungen
- addieren sich dabei auf. Eine andere Möglichkeit besteht darin, daß
- diese Aufgabe sich selbst die nächste 'Soll-Aufrufzeit' mit merkt, und
- beim reinschreiben die Prozedur PutPerAbs benutzt wird, mit absoluter
- Zeit gleich der nächsten Sollzeit. Auf diese Zeit wird dann (zum sich
- merken) wieder das passende Delta addiert. Damit kann durchaus einmal
- eine Verzögerung auftreten, aber normalerweise wird sie in den nächsten
- Zyklen wieder aufgeholt, indem dann einfach etwas schneller wieder die
- Aufgabe drankommt. Ob so ein Verfahren sinnvoll ist, muß aber bei jeder
- Aufgabe extra entschieden werden.
-
- Die ersten Versuche mit dem soweit kompletten Echtzeitsystem sind er-
- wartungsgemäß nur dem Test dieses Systems vorbehalten. Dazu wird ein
- Testprogramm benutzt (siehe Listing, TEST02.PAS), das einmal alle mög-
- lichen Aufgaben in die Liste der Aufgaben einträgt und zum anderen die
- Konstante für die Nullbelastung bestimmt. Für die Bestimmung dieser
- Konstanten kann beliebig viel Aufwand getrieben werden, aber nachdem
- schon bei der Erklärung des Debuggers darauf hingewiesen wurde, daß eine
- Aussage zur Belastung doch nur relativ ist, genügt es vollkommen, den
- Scheduler ca. 10 Minuten 'leer' laufen zu lassen, d. h. ohne jede Auf-
- gabe, und den Belastungszähler dann auszuwerten. Diese Auswertung ist
- auch nicht sehr detailliert, man kann sehr einfach solange probieren,
- bis als Endergebnis beinahe 0 herauskommt. Damit wird aber nur die Kon-
- stante für das System bestimmt, auf dem das Testprogramm lief; auf an-
- deren kann ein ganz anderer Wert herauskommen. Eine andere Möglichkeit
- bestünde darin, bei InitSchedul durch einen kurzen leeren Testlauf von
- beispielsweise 2 Sekunden diesen Wert grob bestimmen zu lassen (aber
- dann in einer Variablen ablegen!).
-
- Diese Aufgabe wird einmal durch das Testprogramm selbst ausgeführt, in-
- dem die periodische Aufgabe Nummer 1 in die Liste der Aufgaben einge-
- tragen wird, zum anderen durch diese Aufgabe (siehe Listing, File
- PERIOD1 .PAS), indem die Nullbelastung berechnet und ins LogFile ausge-
- geben wird. Dabei wird in einer Aufgabe die Ausgabemöglichkeit des
- Debuggers aufgerufen. Nachdem dabei für die NullBelastung bei meiner
- Maschine der Wert von 2.57 (oder 257 für 100 mal) herausgekommen ist,
- habe ich diese Zahl als Konstante ins File SCHEDUL.DCL übernommen.
-
- Dieses Beispiel zeigt auch, daß in einem Programm zweimal nacheinander
- Echtzeitteile drin sein können, aber dies wurde nur deshalb gemacht,
- damit nur ein Testprogramm nötig ist; es hätten genauso gut zwei ge-
- trennte sein können.
-
- Die LogFiles sind in diesem Fall das interessanteste, es sind TEST0
- 2_1.LOG und TEST02_2.LOG. Bei Schwierigkeiten mit der Reihenfolge der
- Verzweigungen sollten zusätzlich die Prozeduren des Debuggers zur
- Verfolgung der Aufgabenlisten (PLogInhalt...) an verschiedenen Stellen
- aufgerufen werden, siehe auch dazu die Hinweise beim Debugger.
-
- Scheduling begonnen: 6950403 beendet: 6950843 Dauer: 440 (1/100 Sek)
- Anzahl Durchläufe des Schedulers: 936
- ergibt eine Belastung von 170 Promill
- Overflow bei hoher Priorität: 0
- Fifo: 0
- Periodic: 0
-
-
- File TEST02_2.LOG
-
- Periodic Nr. 1 mit Delta 60000 (7012639 abs) in Aufgabenliste eingetragen
- Periodic Nr. 1 SollZeit 7012644 IstZeit 7012645
- Belastungszaehler: Hoch 15
- Niedrig 4207
- Real Wert: 154207
- ergibt 257 Durchläufe in 1/10000 Sek. (=NullBelastung * 100)
-
- Scheduling begonnen: 6952644 beendet: 7012645 Dauer: 60001 (1/100 Sek)
- Anzahl Durchläufe des Schedulers: 154207
- ergibt eine Belastung von -3 Promill
- Overflow bei hoher Priorität: 0
- Fifo: 0
- Periodic: 0
-
- Anwendung - Einführung
-
-
- Nachdem das Echtzeitsystem jetzt fertig ist, soll natürlich an einem
- Beispiel dessen Anwendung und Funktionsfähigkeit demonstriert werden.
- Doch was eignet sich dazu? Am einfachsten und sinnvollsten wäre sicher
- eine mehr oder weniger komplexe Meßwerterfassung mit dazugehöriger Steu-
- erung, aber dazu ist extra Hardware erforderlich, die sicher nicht je-
- dem, der bis jetzt dieser Serie gefolgt ist, zur Verfügung steht. Das-
- selbe gilt sogar bei sogenannten Lehrsystemen zu demselben Thema, die
- inzwischen relativ günstig zu bekommen sind, aber der Preis und die
- Frage 'wozu?' sind meistens noch ein Hindernis. Deshalb wird hier ein
- Spielprogramm als Anwendung vorgestellt, das nicht allzu einfach sein
- soll, aber doch noch verständlich und gleichzeitig eine mehr oder we-
- niger sinnvolle Anwendung der Echtzeitprogrammierung darstellt. Unbe-
- stritten ist aber, daß ein ähnliches Spielprogramm auch ohne Echtzeit-
- system erstellt werden kann, aber ich hoffe, daß mit diesem der Ablauf
- leichter zu verstehen (und evtl. zu ändern) ist. Zusätzlich ist zu er-
- warten, daß dieses Spiel auf allen Systemen etwa gleich schnell abläuft,
- also nicht auf einem schnellen PC-AT nicht mehr zu spielen ist. Das
- einzige evtl. auftretende Problem könnte bei sehr langsamen Maschinen
- und Interpreter anstelle eines Compiler eine Überlastung werden, aber im
- Normalfall ist dies nicht zu erwarten.
-
- Die erste Frage ist, welche Ein- und Ausgabemöglichkeiten der 'normale
- Leser' zur Verfügung hat. Hier wird davon ausgegangen, daß zur Eingabe
- die Tastatur ausreichen muß, da die sicher bei jedem vorhanden ist. Der
- Ausgabe dient der Bildschirm, den auch jeder hat, aber dazu kommt noch
- die Möglichkeit der akustischen Untermalung (bei PC's ebenfalls Stan-
- dard) und eine Druckerausgabe. Die Druckerausgabe ist nicht unbedingt
- erforderlich, da sie bei dem Spiel kaum sinnvoll ist und nur zur Ausgabe
- der Resultate und zu Demonstrationszwecken eingebunden ist; zudem sind
- schon viele residente Druckerutilities in Umlauf oder veröffentlicht
- worden. Eine schöne Demonstration der Echtzeitfähigkeit liegt aber dar-
- in, daß der Status des Druckers praktisch 'jederzeit' angezeigt und auch
- etwas ausgedruckt wird, obwohl nebenher ein Spiel abläuft. Dies ist aber
- leider in der vorgestellten Version nur bei PC's so zu realisieren, bei
- anderen Systemen muß dieser Teil angepasst (oder weggelassen) werden;
- dasselbe gilt natürlich auch für die akustische Untermalung. Um die
- Bildschirmausgaben 'allgemein' zu halten, wird nicht der Grafik-,
- sondern nur der Textmodus benutzt; hier sind Anpassungen wahrscheinlich
- nicht erforderlich.
-
- Als nächstes stellt sich natürlich die Frage, wie das Spiel ablaufen
- soll. Um nicht wieder ein Schießspiel (egal ob wirklich neu oder ein
- altes in neuer Version) zu spielen, habe ich mir folgendes ausgedacht:
-
- Der Rechner generiert zufällig eine Folge von Buchstaben, die am Bild-
- schirm dargestellt werden. Der Bediener (Spieler) muß nun diese Buch-
- staben eingeben, wobei aber beim ersten Fehler abgebrochen wird und für
- jede neue Folge steht etwas weniger Zeit zum Eingeben zur Verfügung.
- Nach 15 Folgen ist das Spiel beendet. Auf dem Drucker wird jede Folge
- und auch eine Meldung ausgegeben, ob diese korrekt eingegeben wurde, ein
- Fehler war oder die Zeit überschritten wurde. Bei Spielende wird eine
- Art von kleiner Statistik auf dem Drucker und auf dem Bildschirm
- ausgegeben, die unter Umständen für eine Auswertung der
- Belastungsmöglichkeit des Spielers herangezogen werden könnte; aber dies
- bleibt dem dafür ausgebildeten Personenkreis vorbehalten und wird nicht
- vom Programm übernommen. Der Zustand des Druckers wird parallel am
- Bildschirm angezeigt. Bei jeder Folge von Buchstaben wird ein Geräusch
- ausgegeben, dessen Frequenz von der verbleibenden Zeit (für die aktuelle
- Folge) abhängig ist.
-
- Anwendung - Design
-
-
- Eine der schwierigsten Aufgaben bei den Echtzeitsystemen ist das Design.
- Darunter versteht man die Aufteilung in einzelne Komponenten (Software
- und Hardware), und die Definition deren Aufgabe; hier wird aber davon
- ausgegangen, daß die verwendete Hardware den Anforderungen genügt. Diese
- Aufteilung erfolgt meistens Top-Down, das bedeutet, daß zuerst eine
- grobe Aufteilung gemacht wird, die anschließend weiter verfeinert (jede
- Aufgabe wird wieder in mehrer Aufgaben aufgeteilt) wird. Ein großes
- Problem besteht darin, daß diese Aufteilung 'konsistent' sein muß, d.h.
- daß eine Aufgabe nur die Eingaben verarbeiten kann, die ihr auch zur
- Verfügung gestellt werden. Dies hört sich zuerst trivial an, aber wie
- sieht es nach der x-ten Änderung aus? Erfahrungsgemäß rühren sehr viele
- Fehler daher, daß bei einer Änderung ein Zustand verändert wird, der
- aber von einer anderen Komponente praktisch nebenher mitverwendet wird.
- Diese Fehler zu finden ist gar nicht mehr so einfach, es wird aber ver-
- sucht, durch verschiedenartige Methoden, die beim Design und bei der
- Realisierung angewendet werden, diese Art Fehler zu vermeiden. In
- neuerer Zeit sind mehrere softwareunterstützte Tools auf dem Markt, die
- bei dieser Aufgabe helfen, aber bis heute hat sich noch keiner soweit
- durchsetzen können, daß von einem Standard die Rede sein kann. Dies
- liegt sicher auch daran, daß sich schon bei den Methoden, die ja auch
- manuell angewandt werden können, noch keine hat durchsetzen können. Al-
- lein die Ansicht, daß eine grafische Darstellung günstig ist, hat sich
- so ziemlich durchgesetzt, aber schon bei den einzelnen Komponenten (de-
- ren Bezeichnung und Darstellung) scheiden sich die Geister.
-
- Auf diese Problematik soll aber im Rahmen dieser Serie über Echtzeit-
- programmierung nicht weiter eingegangen werden, hier soll eine Anwendung
- entworfen werden. Dazu wird eine Art von strukturierter Analysis be-
- nutzt, wie sie von de Marco [1] beschrieben wurde. Nicht auf Software be-
- zogen, kann dasselbe als eine Art Blockschaltbild bezeichnet werden,
- wobei die Komponenten nicht aus Hardware, sondern hier aus Software be-
- stehen.
-
-
- Eine mögliche Aufteilung sieht folgendermaßen aus:
-
-
- ┌─────────────────────────────────────────────────────────┐
- │ P E R I O D I C │
- └────────┬─────────────────┬───────────────────┬──────────┘
- │ │ │
- ┌────────┴────────┐┌───────┴─────────┐┌────────┴──────────┐
- │Folge ││Tastatur ││Drucker-Status │
- │generieren ││abfragen ││abfragen │
- │(Per1) ││(Per2) ││(Per3) │
- └────────┬────────┘└───────┬─────────┘└────────┬──────────┘
- │ │ │
- ┌────────┴────────┐┌───────┴─────────┐┌────────┴──────────┐
- │Timeout für ││Taste verarbeiten││Status darstellen │
- │diese Folge ││und Geräusch ││und Zeichen drucken│
- │(Per1) ││(Per2) ││(Per3) │
- └─────────────────┘└─────────────────┘└───────────────────┘
-
-
- Diese erste Aufteilung soll in diesem Fall auch schon gleichzeitig die
- letzte sein, da sie schon ziemlich dedailliert für die kleine Aufgabe
- ausgefallen ist. Normalerweise wird, zumindest bei größeren Projekten,
- eine Beschreibung mit den dazugehörigen Eingangs- und Ausgangsdaten er-
- stellt, die dann als Grundlage für eine weitere Verfeinerung dient. Hier
- sagen die Bezeichnungen eigentlich schon genügend aus, so daß sogar auf
- eine Beschreibung verzichtet wird, vor allem aber auch deshalb, weil die
- Komponenten eigentlich sehr gut im Listing bzw. in den folgenden Details
- wiederzufinden sind.
-
- Auf eine Besonderheit muß jedoch noch hingewiesen werden; hier wird
- ausschließlich die periodische Steuerung des Ablaufs benutzt, wobei jede
- Periodic nach dieser Aufteilung 2 Aufgaben wahrnimmt. Daß nur die
- Periodic verwendet wird, liegt erstens im Spiel (Art der Aufgabe), aber
- es wäre zweifellos angebracht, die Tastatur über Interrupt zu
- verarbeiten. Dies wurde aus dem inzwischen schon zur Genüge bekannten
- Grund der Inkompatibilität zwischen verschiedenen System (Hardware) und
- Compilern (Software) wieder einmal umgangen, und es funktioniert auch
- so. Daß eine Aufgabe gelegentlich mehrere, die doch irgendwie
- zusammenhängen (zumindest von der Logik her) wahrnimmt, kommt auch in
- größeren Systemen häufiger vor, zumindest spricht nichts dagegen, da ja
- eine normale Prozedur auch je nach Eingangswerten in verschiedene Zweige
- aufgeteilt werden kann.
-
-
- Anwendung - Details
-
-
- Nach diesen allgemeinen Überlegungen zu einer Anwendung in Form eines
- Spiels folgt nun die Erklärung der einzelnen Komponenten. Die
- Modularisierung wird so ähnlich wie bisher vorgenommen; es gibt ein
- Deklarationsfile (siehe Listing, SPIEL.DCL), das alle mehrfach
- verwendeten Konstanten und Variablen enthält, ein HauptFile (SPIEL.PAS),
- und für jede Aufgabe ein extra File (FOLGE.PAS, TASTATUR.PAS und
- DRUCKER.PAS). Hier kommt noch ein weiteres File zur Initialisierung
- (INITIAL.PAS) dazu, das neben der Spielerklärung alle Variablen
- vorbelegt; bei Turbo-Pascal könnte dies schon in der Deklaration
- geschehen, aber das geht nicht bei allen Versionen von Pascal, deswegen
- wurde es extra in einer Prozedur gemacht.
-
-
- Deklarationen:
-
-
- Bei den Deklarationen wurde kein Wert auf eine ausgeklügelte
- Datenstruktur gelegt; es wurden nur Grundtypen verwendet. Die Kommentare
- müßten bei weitem ausreichen, um zu verstehen, wozu die jeweilige
- Variable verwendet wird, deshalb folgt hier keine genauere Erklärung.
-
- Hauptprogramm (SPIEL.PAS):
-
- Das Hauptprogramm enthält die schon bekannten Includes für die
- allgemeinen Files, danach folgen die speziell für diese Anwendung
- erforderlichen. Bei der Verarbeitung wird zuerst der Debugger
- initialisiert, dann der Scheduler und danach der Anwendungsteil. Um
- danach sofort Schedul aufrufen zu können, muß bei der Initialisierung
- des Anwendungsteils mindestens eine Aufgabe in den Scheduler eingetragen
- werden (siehe in PInit). Zum Schluß wird nur noch der Bildschirm
- gelöscht. Die Initialisierung des Debuggers erfolgt bei fertigen
- Programmen meistens ohne jegliches Protokollieren, auch ohne Abfrage, ob
- protokolliert werden soll, außer wenn dies eine spezielle Anforderung an
- das System ist. Während der Testphase wurde aber von Protokollen
- ausgiebig Gebrauch gemacht; es wurde teilweise an kritischen Stellen
- extra aufgezeichnet (siehe Debugger, Anwendungshinweise).
-
- Initialisierung (INITIAL.PAS):
-
- Es wird eine Spielerklärung ausgegeben, abgefragt, welcher Drucker
- verwendet werden soll, Variablen initialisiert und die Aufgaben fürs
- Generieren einer Folge sowie für Ausgabe auf den Drucker in die Liste
- der Aufgaben eingetragen.
-
- Die Prozedur PErklaerung hat eine lokale Prozedur InitRandom, mit der
- eine eingebaute Zufallsfunktion wirklich zufällig wird. Dies ist bei
- vielen BASIC-Versionen dringend nötig, da nach dem Start Random immer
- die gleiche Folge von Zahlen erzeugt. Ob es in Pascal wirklich nötig
- ist, wurde nicht ausprobiert; dies ist auf jeden Fall der sichere Weg.
- Dem Zufall wird dadurch auf die Sprünge geholfen, indem solange die
- Zufallsfunktion aufgerufen wird, bis eine Taste gedrückt ist. Da dies
- nicht immer gleich schnell passiert, wird unterschiedlich oft die
- Funktion aufgerufen; also erzeugt sie zufälligere Werte. In der
- Hauptprozedur wird die Bedienungsanleitung ausgegeben, danach die
- Zufallsfunktion initialisiert und dann das Gerüst des Bildschirms
- aufgebaut.
-
- Die Prozedur PInit initialisiert zuerst einige Werte (evtl auch anders
- möglich, siehe vorne), dann wird PErklaerung aufgerufen und zum Schluß
- die Aufgaben der Periodic 1 und 3 in die Liste der Aufgaben eingetragen.
-
- Folge generieren und Zeit dafür überwachen (FOLGE.PAS):
-
- Dieser Teil beinhaltet das generieren einer Folge sowie eine TimeOut
- Überwachung, ob die letzte Folge in der zur Verfügung stehenden Zeit
- eingegeben wurde.
-
- Zuerst wird die Prozedur FolgeAusgeben zur Verfügung gestellt, die eine
- Folge auf dem Bildschirm und auf dem Drucker (durch Aufruf der
- Druckerprozeduren Druck...) ausgibt.
-
- Dann gibt es eine Prozedur Statistik, die eine kurze Statsitik über das
- abgelaufene Spiel erstellt und wieder auf beiden Ausgabemedien ausgibt.
- Für die weitere Ablaufsteuerung wird noch eine Variable (RestDrucken)
- auf True gesetzt, mit der angegeben wird, daß keine eigentliche
- Verarbeitung mehr passiert, sondern nur noch der Rest (von Folgen und
- Statistik) ausgedruckt werden soll.
-
- Danach folgt die Hauptverarbeitung in der Prozedur PPeriodic3. Hier ist
- zu beachten, daß diese Prozedur aus dem File SONSTIGE.PAS gelöscht oder
- als Kommentar gekennzeichnet wird, da sonst Fehler während des
- Compilierens auftreten. Diese Prozedur hat 2 Hauptzweige, entsprechend
- den Aufgaben generieren und überwachen.
-
- Beim generieren werden zuerst einige Variablen gesetzt, dann die Folge
- mit der Randomfunktion generiert (bei nicht initialisierter Funktion
- könnte evtl. immer die gleiche Folge entstehen, siehe bei Initialisiere)
- und die Folge mit FolgeAusgeben auf dem Bildschirm ausgegeben. Bei der
- Generierung der Folge wird noch extra darauf geachtet, daß nicht 2
- gleiche Buchstaben direkt aufeinander folgen. Dies könnte evtl. dazu
- führen, daß mit Autorepeat der Tastenfunktion eine komplette Folge
- praktisch 'auf einmal' eingegeben werden könnte, aber viel
- entscheidender ist, daß sonst Autorepeat von der Tastaturverarbeitung
- selbst erledigt werden müßte. Warum denn das, wird sich mancher zuerst
- denken. Ganz einfach, weil die Zeit bis zur Wiederholung zwischen dem 1.
- und 2. Buchstaben sehr viel größer ist, als zwischen den folgenden. Wie
- könnte dann nach 4 gleichen Buchstaben beispielsweise ein anderer
- 'gezielt an dieser Stelle' eingegeben werden wenn vorher wiederholt
- wird? Beim ersten Aufruf der Aufgabe, also wenn die 1. Folge generiert
- wird, wird die Aufgabe mit der Periodic 2 (Tastaturabfrage) mit der
- aktuellen Zeit in die Liste der Aufgaben aufgenommen; sie kommt also
- sofort dran. Dann trägt sich diese Periodic selber in die Liste der
- Aufgaben ein, um den TimeOut zu überwachen, wobei die Zeit, die zur
- Verfügugn steht, am Anfang berechnet wurde.
-
- Beim Überwachen der Zeit für die zuletzt generierte Folge wird zuerst
- überprüft, ob noch nicht alle Buchstaben eingegeben wurden und noch kein
- Fehler auftrat, um einen entsprechenden Hinweis auszugeben; falls alle
- korrekt eingegeben wurden oder ein Fehler entdeckt wurde, so wird dies
- bei der Tastaturabfrage mit bearbeitet. Wenn noch nicht alle 15 Folgen
- fertig sind, wird eine Variable FolgenGenerier, mit der die Verzweigung
- in generieren und überwachen erfolgt, zu True gesetzt und dieselbe
- Aufgabe wieder in die Liste aufgenommen, damit nach 5 Sekunden eine neue
- Folge generiert wird. Wenn aber alle Folgen beendet sind, wird Statistik
- aufgerufen, um eine kleine Auswertung zu machen.
-
- Tastatur abfragen, auswerten und Geräusch (TASTATUR.PAS)
-
- Die Aufgabe dieses Teils besteht darin, mit der Periodic 2 die Tastatur
- abzufragen, den Code einer gedrückten Taste auszuwerten und, wenn gerade
- eine Folge einzugeben ist, das entsprehende Zeichen darzustellen.
- Daneben wird dann noch festgestellt, ob bei der Eingabe ein Fehler
- auftrat oder die Folge schon komplett eingegeben ist, sowie ein Geräusch
- ausgegeben, falls weder ein Fehler festgestellt wurde, noch die Folge
- fertig ist.
-
- Zu diesen Aufgaben wird zuerst die Prozedur EingabeAuswerten
- bereitgestellt, die, wie ihr Name schon sagt, die Eingaben während einer
- Folge auswertet. Dazu wird zuerst untersucht, ob das Zeichen ein
- Großbuchstabe ist, da alle anderen nicht beachtet werden. Die gewählte
- Konstruktion der Abfrage eines geschlossenen Bereiches auf die Grenzen
- ist im allgemeinen erheblich schneller als die von Pascal vorgegebene
- Funktion IN; nur speziell auf Laufzeit getrimmte Compiler können bei
- solchen Spezialfällen mit IN den gleich schnellen Code erzeugen, wie es
- der Programmierer durch die gezielte Abfrage vorgibt. Danach wird das
- Zeichen dargestellt und mit dem nächsten der Folge verglichen. Falls
- eine Übereinstimmung festgestellt wird, dann wird noch untersucht, ob
- die Folge komplett fertig ist und, dalls dies der Fall ist, ein Hinweis
- ausgegeben und die Variable FolgeEingeben zu False gesetzt. Wenn ein
- Fehler auftrat, wird ebenfalls ein entsprechender Hinweis ausgegeben und
- dieselbe Variable gesetzt.
-
- In der Hauptprozedur PPeriodic2 wird zuerst nachgeschaut, ob eine Taste
- gedrückt ist. Falls keine Taste gedrückt ist, wird sofort die neue Zeit,
- zu der dieselbe Prozedur wieder aufgerufen werden sollte, berechnet und
- die Aufgabe wieder in die Liste der Aufgaben übernommen. Mit dieser
- etwas eigenwilligen Konstruktion, daß ein Delta zu der ursprünglichen
- Sollzeit addiert wird, kann sichergestellt werden, daß zumindest im
- Mittel diese Aufgabe mit der durch Delta festgelegten Frequenz
- aufgerufen wird, auch wenn die Genauigkeit der Uhr oder eine etwaige
- große kurzfristige Belastung dies normalerweise nicht zulassen würden.
- Mit der von den Utilities zur Verfügung gestellten Prozedur PutPerDelta
- kann höchstens die Frequenz erreicht werden, mit der sich die Uhr
- verändert, aber dies könnte bei PC's (Uhränderung mit ca. 18 Hz) evtl.
- zu einem nachlaufen der Tastaturverarbeitung führen. Falls aber eine
- Taste gedrückt ist, so wird zuerst deren Code gelesen, in Großbuchstaben
- umgewandelt, und, falls eine Folge eingegeben werden soll, die Prozedur
- EingabeAuswerten aufgerufen. Falls nur der Rest noch zu drucken ist, so
- wird, falls ein Leerzeichen eingegeben wurde, Ende zu True gesetzt, um
- das Scheduling zu beenden. Falls immer noch eine Folge eingegeben werden
- soll, so wird die nächste Frequenz berechnet und ein entsprechendes
- Geräusch ausgegeben.
-
- Drucker Status und Drucken (DRUCKER.PAS)
-
- Die Aufgabe dieses Moduls besteht darin, den Druckerstatus darzustellen
- und Zeichen auf dem Drucker auszugeben. Dazu muß der Status natürlich
- abgefragt werden und ein Druckerbuffer zur Verfügung gestellt werden, in
- dem Zeichen gespeichert werden können, wenn deren Ausgabe nicht schnell
- genug erfolgt. Hier wird jedes Zeichen zuerst in den Buffer aufgenommen,
- und die Ausgabe erfolgt dann später.
-
- Zuerst werden einige Prozeduren (Druck...) zur Verfügung gestellt, die
- Zeichen in den Buffer (DruckBuffer) aufnehmen, solange darin noch Platz
- ist. Man kann diese Prozeduren relativ leicht ergänzen, so daß sie
- entweder als Parameter oder in einem Zähler, der erst am Ende
- ausgewertet wird, einen Overflow, also nicht mehr genügend Platz im
- DruckBuffer, melden. Die Schwierigkeit liegt darin, daß zumindest eine
- x-beliebige Anwenderprozedur meistens nicht weiß, was sie tun soll, wenn
- der Platz nicht ausreicht. Bei Bedarf kann mit nur einer Änderung
- (Konstante DruckBufLen in SPIEL.DCL) der Speicher fast beliebig (Grenzen
- sind vom Compiler vorgegeben oder aber Systemgrenzen, also Hardware)
- verändert werden.
-
- Die Hauptprozedur PPeriodic3 hat 2 lokale Prozeduren. Die erste,
- DruckStatus, fragt den Status des Druckers über einen Systemaufruf ab.
- Dieser Teil ist sehr von dem verwendeten System abhängig und muß bei
- anderen angepasst werden. Die zweite, DruckStatusAusgeben, gibt den
- Status auf dem Bildschirm aus. Hier wurden nicht alle Möglichkeiten
- ausgeschöpft, eine detailliertere Fehleranalyse kann mit einem der
- vielen Programme dafür (z. B. Druckerabfrage unter MS-DOS, PASCAL 4/88)
- erfolgen, aber als grobe Richtung, welcher Fehler vorliegt, genügt diese
- Prozedur. In der Hauptprozedur wird dann solange der Status abgefragt
- und, wenn dieser keinen Fehler anzeigt, ein Zeichen auf dem Drucker
- ausgegeben, bis entweder eine maximale Anzahl erreicht ist der Buffer
- leer oder der Druckerstatus nicht mehr Ok ist. Der mehrfache Aufruf der
- Statusabfrage könnte entfallen, da bei der Ausgabe eines Zeichens der
- Status mitgeliefert wird, aber so bleiben wir auf der sicheren Seite.
- Danach wird dann der Status ausgegeben, und die Aufgabe setzt sich
- selber wieder in die Liste, wobei aber die Zeit bis zum nächsten Aufruf
- davon abhängt, ob noch was im Buffer steht oder nicht. Wenn der Buffer
- leer ist und nur noch der Rest gedruckt werden sollte, dann wird Ende zu
- True, um das Scheduling zu beenden.
-
- Anwendungshinweise:
-
- Bei realen Systemen, die wichtige Meßwerte verarbeiten und hart an der
- Grenze ihrer Belastung liegen, wird versucht, so viel Aufgaben wie
- möglich durch Interrupts zu initiieren und dann die Verarbeitung mit
- hoher Priorität zu erledigen; aber eben nur bei Bedarf. Hier bietet sich
- zuerst wieder die Tastaturabfrage an, aber auch Meßwerte können über ein
- entsprechendes Interface Interrupts liefern.
-
- Der DruckBuffer, in dem Zeichen gespeichert werden, ist als Fifo
- ausgelegt, genau so wie der Fifo für Aufgaben. Die Deklaration sowie die
- Verwaltung von Fifos kann über allgemeine Prozeduren erfolgen, deren
- Parameter dann jeweils angepasst werden. Dies kann entweder über mehrere
- Include-Files geschehen (wird aber nicht von allen Versionen
- unterstützt) oder sogar Bestandteil der Sprache sein. Diese Methode wird
- als 'Generic' bezeichnet und setzt sich in letzter Zeit immer mehr
- durch.
-
- Ein sinnvoller Ausbau des vorgestellten Systems zur Echtzeitverarbeitung
- wäre es, den Modul zur Druckerausgabe auch in Debug zu verwenden,
- anstatt direkt auszugeben. Dann kann ein Protokoll auf Drucker wirklich
- in Echtzeit, also 'nebenher', erstellt werden.
-
- Dieses System oder ähnliche, die aber einfach zu verstehen sein sollten,
- eignet sich sehr gut für die Programmierung von jeder Art von
- Reaktionsspielen. Die damit realisierten Spiele können dann auf einem
- normalen XT genauso gespielt werden wie auf einem schnellen AT. Viele
- Spiele laufen auf einem schnelleren Rechner einfach zu schnell; mit
- Echtzeitsystemen wäre das nicht passiert. Es ist sehr mühsam, ein
- Programm, das nicht für Echtzeit vorgesehen war, später darauf zu
- trimmen, daß es auf allen Maschinen fast gleich schnell läuft.
-
-
- Literatur
-
- [1] Tom de Marco
- Structured Analysis and System Specification
- Jourdon Verlag