home *** CD-ROM | disk | FTP | other *** search
/ Turbo Toolbox / Turbo_Toolbox.iso / spezial / 03 / dokument / echtzeit.doc
Encoding:
Text File  |  1988-09-14  |  76.5 KB  |  1,372 lines

  1.  
  2. TOOLBOX-Spezial III
  3.  
  4.  
  5. Echtzeitprogrammierung in Pascal
  6.  
  7.                                    von Thomas Lang
  8.  
  9.  
  10. Einleitung
  11.  
  12. In PASCAL sind die Grundzüge und einige allgemeine Ausblicke auf die 
  13. Echtzeitprogrammierung schon vorgestellt worden (Ausgabe 2/1988).
  14. Um einen etwas tieferen Einblick in diese Materie zu vermitteln
  15. und die Aussage zu bekräftigen, daß ein Realtime System in jeder Pro-
  16. grammiersprache erstellt werden kann, soll jetzt ein System zur Echt-
  17. zeitprogrammierung in Pascal vorgestellt werden. Dieses System ist eine
  18. reine Software-Lösung, die unter dem normalen Betriebssystem als Anwen-
  19. derprogramm läuft. Es wurde ein kompatibler PC mit MS-DOS und Turbo
  20. Pascal verwendet, aber auf eventuelle Anpassungen wird oft hingewiesen,
  21. auch mit Tips, wie sie zu machen sind.
  22.  
  23. Die Zielrichtung bei der Entwicklung dieses Systems war nicht, ein aus-
  24. gefeiltes und möglichst 'trickreiches' System zu entwickeln, sondern
  25. eines, das auf fast jedem Computer mit sehr wenig Anpassungen unter
  26. Pascal realisiert werden kann. Außerdem habe ich versucht, sehr 'ein-
  27. fach' zu programmieren, in der Hoffnung, daß es dadurch vielen Lesern 
  28. leichter fällt, die einzelnen Teile nachzuvollziehen. Deshalb mögen alle
  29. Freaks, die jedes Bit am liebsten mehrfach benutzen, Nachsicht üben,
  30. wenn auf ihrer (und evtl. auch meiner) Maschine die eine oder andere 
  31. Realisierung schneller und mit weniger Speicherbedarf möglich ist; es
  32. steht jedem frei, alle Teile zu verändern, ich will sogar dazu ermuti-
  33. gen, damit zu experimentieren, die vorgestellte Version also nur als 
  34. Anleitung zu nehmen. 
  35.  
  36. Dieses System ist ferner nicht geeignet, sehr komplexe Echtzeitaufgaben 
  37. zu erfüllen, aber es kann für einfache Steuerungen oder ähnliches als 
  38. Basis genommen werden, in die dann die Anwendung (also die Steuerung) in 
  39. derselben Sprache (hier also Pascal) als Prozeduren eingefügt wird. Au-
  40. ßerdem gibt es natürlich sehr viel komplexere Systeme, die beispiels-
  41. weise 'nur bei Bedarf' Echtzeitaufgaben wahrnehmen, sonst aber von vie-
  42. len Benutzern im Timesharing gebraucht werden. Mit solchen kann und will
  43. dieses nicht konkurrieren, es sollen nur die Ansätze und deren Lösung am 
  44. konkreten Beispiel aufgezeigt werden, ohne jeden Anspruch auf Allein-
  45. gültigkeit (aber doch mit dem, daß es so möglich ist!). Bei Aufgaben, 
  46. die in einem eigenen System ohne Betriebssystem ablaufen sollen, kann 
  47. das vorgestellte ebenfalls als Basis dienen; beispielsweise auf Einpro-
  48. zessorplatinen zur Verarbeitung von Meßwerten.
  49.  
  50.  
  51. Allgemeine Anmerkungen
  52.  
  53.  
  54. Wie sieht nun ein solches System zur Echtzeitprogrammierung in Pascal 
  55. aus? Dazu wird ein sogenannter 'Scheduler' benötigt, wie er schon in dem 
  56. allgemeinen Artikel in PASCAL angedeutet wurde. Daneben werden einige
  57. Hilfsroutinen und ein Debugger, der speziell auf den Scheduler zuge-
  58. schnitten ist, vorgestellt.
  59.  
  60. Bevor die ersten Leser jetzt schon das Handtuch werfen und meinen, daß 
  61. dies alles auf ihrer Maschine XY nicht in allgemeiner Form zu verwirk-
  62. lichen ist, sei hier darauf hingewiesen, daß dem nicht so ist! Falls Ihr 
  63. Compiler (oder evtl. auch Interpreter) den von mir oft benutzten Typ 
  64. Byte beispielsweise nicht kennt, so ändern Sie diese einfach in Integer,
  65. und es müßte alles genauso, wenn auch vielleicht etwas langsamer, funk-
  66. tionieren. Eines sollte aber doch möglich sein, nämlich andere Quell-
  67. texte einzufügen, was in Turbo mit der Compileranweisung $I gemacht 
  68. wird. Falls das bei Ihnen nicht geht, so sollten Sie zumindest einen 
  69. sehr guten Editor haben, damit Sie alle Files wie vorgestellt verwenden
  70. können und die Einfügung mit dem Editor selbst machen, also 'von Hand'.
  71.  
  72. Damit bin ich schon bei der Erklärung, wie der Aufbau des Systems gene-
  73. rell aussieht. Turbo Pascal bot bis vor kurzem nicht die Möglichkeit der
  74. echten Modularisierung, das heißt, daß nur im Quelltext einzelne Module
  75. möglich waren, die aber bei jedem Compilerlauf wieder compiliert wurden.
  76. Da Pascal von Haus aus keine echte Modularisierung vorsieht, habe ich 
  77. ebenfalls darauf verzichtet und nur die schon erwähnte Einfügung von
  78. Quelltexten verwendet. Damit kann ein relativ einfacher 'Trick' eine 
  79. noch bessere Modularisierung vortäuschen: Man teilt jeden Modul in zwei
  80. auf, einer enthält die Deklarationen und alle Prozeduren als sogenannte 
  81. Forwards (d. h., daß die Prozedur bekannt gemacht wird, sie aber erst
  82. später wirklich im Quelltext vorkommt), der andere enthält die Prozedu-
  83. ren und Funktionen. Mit dieser Methode können auch alle Prozeduren in 
  84. dem Modul enthalten sein, zu dem sie logisch gehören; sie müssen nicht 
  85. unbedingt vor einer anderen Prozedur auftauchen, nur weil diese evtl. 
  86. davon Gebrauch macht.
  87.  
  88. Eine weitere, positive Eigenart von Turbo-Pascal (und den meisten, mir
  89. bekannten Compilern) wird verwendet: Man kann überall Typen, Konstanten
  90. und Variablen definieren. Falls Ihr Compiler dies nicht zuläßt, so soll-
  91. te entweder nur ein Deklarationsfile erstellt werden, in dem neue Defi-
  92. nitionen dann an der entsprechenden Stelle eingefügt werden, oder jedes
  93. Deklarationsfile in mehrere aufgeteilt werden, die nur Typen, nur Va-
  94. riablen, u.s.w. enthält. Das Einfügen dieser mehrfachen Files gestaltet
  95. sich dann nicht sehr schwierig, es müßen zuerst alle Konstantenfiles, 
  96. dann alle Typenfiles und zum Schluß alle Variablenfiles eingefügt wer-
  97. den.
  98.  
  99.  
  100. Scheduler - Einführung
  101.  
  102.  
  103. Doch nun werden die 'Wenn und Aber' zunächst beendet, wir wenden uns der 
  104. ersten Aufgabe zu, einen Scheduler zu erstellen. Was ist ein Scheduler? 
  105. Das ist der Teil, der die Aufgaben entsprechend ihrer Priorität oder 
  106. sonstiger, vorher festgelegter Eigenschaften, aufruft. Ein allgemeines 
  107. Konzept, wie sowas bewerkstelligt wird, ist schon vorgestellt worden, 
  108. hier noch einmal kurz zur Erinnerung:
  109.  
  110.    Aufgabe: Wie sieht prinzipiell eine Warteschlange mit 3 Prioritäten
  111.             und jeweils maximal X Aufgaben, die warten, aus?
  112.  
  113.    Lösung:  Priorität=(hoch,mittel,niedrig)
  114.             Schlange=ARRAY [1..X] of Aufgabe
  115.             Warteschlange=((hoch,Schlange),
  116.                            (mittel,Schlange),
  117.                            (niedrig,Schlange))
  118.  
  119.             Die Aufgabe wird jeweils von einer Prozedur durchgeführt.
  120.  
  121.             REPEAT
  122.                IF Warteschlange_hoch enthält Aufgabe THEN
  123.                   führe Aufgabe aus
  124.                   lösche diese Aufgabe aus der Schlange
  125.                ELSEIF Warteschlange_mittel enthält Aufgabe THEN
  126.                   führe Aufgabe aus
  127.                   lösche diese Aufgabe aus der Schlange
  128.                ELSEIF Warteschlange_niedrig enthält Aufgabe THEN
  129.                   führe Aufgabe aus
  130.                   lösche diese Aufgabe aus der Schlange
  131.             UNTIL Ende erreicht
  132.  
  133. So ein ähnliches Verfahren soll hier angewendet werden; die Vorausset-
  134. zungen zur Erfüllung der Aufgaben sind:
  135.  
  136.      Es sind 3 Prioritäten gegeben, wobei die höchste Priorität 5 ver-
  137.      schiedene Aufgaben enthalten kann, aber jede dieser Aufgaben nur 
  138.      einmal. Die mittlere Priorität sei ein sogenannter Fifo, also eine
  139.      echte Warteschlange, in der gilt, daß die Aufgaben in der Reihen-
  140.      folge bearbeitet werden, in der sie in die Warteschlange hineinge-
  141.      schoben worden sind (Fifo = first in first out); die Aufgaben, die 
  142.      damit aufgerufen werden, bekommen noch einen Parameter mit. Die
  143.      niedrigste Priorität sei von der aktuellen Zeit abhängig, d. h., 
  144.      daß Aufgaben erst abgearbeitet werden, wenn eine bestimmte Zeit
  145.      erreicht ist. 
  146.  
  147. Diese an sich sehr einfache Aufgabenstellung sieht in der (Programmier-) 
  148. Praxis aber schon gar nicht mehr so einfach aus. Die hohe Priorität ist 
  149. noch einfach, aber schon beim Fifo fangen die Schwierigkeiten an; es ist 
  150. zu beachten, daß die Anzahl der Aufgaben, die im Fifo gehalten werden 
  151. können, nicht gleich der verschiedenen Aufgaben sein muß, die vom Fifo
  152. aufgerufen werden können. Die Aufgabe der niedrigsten Priorität wird
  153. allgemein als Periodic bezeichnet, auch wenn im allgemeinen Sprachge-
  154. brauch unter periodisch ein sich in bestimmtem Zeitabstand wiederholen-
  155. des Ereignis gemeint ist, hier genügt hingegen schon ein einmaliger
  156. Aufruf (ich beschränke mich sogar auf einmalige, aber nur, um den
  157. Scheduler einfacher zu halten). Die nächste Schwierigkeit scheint die 
  158. aktuelle Zeit zu sein, aber MS-Dos bietet einen Systemaufruf, mit dem
  159. diese abgefragt werden kann, und, falls bei einer speziellen Maschine
  160. diese Abfrage nicht oder nur sehr umständlich zu realisieren ist, so
  161. kann man sich mit einem Zähler ganz gut helfen.
  162.  
  163. Die nächste, noch viel größere Schwierigkeit liegt darin, daß es in 
  164. Pascal normalerweise keinen Prozedurswitch oder Array von Prozeduren 
  165. gibt. In real existierenden Echtzeitsystemen kommen diese sehr häufig 
  166. vor, und evtl. Verzweigungen gehen über den Inhalt des Switches, der
  167. (fast schon natürlich) dynamisch verändert wird. Damit kann eine Aufgabe
  168. einfach dadurch in die Folge der folgenden Aufgaben eingetragen werden, 
  169. indem die Adresse der Prozedur, durch die sie bearbeitet wird, in den 
  170. Switch eingetragen wird. Dieses Verfahren ist bei Compiler XY natürlich
  171. auch möglich, aber schon bei YZ ganz anders zu realisieren. Deswegen 
  172. wird hier ein wenig eleganter Kunstgriff angewendet; alle Prozeduren, 
  173. die Aufgaben bearbeiten, müssen deklariert sein, wenn auch nur als Dummy 
  174. (das ist ein Platzhalter, der nichts tut). Auch hierbei wird wieder das 
  175. Verfahren angewendet, die notwendigen Prozeduren zuerst als Forwards zu 
  176. deklarieren (siehe Listing, AUFGABEN.DCL), und in einem extra File, das 
  177. alle Prozeduren als Dummys enthält (siehe Listing, SONSTIGE.PAS) erhal-
  178. ten sie ihren Inhalt (Verarbeitungsteil, also nichts). Wenn in einer 
  179. konkreten Anwendung nun einige dieser Prozeduren verwendet werden, so 
  180. können sie darin entweder gelöscht werden, oder noch einfacher, als 
  181. Kommentar gekennzeichnet werden.
  182.  
  183.  
  184. Scheduler - Details
  185.  
  186.  
  187. Nach diesen etwas allgemeineren Überlegungen zum Scheduler und dabei zu 
  188. erwartenden Problemchen folgt nun eine Erklärung des realisierten
  189. Schedulers. Die notwendigen Deklarationen (siehe Listing, SCHEDUL.DCL) 
  190. sind wieder getrennt vom Verarbeitungsteil (siehe Listing, SCHEDUL.PAS).
  191.  
  192. Initialisierung:
  193.  
  194. Die erste Aufgabe ist es, den Scheduler zu initialisieren, d. h., daß 
  195. eine Variable, die angibt, ob der Scheduler aufhören soll, zu falsch 
  196. gesetzt wird. Die nächste ist, alle Aufgaben, die evtl. in den Arrays 
  197. für anstehende Aufgaben enthalten sind, zu löschen. Beim Fifo geschieht
  198. das nicht durch explizites Löschen sondern dadurch, daß die Zeiger, mit
  199. denen der Fifo als Ringbuffer verwaltet wird, initialisiert werden. Danach
  200. folgt, alle Zähler für evtl. auftretenden Overflow auf 0 zu setzen und 
  201. den Belastunngszähler ebenfalls. Die Behandlung von Overflow wird bei 
  202. den Utilities beschrieben, die des Belastungs-zählers (vor allem seine 
  203. Auswertung) beim Debugger. 
  204.  
  205. Als Belastungszähler wurde hier eine künstliche 'große Integer' gewählt, 
  206. da bei einer einfachen Integer sehr schnell ein Overflow auftritt, und 
  207. eine Real-Zahl in der Verarbeitung langsamer ist, wie folgendes zeigt:
  208.  
  209. Die folgende Liste wurde von einer Zeitmessung mit der Funktion TimeNow 
  210. (wird bei den Utilities besprochen) erstellt, wobei untersucht wurde, ob 
  211. das Hochzählen eines Doppelwortes mit der Prozedur PIncrDW schneller als
  212. das Hochzählen einer RealZahl ist:
  213.  
  214. Zeitmessung für Doppelwort um 1 erhöhen für 100 000 mal
  215.  
  216. BeginZeit:7753936.0000
  217. EndeZeit:7754924.0000
  218. Differenz:    988.0000
  219.  
  220.  
  221. Zeitmessung für RealZahl um 1 erhöhen für 100 000 mal
  222.  
  223. BeginZeit:7754935.0000
  224. EndeZeit:7757220.0000
  225. Differenz:   2285.0000
  226.  
  227. Das Ergebnis zeigt deutlich, daß dies tatsächlich der Fall ist! Somit 
  228. ist es günstiger, für Belastungsmessungen das gewählte Verfahren beizu-
  229. behalten, als auf Real-Zahlen umzusteigen. Ich glaube, daß bei jedem 
  230. Pascal Compiler Reals länger brauchen, als die gewählte 'künstliche' 
  231. Erweiterung des Integerbereichs, wobei aber sogenannte LongInteger vor-
  232. zuziehen sind, falls der Compiler diese bietet.
  233.  
  234. Verzweigung:
  235.  
  236. Die Verzweigung des Schedulers zu einer anstehenden Aufgabe wird für 
  237. jede Priorität mit einer eigenen Funktion durchgeführt, die als Resultat 
  238. angibt, ob verzweigt wurde oder nicht. Dabei ist bei jeder Priorität 
  239. darauf zu achten, daß die zu bearbeitende Aufgabe aus der Liste der 
  240. Aufgaben gelöscht wird, bevor sie wirklich aufgerufen wird, da sonst
  241. diese Aufgabe als 'Leiche' zumindest eine Zeit lang mitgeführt wird,
  242. obwohl es gar nicht nötig ist.
  243.  
  244.      -    Hohe Priorität
  245.  
  246.           Diese Verzweigung sieht auf den ersten Blick sehr eigenwillig 
  247.           aus, denn es wird zuerst nachgeprüft, ob überhaupt eine Auf-
  248.           gabe mit hoher Priorität zu erledigen ist, und dann wird, 
  249.           wieder mit einer seltsamen Konstruktion, zu dieser Aufgabe
  250.           verzweigt. Tatsächlich könnte dies sehr viel einfacher durch
  251.           ein CASE oder nur die verschachtelten IF's gemacht werden, 
  252.           aber dann müßte jedesmal die Prozedur PLogHoch (Beschreibung 
  253.           folgt beim Debugger) mit angegeben werden, und diese ver-
  254.           schachtelten IF's sind nach meiner Meinung ebenso deutlich wie 
  255.           ein CASE; zumindest werden hier die folgenden Indizes nicht 
  256.           mehr überprüft oder angesprungen, falls schon einer gefunden 
  257.           worden ist, was bei CASE evtl. möglich wäre (zumindest der 
  258.           Sprung dorthin). Ein weitere Grund für diese Realisierung ist, 
  259.           daß ein ähnliches Verfahren in exisitierenden Systemen sehr
  260.           oft angewendet wird, statt eines Array of Boolean wird aber 
  261.           eine (oder mehrere) normale Zahlen oder Bitfelder benutzt, die 
  262.           dann mit einem oder zumindest sehr wenigen Befehlen abgefragt
  263.           werden können, ob sie überhaupt etwas enthalten; und erst dann
  264.           wird die eigentliche Aufgabe herausgesucht und aufgerufen.
  265.           Dies ist in vielen Pascalversionen auch möglich, aber leider 
  266.           fast überall ein wenig anders, weshalb hier absichtlich eine 
  267.           umständlicher, aber überall möglicher Weg gewählt wurde.
  268.  
  269.      -    Fifo
  270.  
  271.           Die Verzweigung innerhalb des Fifos ist am schwierigsten zu 
  272.           verstehen, weil der Fifo als Ringbuffer aufgebaut ist, d. h.,
  273.           daß bei einem Überlauf wieder von vorne angefangen wird. In
  274.           der Literatur tauchen diese Warteschlangen öfters auf, und es 
  275.           gibt auch ausgefeilte Verfahren zu ihrer Verwaltung, die nur
  276.           mit zwei Zeigern auskommen, einer der angibt, an welche Stelle
  277.           das nächste Element reingeschrieben wird, und einer, der an-
  278.           gibt, an welcher Stelle das nächste Element herausgeholt wird. 
  279.           Hier ist noch zusätzlich eine Variable mit benutzt, die an-
  280.           gibt, ob der Fifo leer ist oder nicht. Dies ist deswegen nö-
  281.           tig, da in dieser Version beide Zeiger (für Input und Output) 
  282.           nur im Bereich der Elemente des Fifos sind, es muß also eine 
  283.           zusätzliche Angabe gemacht werden, falls beide gleich sind. 
  284.           Damit kann der Fifo komplett ausgenutzt werden, und man kommt 
  285.           mit addieren oder subtrahieren und seltenen Vergleichen auf 
  286.           Maximalgröße aus. Die eigentliche Verzweigung findet dann nach 
  287.           einem recht eigentümlichen Verfahren statt; alle Informatiker 
  288.           (oder mit etwas theoretischer Informatikausbildung behaftete 
  289.           Personen) kennen dieses Verfahren, das durch laufende Halbie-
  290.           rung des verbeibenden Bereiches besteht. So etwas wird im 
  291.           Prinzip bei Baumstrukturen angewandt, genauso wie beim inzwi-
  292.           schen wohl bekannten Quicksort. Hier wird es deswegen benutzt, 
  293.           weil es im Durchschnitt sehr schnell ist, egal welche Aufgabe
  294.           aufgerufen werden soll; bei anderen Verfahren sind die Abfra-
  295.           gen, die zuletzt drankommen, relativ langsam, je nach Compiler
  296.           schätzungsweise um den Faktor 2 bis 4 langsamer. Der Nachteil 
  297.           bei diesem Verfahren ist aber, daß bei einer anderen Anzahl 
  298.           verschiedener Aufgaben, die durch den Fifo bearbeitet werden
  299.           können, der Quelltext verändert werden muß.
  300.  
  301.      -    Periodic
  302.  
  303.           Die Verzweigung innerhalb der periodischen Aufgaben erfolgt 
  304.           nach einem relativ einfachen Grundprinzip; es wird die klein-
  305.           ste Zeit aller Aufgaben, die erledigt werden sollen, heraus-
  306.           gesucht. Damit nicht immer von der gleichen Position an ge-
  307.           sucht wird, was natürlich auch denkbar wäre, wird der Start-
  308.           index, ab dem gesucht wird, immer um 1 erhöht, bzw. auf den 
  309.           Anfang gesetzt, falls das Ende erreicht war. Mit diesem Ver-
  310.           fahren kommen alle Aufgaben, die nach einem gewissen Zeitpunkt 
  311.           zu erledigen sind, immer mal als erste in der Abfrage dran, 
  312.           womit unter anderem verhindert wird, daß immer nur die gleiche 
  313.           Aufgabe vor einer anderen kommt, falls sie zur selben Zeit 
  314.           bearbeitet werden sollen. Die eigentliche Verzweigung ge-
  315.           schieht wieder mit verschachtelten IF's wie bei den Aufgaben 
  316.           hoher Priorität, aber natürlich nur, falls die aktuelle Zeit
  317.           kleiner oder gleich der herausgefundenen kleinsten Zeit ist,
  318.           zu der eine Aufgabe erledigt werden soll. 
  319.  
  320. Zusammenfassend ist festzustellen, daß die einzelnen Verzweigungen doch 
  321. noch recht einfach zu verstehen sind (hoffentlich), aber in wirklich 
  322. existierenden Echtzeitsystemen wird dieser Teil meistens sehr optimiert 
  323. und auf die jeweilige Zielmaschine angepasst, da dieser Teil derjenige 
  324. ist, der am häufigsten durchlaufen wird, und die hierfür benötigte Zeit
  325. den eigentlichen Aufgaben, die zu erledigen sind, nicht zur Verfügung 
  326. steht. Auf der anderen Seite ist aber zu bedenken, daß ein so einfaches 
  327. System zumindest als Basis, an der dann optimiert werden kann, auch 
  328. seine Vorteile hat, denn allzu komplexe Verfahren zur Aufgabensteuerung
  329. sind leider nur sehr schwer zu verstehen und sie tendieren mit zuneh-
  330. mendem Komfort danach, mit ihrer eigenen Verwaltung so beschäftigt zu 
  331. sein, daß die eigentlichen Aufgaben nur am Rande manchmal mit ausgeführt
  332. werden.
  333.  
  334. Scheduling:
  335.  
  336. Nach diesen Vorbereitungen ist der eigentliche Scheduler sehr einfach. 
  337. Zu Beginn wird die aktuelle Zeit gerettet, da sie für die Belastung
  338. (oder vielleicht besser Auslastung) des Systems mit den eigentlichen
  339. Aufgaben benötigt wird; dies wird später bei Debug mit beschrieben. Da-
  340. nach geht eine Schleife solange, bis Ende erreicht wird, in der die 
  341. Verzweigung der einzelnen Prioritäten mit den vorher angegebenen Funk-
  342. tionen erfolgt, und die Verzweigungen der nächst niederen Priorität 
  343. werden nur ausgeführt, falls noch keine Aufgabe in einer höheren auszu-
  344. führen war. Falls gar keine Aufgabe gefunden worden ist, wird zuletzt 
  345. der Belastungszähler um 1 erhöht. Nachdem Ende erreicht ist, wird die
  346. aktuelle Zeit gerettet und mit PLogSchedul eine Statistik und Be-
  347. lastungsauswertung erstellt.
  348.  
  349. Anwendungshinweise:
  350.  
  351. Der Scheduler in der vorgestellten Form ist leider nicht sofort anwend-
  352. bar, da alle Log-Prozeduren erst im Debugger vorgestellt werden und aus 
  353. den Utilities die Funktion TimeNow und PIncrDW benötigt werden. Falls 
  354. alle diese Aufrufe vorläufig entfernt werden, dann kann er aber schon 
  355. notdürftig angewendet werden; nur die Zeit sollte noch nicht benutzt
  356. werden (also Periodic weglassen). Anstatt der Zeitabfrage mit TimeNow 
  357. kann zur Not auch eine Abfrage auf einen Zähler erfolgen, das bedeutet, 
  358. daß dieser Zähler einen bestimmten Wert erreicht haben muß, bevor die
  359. Aufgabe aufgerufen wird. In Systemen, in denen eine Uhr nicht oder nur 
  360. sehr umständlich abzufragen ist, sollte dies zumindest als Einführung in 
  361. Echtzeitprogrammierung genügen. Der Zähler muß aber ein Doppelwort oder 
  362. eine Real-Zahl sein, damit nicht sehr schnell ein Overflow auftritt. Um 
  363. gewisse Zeitrelationen doch noch einigermaßen nachbilden zu können,
  364. sollte der Zähler am Beginn der Abfragen, ob eine Aufgabe zu tun ist, 
  365. erhöht werden, und dann entweder bei jeder Aufgabe nochmal mit einem 
  366. geschätzten Wert, oder pauschal für alle Aufgaben mit demselben Wert
  367. nochmals. Damit überhaupt eine Aufgabe erledigt werden kann, muß zuerst 
  368. die Prozedur InitSchedul aufgerufen werden, dann muß (mindestens) eine 
  369. Aufgabe in die Liste der zu bearbeitenden eingetragen werden, und dann 
  370. kann Schedul aufgerufen werden. Wie Aufgaben eingetragen werden ist un-
  371. ter anderem Aufgabe der Utilities, die ebenso wie der Debugger noch 
  372. vorgestellt werden.
  373.  
  374.  
  375. Debugger - Einführung
  376.  
  377.  
  378. In diesem Teil wird ein Debugger für das verwendete Echtzeitsystem vor-
  379. gestellt und zuerst erklärt, wozu er nötig ist. Dieser Debugger ist auf 
  380. gar keinen Fall zu vernachlässigen, geschweige denn durch einen 'ganz 
  381. normalen', der auf Pascal zugeschnitten ist, zu ersetzen, da er ganz 
  382. andere Aufgaben hat! In diesem Zusammenhang ist es noch nützlich, die 
  383. unterschiedlichen Begriffe, die verwendet werden, kurz zu erläutern; 
  384. 'Logging' bedeutet in diesem Zusammenhang aufzeichnen oder protokollie-
  385. ren, 'Debugging' bedeutet Fehlersuche, die in Echtzeitsystemen aber fast 
  386. nur an Hand von Protokollen erfolgen kann.
  387.  
  388. Wenn dieser Debugger, oder vielleicht besser ausgedrückt Logger (aber 
  389. wer kann sich darunter schon was vorstellen, ohne Assoziationen mit He-
  390. ringen? ), allein vom Umfang mit den anderen Grundteilen des Echtzeit-
  391. systems Scheduler und Utility verglichen wird, so zeigt sich schon da-
  392. ran, daß er sehr wichtig ist. Es ist nochmals extra zu betonen, daß das
  393. schönste und am besten durchdachte Echtzeitsystem ohne einen darauf zu-
  394. geschnittenen Debugger fast wertlos ist, da in diesen Systemen ohne 
  395. Protokollierung und etwaiger dynamischer Änderung von Variablen 'von
  396. außen' (die aber hier nicht realisiert ist) keine Fehlersuche möglich 
  397. ist.
  398.  
  399. Die Aufgabe dieses Debuggers besteht hauptsächlich darin, den Zustand 
  400. der Aufgabenlisten bzw. das Einschreiben oder Ausführen von solchen 
  401. Echt-zeitaufgaben zu protokollieren. Dies ist gänzlich anders, als in 
  402. 'fast Pascal' einzelne Schritte zu verfolgen. Das verfolgen oder 
  403. nachvollziehen der Echtzeitaufgaben ist deshalb besonders wichtig, weil
  404. die meisten Fehler darin bestehen, daß eine oder mehrere Aufgaben nicht 
  405. oder in der falschen Reihenfolge (oder so ähnlich) in die Liste 
  406. eingetragen oder ausgeführt wird. Ein sehr schwieriges Problem liegt 
  407. auch in sogenannten 'Deadlocks', das sind Zustände, in denen eine
  408. Aufgabe auf eine Nachricht von einer anderen wartet, diese aber 
  409. ebenfalls wartet, im schlimmsten Fall auf eine Nachricht der Aufgabe, an 
  410. die eine Nachricht geschickt werden soll. Ein 'fast Deadlock' tritt dann 
  411. auf, wenn ein externes Gerät nicht reagiert, und dies nicht oder mit zu
  412. großer Wartezeit abgefangen wird. Diese Zustände können fast nur an 
  413. Protokollen von ausgeführten Echtzeitaufgaben nachvollzogen werden, aber
  414. leider sind die beschriebenen (und auch noch andere) Fehler gar nicht so
  415. selten, ja man kann sagen, daß ein etwas größeres System solche Fehler 
  416. am Anfang immer hat.
  417.  
  418. In der aktuell vorliegenden Version werden die Zustände oder Aufrufe, 
  419. die aufgezeichnet werden, mit den normalen Write-Prozeduren von Pascal 
  420. erledigt, aber es wäre generell möglich, dafür eigene Aufgaben in Echt-
  421. zeit zu schreiben, beispielsweise Aufgaben des Fifos zu verwenden.
  422.  
  423. Debugger - Details
  424.  
  425. Der Debugger ist nach dem schon im ersten Teil angegeben Schema aufge-
  426. baut; also erst die Deklarationen (siehe Listing, DEBUG.DCL) in einem 
  427. extra File und dann genauso die Verarbeitung (siehe Listing, DEBUG.PAS).
  428.  
  429. Deklarationen:
  430.  
  431. Die Deklarationen sind sehr einfach, sie enthalten zuerst Zustandsvari-
  432. able (sogenannte Flags), die angeben, ob Logging auf Bildschirm, Drucker
  433. oder eine Datei erfolgen soll oder nicht. Danach wird ein etwaiger 
  434. Dateiname deklariert und das File, in das evtl. aufgezeichnet wird, als
  435. Text (die eine Standard Deklaration von Pascal ist).
  436.  
  437. Für die einzelnen Echtzeitaufgaben werden Arrays mit Flags deklariert, 
  438. die angeben, ob bei den entsprechenden Put... -Prozeduren (Aufgabe in 
  439. Liste eintragen) bzw. den entsprechenden Aufrufen die jeweilige Aufgabe 
  440. geloggt werden soll. Dabei ist zu beachten, daß für den Fifo die Anzahl 
  441. der verschiedenen Aufgaben, die er aufrufen kann, wichtig ist, und nicht 
  442. die Anzahl der Aufgaben, die er speichern kann. Diese Flags könnten 
  443. theoretisch in der Deklaration der Aufgabenliste im Scheduler mit auf-
  444. genommen werden, aber sie gehören eindeutig mit zum Debugger und werden 
  445. deswegen auch getrennt deklariert.
  446.  
  447. Verarbeitung:
  448.  
  449. Die Prozedur InitDebug initialisiert den Debugger, sie muß vor der 
  450. ersten Verarbeitung in Echtzeit (also vor der Prozedur SCHEDUL) aufge-
  451. rufen werden. Mit der Variablen Frage wird angegeben, ob die Initiali-
  452. sierungsdaten interaktiv abgefragt werden sollen, oder ob sie aus den 
  453. weiteren Parametern übernommen werden sollen. Dieses Verfahren hat den
  454. Vorteil, daß während der Entwicklung oder auch in der Testphase abge-
  455. fragt werden kann, danach ist nur dieser Parameter auf FALSE zu setzen, 
  456. und die Abfrage wird nicht ausgeführt.
  457.  
  458. Bei Abfragen werden zuerst die Flags abgefragt, ob Logging auf Bild-
  459. schirm, auf Drucker oder in eine Datei gehen soll. Wenn in eine Datei 
  460. aufgezeichnet werden soll, wird der Name dieser Datei abgefragt, es ist 
  461. also möglich, mehrere Tests jeweils mit Protokollen in eine Datei aus-
  462. zuführen, und diese dann später auszuwerten; bei jeweils dem gleichen 
  463. Da-teinamen würde die zuerst erstellte jedesmal wieder überschrieben.
  464.  
  465. Danach wird abgefragt, ob die Voreinstellung 'alle Aufgaben aufzeichnen' 
  466. oder 'keine Aufgaben aufzeichnen' sein soll. Diese Voreinstellung wird 
  467. dann in die FlagListen eingetragen. Danach wird für hohe Priorität, Fifo 
  468. und Periodic jeweils abgefragt, für welche Aufgaben diese Voreinstellung 
  469. geändert werden soll; das bedeutet bei Voreinstellung 'alle aufzeich-
  470. nen', welche nicht aufgezeichnet werden, bei Voreinstellung 'keine auf-
  471. zeichnen' hingegen welche doch aufgezeichnet werden. Damit ist es bei-
  472. spielsweise möglich, mit relativ wenig Eingaben alle Aufgaben aufzu-
  473. zeichnen oder nur eine einzige.
  474.  
  475. Wenn nicht abgefragt wird, so werden die Daten für Logging auf Bild-
  476. schirm, Drucker oder in Datei übernommen. Dann wird auch vorausgesetzt, 
  477. daß das aufrufende Programm angibt, welche Aufgaben geloggt werden sol-
  478. len oder nicht. Dieses Verfahren ist sicher nicht sehr elegant, man 
  479. könnte alles als Parameter übergeben, aber dann wird dieser Teil, der 
  480. die übergebenen Parameter auswertet und übernimmt, ziemlich unüber-
  481. sichtlich.
  482.  
  483. Falls in eine Datei aufgezeichnet werden soll, so wird diese dann der 
  484. Textdatei zugewiesen (evtl. Anpassung bei anderen Systemen nötig!) und 
  485. geöffnet. Hierbei wird keine Überprüfung vorgenommen, ob der Dateiname 
  486. gültig ist, ob das File schon exisiert, also keinerlei Fehler werden
  487. abgefangen.
  488.  
  489. Die Prozedur PLogLn gibt ein WriteLn ohne jegliche Parameter auf Bild-
  490. schirm, Drucker (=Lst) oder in Datei (=LogFile) aus, je nachdem, ob das 
  491. entsprechende Flag gesetzt ist oder nicht. Dieser Teil könnte sicher 
  492. wieder um einiges eleganter gelöst werden, aber in dieser Version ist 
  493. keine oder nur eine geringe Anpassung an andere System nötig, was bei 
  494. einer trickreichen Lösung nicht der Fall wäre.
  495.  
  496. Die Prozedur PLogText gibt einen Text aus, analog zu PLogLn.
  497.  
  498. Die Prozedur PLogInteger gibt eine Integer aus, analog zu PLogLn.
  499.  
  500. Die Prozedur PLogReal gibt eine Real-Zahl aus, analog zu PLogLn. Hier 
  501. wird eine Formatierung mit den vom Standard gegebenen Möglichkeiten 
  502. vorgenommen, insgesamt 7 Stellen Ausgabe, mit keiner Stelle hinter dem 
  503. Komma. Dies ergibt eine erweiterte Integer, aber da diese Prozedur fast
  504. nur für die Zeit verwendet wird, genügt es vollkommen. Falls keine 
  505. Formatierung gewählt wird, so werden die Protokolle für Real-Zahlen sehr 
  506. schwer lesbar, bei Bedarf kann aber jederzeit eine andere Formatierung 
  507. gewählt werden oder noch mehrere zusätzlich, am einfachsten mit extra 
  508. Prozeduren.
  509.  
  510. Die Prozedur PLogHoch loggt den Aufruf einer Aufgabe mit hoher Priori-
  511. tät, deren Nummer als Parameter übergeben wird.
  512.  
  513. Die Prozedur PLogFifo loggt den Aufruf einer Aufgabe des Fifos, deren 
  514. Nummer und Aufrufparameter übergeben werden.
  515.  
  516. Die Prozedur PLogPeriodic loggt den Aufruf einer periodischen Aufgabe, 
  517. deren Nummer als Parameter übergeben wird. Zusätzlich wird die Zeit, zu 
  518. der sie aufgerufen werden sollte, und die aktuelle Zeit ausgegeben.
  519.  
  520. Die Prozedur PLogSchedul gibt eine Art Statistik über den gesamten Zeit-
  521. raum der Echtzeitverarbeitung aus. Dabei wird die BeginnZeit, die Ende-
  522. Zeit und die daraus berechnete Dauer ausgegeben. Die Anzahl der Durch-
  523. läufe des Schedulers ist der Wert des Belastungszählers, dessen Ver-
  524. hältnis zu seinem maximal erreichbaren Wert, der natürlich von der Dauer 
  525. und dem verwendeten System abhängig ist (Systemkonstante NichtBelastung 
  526. wird experimentell ermittelt, siehe Listing TEST02.PAS, PERIOD1.PAS und 
  527. Log-File TEST_02.LOG). Diese relative Belastung wird in Promill ausgege-
  528. ben, wobei 0 keine Belastung bedeutet und 1000 der maximale Wert ist. 
  529. Falls ein negativer Wert (so etwa -3 Promill) erreicht wird, so ist das 
  530. wie 0 anzusehen, also nicht sklavisch auf die letzte Stelle in den Pro-
  531. millen schauen, da erstens Rundungsfehler nicht abgefangen werden, und
  532. zweitens ist die Aussage über die Belastung auch nur relativ zu werten.
  533. Es gibt keine allgemein gültigen Grundsätze, die angeben, daß eine Be-
  534. lastung von XY noch tragbar ist, eine darüber aber nicht mehr. Die Er-
  535. fahrung zeigt, daß so gegen 500 Promille im allgemeinen keine Über-
  536. lastung darstellt, bei Werten größer als 750 wird es aber öfters kri-
  537. tisch. Meistens ist eine subjektive Beurteilung, ob die Reaktionszeiten
  538. noch ausreichen oder nicht, genauso aussagekräftig wie die Angabe der 
  539. Belastung. Das Problem, an welcher Stelle eine Optimierung dann sinnvoll 
  540. ist, wird in den Anwen-dungshinweisen kurz angesprochen. Danach wird noch 
  541. angegeben, wieviel OverFlows bei Aufgaben hoher Priorität, beim Fifo und
  542. bei periodischen Aufgaben aufgetreten sind. Diese Angaben sind bei 
  543. weitem wichtiger, da eigentlich gar kein Überlauf (d. h., daß eine Auf-
  544. gabe in die Liste eingetragen werden sollte, aber kein Platz vorhanden
  545. war) auftreten sollte. Bei Fifo ist dieses Problem noch relativ leicht 
  546. zu lösen, indem einfach die Größe des Fifos heraufgesetzt wird ( 
  547. AnzahlFifo) , aber bei periodischen Aufgaben oder denen mit hoher Prio-
  548. rität wird es schwieriger; hier könnte evtl. auch der Buffer (mit 
  549. AnzahlHoch bzw. AnzahlPeriodic) größer gemacht werden, aber dann muß 
  550. zusätzlich noch eine Aufteilung der Aufgaben vorgenommen werden, bei-
  551. spielsweise wird Periodic1 aufgeteilt in 3 Komponten, Periodic1 und 
  552. Periodic7 und Periodic8, die dann aber auch nur Teilaufgaben der bishe-
  553. rigen Periodic1 bearbeiten.
  554.  
  555. Die Prozedur PLogPutHoch loggt die Tatsache, daß eine Aufgabe mit hoher 
  556. Priorität in die Aufgabenliste eingetragen werden soll und ihre Nummer. 
  557. Falls dies nicht möglich ist, wird ein Hinweis auf OverFlow mit ausge-
  558. geben.
  559.  
  560. Die Prozedur PLogPutFifo loggt die Tatsache, daß eine Aufgabe des Fifos 
  561. in die Aufgabenliste eingetragen werden soll und ihre Nummer und den 
  562. Parameter. Falls dies nicht möglich ist, wird ein Hinweis auf OverFlow 
  563. mit ausgegeben.
  564.  
  565. Die Prozedur PLogPutPerAbs loggt die Tatsache, daß eine periodische Auf-
  566. gabe mit absoluter Zeit in die Aufgabenliste eingetragen werden soll und 
  567. ihre Nummer und absolute Zeit. Falls dies nicht möglich ist, wird ein 
  568. Hinweis auf OverFlow mit ausgegeben.
  569.  
  570. Die Prozedur PLogPutPerDelta loggt die Tatsache, daß eine periodische 
  571. Aufgabe mit DeltaZeit in die Aufgabenliste eingetragen werden soll und 
  572. ihre Nummer, die DeltaZeit und die berechnete absolute Zeit. Falls dies 
  573. nicht möglich ist, wird ein Hinweis auf OverFlow mit ausgegeben.
  574.  
  575. Die Prozedur PLogInhaltHoch loggt den angegebenen Text und den Inhalt 
  576. der Aufgabenliste mit hoher Priorität, wobei nur T (für Aufgabe steht 
  577. an) oder F (für Aufgabe steht nicht an) ausgegeben wird.
  578.  
  579. Die Prozedur PLogInhaltFifo loggt den angegebenen Text, die Zeiger für 
  580. die Fifoverwaltung (FifoIndexIn, FifoIndexOut) und ob der Fifo als leer 
  581. gekennzeichnet ist oder nicht, und den Inhalt, bestehend aus IndexNum-
  582. mer, eingetragener Aufgabennummer (gibt an, welche FifoAufgabe darin 
  583. steht) und eingetragenem Parameter. Bei Aufruf dieser Prozedur ist deut-
  584. lich zu sehen, daß die alten, schon abgearbeiteten Aufgaben nicht ge-
  585. löscht werden, sondern nur durch ändern der Zeiger dieser Platz als frei 
  586. gekennzeichnet ist. Die verwendete Turbo-Prozedur Str(I,T) weist dem 
  587. String T den Inhalt der Integer I als Text, also Character, zu. Dies 
  588. kann entfallen oder durch ähnliche Prozeduren ersetzt werden, es ergibt 
  589. nur ein besseres Layout.
  590.  
  591. Die Prozedur PLogInhaltPer loggt den angegebenen Text, den Zeiger für 
  592. die Verwaltung der periodischen Aufgabenliste (PeriodicIndexOut) und die 
  593. aktuelle Zeit. Danach wird der Inhalt dieser Aufgabenliste geloggt, be-
  594. stehend aus Index, ob die Aufgabe ansteht (wieder nur T oder F) und, 
  595. falls die Aufgabe ansteht, der Sollzeit, zu der die Aufgabe zu 
  596. bearbeiten ist.
  597.  
  598. Anwendungshinweise:
  599.  
  600. Wie schon angesprochen, können die Prozeduren, die direkt aufzeichnen 
  601. (PLog... mit Ln, Text, Integer und Real) in späteren Versionen dahinge-
  602. hend abgeändert werden, daß das aufzeichnen auch mit Echtzeitaufgaben 
  603. vorgenommen wird. In erster Näherung genügen die angegebenen Prozeduren 
  604. den Anforderungen, auch wenn sie nicht in Echtzeit verarbeitet werden 
  605. und keine Fehler abfangen. Bei Logging auf Drucker sollte dieser einen 
  606. genügend großen internen Buffer haben und es sollte auch nicht allzu 
  607. viel geloggt werden. Bei Dateien ist ein Schwachpunkt, daß keine oder 
  608. nur sehr schwer Aussagen darüber zu machen sind, wie lange die Positio-
  609. nierung des Schreibkopfes dauert und wann sie stattfindet. Wenn eine 
  610. Festplatte zur Verfügung steht, sollten wenig Probleme auftreten, bei 
  611. Diskettenlaufwerken wird es schon kritischer. Dann ist darauf zu achten, 
  612. daß das Laufwerk nicht 'steht', also lieber etwas mehr aufzeichnen, 
  613. falls es wirklich einmal stehenbleiben sollte, eine elegantere und 
  614. schnellere Lösung ist aber die Einrichtung eines virtuellen Laufwerks 
  615. (auch RAMDISK genannt), wozu es auch schon Programme in Public Domain 
  616. gibt.
  617.  
  618. Wie kann eine Anwendung optimiert werden, falls die Angabe der Belastung 
  619. sehr hoch ist, oder zumindest der subjektive Eindruck entsteht, daß 
  620. manchmal die Reaktionszeiten zu groß werden? Dieses nicht allgemein 
  621. lösbare Problem kann durch diesen Debugger soweit eingegrenzt werden, 
  622. daß festgestellt werden kann, welche Aufgabe sehr lange braucht, bis sie 
  623. ausgeführt wird, und welche Aufgaben dazwischen an der Reihe waren, 
  624. ausgeführt zu werden. Wenn das immer die gleichen sind, so ist zu über-
  625. prüfen, ob diese vielleicht zurückgestellt werden können (etwa durch 
  626. niedrigere Priorität) oder die kritische vorgezogen (durch höhere Priori
  627. tät oder bei hoher Priorität mit einem kleineren Index, der ja zuerst 
  628. bearbeitet wird). Falls dies nichts hilft, so kann entweder im Scheduler
  629. (aber Vorsicht bitte!) oder in Aufgaben, die oft drankommen, eine Opti-
  630. mierung in Bezug auf die Laufzeit erfolgen, die im Extremfall darin be-
  631. stehen kann, daß einzelne Routinen in Assembler umgeschrieben werden. 
  632. Falls dies alles nichts hilft, so bleibt als letzter Ausweg (vor dem 
  633. aufgeben) die Verwendung eines schnelleren, aber ansonsten kompatiblen, 
  634. Systems oder ein teilweises Neudesign, also neue Zuordnung der Aufgaben, 
  635. evtl. unter Verzicht auf einige, die nur der Kosmetik dienen, aber nicht 
  636. unbedingt erforderlich sind.
  637.  
  638. Die Prozeduren PlogInhaltHoch, PLogInhaltFifo und PLogInhaltPer werden 
  639. im aktuellen System nicht mehr verwendet, sie wurden aber während der 
  640. Testphase oft gebraucht. Sie können an jeder Stelle aufgerufen werden, 
  641. am sinnvollsten aber im Scheduler selbst vor und nach den Verzweigungs-
  642. routinen oder in ihnen am Beginn und am Ende. Damit kann auch sehr ein-
  643. fach nachvollzogen werden, welche Aufgabe zur Verarbeitung drankommt und 
  644. wie die Verwaltung und Aufrufe der Aufgaben vonstatten geht. Speziell 
  645. beim Fifo ist es zu empfehlen, damit einige Kontrollaufzeichnungen zu 
  646. machen, mit denen die Verwaltung von Hand nachträglich kontrolliert 
  647. werden kann. Die anderen Prozeduren können jederzeit an kritischen 
  648. Stellen in eigenen Programmen bei der Entwicklung mit eingebunden wer-
  649. den, um beispielsweise erklärende Texte und Variable in dasselbe LogFile 
  650. zu be-kommen, in dem auch die Aufrufe aufgezeichnet sind.
  651.  
  652.  
  653. Nach diesen Hinweisen ist zu hoffen, daß die meisten Leser die Grund-
  654. strukturen des vorgestellten Echtzeitsystems anwenden können. Ich ermu-
  655. tige nochmal zu eigenen Experimenten damit, aber bitte nur mit Ar-
  656. beitsdisketten und nicht an den bestehenden 'Originalen' herumändern, 
  657. sonst könnte es schwierig werden, den bestehenden Zustand wieder herzu-
  658. stellen.
  659.  
  660. Nachdem dann hoffentlich alles gut gegangen ist, kann man darangehen, 
  661. zuerst den Debugger zu erweitern. Eine erste Ergänzung bestünde zum 
  662. Beispiel darin, ihn auf eine bestimmte Tastenkombination hin zu akti-
  663. vieren oder in interaktiven Modus umzuschalten, wobei aber der Rest 
  664. durchaus in Echtzeit ablaufen könnte. Dazu wäre es nötig, in Echtzeit 
  665. Leseprozeduren zu haben, die auch wissen, an welche Aufgabe Eingaben 
  666. geschickt werden sollen. Danach kann dann dynamisch eine oder mehrere 
  667. Variablen ausgelesen und evtl. verändert werden, oder eine Aufgabe in
  668. die Liste der Aufgaben eingetragen oder (vielleicht sogar selektiv) ge-
  669. löscht werden. Mit diesen Ergänzungen hätte man schon ein ganz passables 
  670. Echtzeitsystem, das sich auch vor wirklich eingesetzten nicht zu ver-
  671. stecken braucht, auch wenn es auf einer kleinen Demonstrationsversion 
  672. basiert.
  673.  
  674. Nachdem schon neben einer ganz kurzen Einführung in die Echtzeitprog-
  675. rammierung der eigentliche 'Kern', der Scheduler, sowie der Debugger 
  676. vorgestellt worden sind, kommen jetzt sogenannte Utilities an die Reihe. 
  677. Jeder hat diesen Ausdruck im Zusammenhang mit Programmierung sicher 
  678. schon öfters gehört oder gelesen, da es Utilities für die verschieden-
  679. sten Programmiersprachen und Anwendungen gibt. Verwirrend ist aber, daß
  680. darunter fast immer etwas anderes zu verstehen ist, manchmal eine Er-
  681. weiterung oder Ergänzung des Compilers, manchmal Hilfsprogramme für eine 
  682. bestimmte Anwendung, u.s.w. Doch genau das sind eigentlich Utilities,
  683. sie sind nichts anderes als eine Sammlung von Routinen, die ganz be-
  684. stimmte (aber im Einzelfall jeweils unterschiedliche) Aufgaben erfüllen 
  685. sollen. Übrigens ist unter Tools meistens das gleiche wie unter
  686. Utilities zu verstehen, aber diese haben meistens noch einen oder meh-
  687. rere 'Vornamen', der dann angibt, wozu sie gedacht sind.
  688.  
  689. Frei ins deutsche übersetzt, sind Utilities ein Sammelsurium von Hilfs-
  690. programmen, die entweder in eigene Programme eingebunden werden können 
  691. oder bei einer bestimmten Aufgabe, die zu lösen ist, Hilfestellung ge-
  692. ben. Und genau das machen die jetzt vorzustellenden auch; sie können
  693. (und müßen sogar) bei diesem kleinen Echtzeitsystem in eigene Programme 
  694. mit eingebunden werden und haben allerlei Aufgaben, die mit ihnen 
  695. leichter zu lösen sind. Eine weitere, meistens sogar mit die wichtigste
  696. Aufgabe, ist darin zu sehen, daß durch die Verwendung von Utilities be-
  697. stimmte, mehrfach vorkommende Teilaufgaben immer auf die gleiche Art und 
  698. Weise gelöst werden; also immer richtig (oder machmal leider auch immer 
  699. falsch) bzw. immer mit den gleichen Grundvoraussetzungen.
  700.  
  701.  
  702. Utilities - Details
  703.  
  704.  
  705. Die Utilities sind nach dem schon im ersten Teil angegeben Schema auf-
  706. gebaut; also erst die Deklarationen (siehe Listing, UTILITY.DCL) in ei-
  707. nem extra File und dann genauso die Verarbeitung (siehe Listing, 
  708. UTILITY. PAS).
  709.  
  710. Deklarationen:
  711.  
  712. Die Deklarationen enthalten zuerst eine Konstante, die angibt, bis zu 
  713. welcher größten (Integer-)Zahl der niedrige Anteil von DoppelWort (siehe 
  714. Scheduler) hochgezählt wird, bevor der höhere Anteil erhöht wird. Die 
  715. Wahl von 9999 ist rein willkürlich, es hätte genauso gut die größte po-
  716. sitive Integer sein können. Ich habe diese genommen, da damit einfacher
  717. die Umwandlungroutinen mit Reals überprüft werden können; der niedrige 
  718. Anteil sind einfach die 4 Ziffern rechts, der höhere Anteil die linken 
  719. Ziffern. 
  720.  
  721. Danach werden die Register des 8086 Prozessors deklariert, die bisher 
  722. aber nur bei dem Funktionsaufruf für die Uhrzeit Verwendung finden. Die 
  723. Definitionen der Strings sind abhängig vom verwendeten Compiler, aber 
  724. hier könnten auch eigene Deklarationen und danach, in der Verarbeitung, 
  725. die zugehörigen Prozeduren für die Stringverarbeitung kommen. Zum Schluß
  726. der Deklarationen werden wieder die Routinen als Forwards deklariert.
  727.  
  728. Verarbeitung:
  729.  
  730. Der Verarbeitungsteil der Utilities ist sehr einfach, da er nur aus 
  731. einzelnen Prozeduren besteht, die untereinander nicht oder zumindest 
  732. fast nicht zusammenhängen. Dies ist aber nicht notwendigerweise so, es
  733. können auch sehr komplexe Unterprogramme implementiert werden, aber in 
  734. diesem kleinen System zur Echtzeitprogrammierung ist es bisher noch
  735. nicht nötig.
  736.  
  737. Die Prozedur PIncrDW erhöht ein Doppelwort um 1, wobei aber davon aus-
  738. gegangen wird, daß dieses größer oder gleich 0 ist (dies wird auch bei 
  739. den beiden folgenden Routinen angenommen).
  740.  
  741. Die Funktion DWinReal gibt als Ergebnis eine RealZahl, die gleich dem 
  742. Doppelwort ist, das als Parameter übergeben wurde. Dabei ist zu beach-
  743. ten, daß der niedrige Anteil 1 Element mehr enthält, als durch die max-
  744. imale Anzahl gegeben ist, da bei jedem Rücksetzen auf 0 zurückgesetzt 
  745. wird, und diese als ein Element mitzählt.
  746.  
  747. Die Prozedur RealinDW wandelt eine RealZahl in ein Doppelwort. Die Er-
  748. höhung des maximalen niedrigen Anteils um 1 ist wieder zu beachten.
  749.  
  750. Die Funktion TimeNow benutzt eine DOS-Funktion, mit der die aktuelle 
  751. Uhrzeit geholt wird. Diese Funktion ist bei anderen Systemen anzupassen, 
  752. es kann aber zur Not auch, wie schon beim Scheduler angedeutet, einfach 
  753. ein Zähler genommen werden. Als Besonderheit ist hier zu beachten, daß 
  754. als kleinste Zeiteinheit 1/100 Sekunden gewählt worden sind. Dies er-
  755. scheint auf den ersten Blick etwas seltsam, aber es ist die kleinste 
  756. Einheit, die der Funktionsaufruf liefert, wenn auch nicht bei jedem auf 
  757. 1/100 Sekunden genau, und somit wird eine Division vermieden, was bei 
  758. ganzen Sekunden nötig wäre. Falls auf Millisekunden ausgewichen wird, so 
  759. täuscht diese Funktion eine größere Genauigkeit vor, als tatsächlich 
  760. geliefert wird. Das Verfahren, solch generelle Funktionen in Utilities 
  761. zu realisieren, zeigt hier seinen entscheidenden Vorteil: wenn die Ein-
  762. heit geändert werden soll, so genügt eine Änderung an dieser Stelle, und 
  763. alles andere greift auf die aktuelle Einheit zu!
  764.  
  765. Die Prozedur PutHoch reiht eine Aufgabe mit der angegebenen Nummer in 
  766. die Aufgaben der hohen Priorität ein, falls diese Aufgabe noch nicht 
  767. belegt ist. Falls diese Aufgabe aber schon in der Aufgabenliste steht, 
  768. so wird ein Zähler für Overflow erhöht.
  769.  
  770. Die Prozedur PutFifo reiht eine Aufgabe mit der angegebenen Aufgaben-
  771. nummer und dem Parameter in den Fifo ein, falls noch ein Platz darin 
  772. ist. Falls der Fifo schon voll ist, so wird wieder ein Zähler für Over-
  773. flow erhöht.
  774.  
  775. Die Prozedur PutPerAbs reiht eine Aufgabe mit der angegebenen Nummer in 
  776. die Liste der periodischen Aufgaben ein, falls diese Aufgabe noch nicht 
  777. belegt ist, andernfalls wird wieder ein Zähler für Overflow erhöht. Die 
  778. anzugebende Zeit ist die absolute Zeit, zu der die Aufgabe bearbeitet 
  779. werden soll. 
  780.  
  781. Ein einfaches Beispiel dazu: 12 Uhr 35 Minuten 20,57 Sekunden ist 
  782.                          ((((12*60)+35)*60)+20)*100+57 = 4 532 057
  783.  
  784. Die Prozedur PutPerDelta ist die Parallele zu PutPerAbs, aber mit einer
  785. sogenannten Deltazeit. Eine Aufgabe, die in 1 Sekunde starten soll, be-
  786. nötigt als Parameter 100.
  787.  
  788. Die Funktion FrageJN gibt den zu übergebenden String aus und wartet auf 
  789. die Eingabe von J oder N. Bei einer anderen Eingabe wird ein Hinweis 
  790. ausgegeben, und so lange wiederholt, bis korrekt eingegeben wird. Bei J 
  791. wird der Funktionswert True, bei N False übergeben. Bei der Eingabe 
  792. könnte genauso gut ein einfaches Lesen auf String oder Charakter erfol-
  793. gen, falls eine Anpassung an andere Systeme zu umständlich wird.
  794.  
  795. Die Funktion FrageInteger gibt den übergebenen String aus und liest so-
  796. lange eine Integer ein, bis sie im angegebenen Wertebereich liegt. Der 
  797. eingelesene Wert wird dann als Funktionswert übergeben. Hier könnte 
  798. beispielsweise eine andere Routine das Einlesen übernehmen, die nur 
  799. Integerwerte annimmmt, da bei dem angegebenen Verfahren ein Runtime-
  800. Error entsteht, falls sonstige Charakter oder Reals eingegeben werden. 
  801.  
  802. Solche abgesicherten Routinen sind aber schon häufig veröffentlicht 
  803. worden und sollen nicht Bestandteil dieses Echtzeitsystems sein, unter 
  804. anderem auch deshalb, weil hier echt gewartet wird, bis eine korrekte
  805. Eingabe erfolgt, also von Echtzeit keine Spur. Benötigt werden diese
  806. Routinen in dem vorgestellten System für den Debugger bei der Initiali-
  807. sierung, also bevor Echtzeitaufgaben laufen.
  808.  
  809. Anwendungshinweise:
  810.  
  811. Die Routinen der Utilities stellen bis auf die Funktionen zur Abfrage 
  812. (die selbst aber nicht in Echtzeit ablaufen!) das absolute Minimum dar, 
  813. was für Echtzeitsysteme notwendig ist. Bei Bedarf können sie jederzeit
  814. erweitert werden, es ist sogar sinnvoll, alle Erweiterungen, die in 
  815. Echtzeit zu verarbeiten sind, in den Utilities unterzubringen, da dann 
  816. eventuell anfallende Korrekturen oder Ergänzungen an einer Stelle er-
  817. folgen.
  818.  
  819. Alle Echtzeitaufgaben sollten mit den angegebenen Put... Prozeduren in 
  820. die entsprechenden Listen eingetragen und nicht 'von Hand' nachcodiert 
  821. werden. Die Log... Prozeduren können im Notfall wieder entfallen, es ist 
  822. aber wirklich nicht zu empfehlen, auf sie zu verzichten.
  823.  
  824. Die Prozeduren, die Aufgaben eintragen, haben aber noch einen 'Haken', 
  825. sie dürfen nämlich nicht so ohne weiteres von Interruptprozeduren auf-
  826. gerufen werden! Wa-rum denn das? Man stelle sich vor, daß gerade eine der 
  827. Put... Prozeduren bearbeitet wird, und dann kommt ein Interrupt. Dieser 
  828. unterbricht die Prozedur, und verzweigt zu der entsprechenden Interrupt-
  829. verarbeitung. Wenn dann in dieser dieselbe Put... Prozedur aufgerufen 
  830. wird, so kann das System unkontrolliert reagieren, da teilweise auf 
  831. globale Variablen (beispielsweise Zeiger bei Fifo, Booleans, die ange-
  832. ben, ob eine Aufgabe zu erledigen ist bei hoher Priorität und Periodic) 
  833. zugegriffen wird. Dies gilt nicht nur bei Unterbrechungen der Put... 
  834. Prozeduren, sondern auch bei den entsprechenden Teilen der Verzweigungen 
  835. im Scheduler. 
  836.  
  837. Wie wäre dies zu lösen? Man kann jeweils während der 'kritischen' Teile 
  838. Interrupts sperren und danach wieder freigeben. Dies ist nicht allzu 
  839. schwierig, aber leider so systemabhängig, daß es hier nicht implemen-
  840. tiert ist. Genauso ist es, wenn jeweils eigene Put... Prozeduren für 
  841. Interrupts verwendet werden, die nachschauen, ob ein kritischer Teil 
  842. bearbeitet wird oder nicht. Falls dies nicht der Fall ist, so kann die 
  843. normale Put... Prozedur aufgerufen werden, falls aber doch, so könnte 
  844. zurückgesprungen werden, zuvor aber das Ende des kritischen Bereichs 
  845. dynamisch so verändert, daß danach die weitere Interruptverarbeitung er-
  846. folgt. Eine andere Möglichkeit wäre, alle Aufgaben mit Fifos zu ver-
  847. walten, und ein ausgeklügeltes System zu verwenden, das jeweils beim 
  848. Verzweigen bzw. Eintragen nur einen Index verändert (hier wird bei-
  849. spielsweise FifoLeer bei beiden verändert!). Diese Systeme sind schwerer 
  850. zu verstehen und benötigen mehrfach Divisionen, die auf den meisten 
  851. Rechnern sehr viel Zeit in Anspruch nehmen. 
  852.  
  853. Die einfachste Möglichkeit ist aber, auf Interrupts von außen so weit 
  854. wie möglich zu verzichten und, falls externe Ge-räte mit diesem Echtzeit-
  855. system bedient werden sollen, den jeweiligen Status periodisch mit einer 
  856. der Periodicaufgaben abzufragen. Dieses Verfahren nennt man Polling, und
  857. es ist gar nicht mal so selten anzutreffen. Manche Geräte liefern auch
  858. gar keinen Interrupt, sondern nur einen Status auf Anforderung, wie etwa 
  859. viele Drucker. In diesem Fall wird dann aber normalerweise eine Art von 
  860. Periodic als Interrupt vorgeschaltet, etwa indem ein interner Timer-
  861. interrupt angezapft wird, der einen Zähler bedient und in einer eigenen 
  862. Liste nachschaut, ob etwas und was (aber nur sehr wenig!) damit zu tun 
  863. ist. Dabei wird dann höchstens eine Aufgabe mit hoher Priorität initi-
  864. iert und, um diese Liste nicht durcheinander zu bringen, im Scheduler 
  865. bei VerzweigHoch dieser periodische Interrupt gesperrt, solange nach 
  866. einer Aufgabe gesucht wird. Diese Aufgabe wird dann noch während Inter-
  867. rupts gesperrt sind aus der Liste gelöscht; im verwendeten System wird 
  868. kein Interrupt gesperrt (siehe vorne) und das Löschen erst danach vor-
  869. genommen. Trotzdem kann bei entsprechender Disziplin auch von Interrupts 
  870. eine Aufgabe hoher Priorität in die Liste der Aufgaben eingetragen wer-
  871. den; wenn nämlich eine bestimmte Aufgabe hoher Priorität nur von einem 
  872. Interrupt (immer demselben!) angestossen wird, geht es trotzdem gut, 
  873. sofern kein Logging für diese Aufgabe vorgenommen wird. Das Protokol-
  874. lieren von Interrupts ist sowieso ein eigenes Thema, das im allgemeinen 
  875. nur dadurch zu realisieren ist, daß im Systemdesign eigene Aufgaben da-
  876. für vorgesehen werden, die dann bei auftreten des Interrupts nur initi-
  877. iert werden.
  878.  
  879. Ist aber dieses System für Echtzeitaufgaben zu gebrauchen? Frei nach 
  880. Radio Eriwan könnte die Antwort lauten: "Im Prinzip ja, aber ohne dis-
  881. ziplinierte Anwenderprogramme nicht." Dies liegt daran, daß eine gerade 
  882. zu bearbeitende Aufgabe nicht unterbrochen wird, wenn eine mit höherer 
  883. Priorität in die Liste der Aufgaben eingetragen wird, sondern die näch-
  884. ste Aufgabe vom Scheduler erst aufgerufen wird, nachdem die gerade be-
  885. arbeitete die Kontrolle zurückgegeben hat. Dieser kleine (vielleicht 
  886. auch große) Nachteil kann in Kauf genommen werden, wenn bei jeder (aber 
  887. wirklich auch jeder) Aufgabe daran gedacht wird, die Kontrolle alsbald 
  888. wieder zurückzugeben; eine typische Aufgabe für die Design-Phase, die 
  889. längste Zeit, die eine Aufgabe brauchen darf, festzulegen. Es ist also 
  890. nicht möglich, eine 'normale' Read-Anweisung in Pascal durchzuführen, da 
  891. diese einfach wartet, bis Zeichen anstehen. Es ist aber möglich, dies zu 
  892. umgehen, indem zuerst abgefragt wird, ob ein Zeichen ansteht, dieses 
  893. dann eingelesen wird, aber solange kein Zeichen ansteht, einfach 
  894. periodisch abgefragt wird (beispielsweise alle 200 Millisekunden), ob
  895. ein Zeichen ansteht. Erst wenn alles eingelesen ist (aber über 
  896. Periodic!) wird dann eine Meldung an die Aufgabe geschickt, die dies 
  897. angefordert hat.
  898.  
  899. Eine weitere mögliche Ergänzung besteht darin, bei jeder Aufgabe zu pro-
  900. tokollieren, wie lange sie braucht und wie lange sie in der Liste der 
  901. Aufgaben gewartet hat, bis sie bearbeitet wurde. Dies ist eine Mischung 
  902. zwischen Debug und Utilities, je nach (selbst festzulegendem) Schwer-
  903. punkt oder 'frei nach Gefühl' kann dies einem der beiden Module zuge-
  904. ordnet werden.
  905.  
  906. Als letzten Tip zu den Utilities ist noch anzumerken, daß eine peri-
  907. odisch wiederkehrende Aufgabe in erster Näherung einfach dadurch zu 
  908. realisieren ist, daß diese Aufgabe sich selbst wieder in die Liste der 
  909. Aufgaben reinschreibt (mit PutPerDelta), aber vorhandene Verzögerungen 
  910. addieren sich dabei auf. Eine andere Möglichkeit besteht darin, daß 
  911. diese Aufgabe sich selbst die nächste 'Soll-Aufrufzeit' mit merkt, und 
  912. beim reinschreiben die Prozedur PutPerAbs benutzt wird, mit absoluter 
  913. Zeit gleich der nächsten Sollzeit. Auf diese Zeit wird dann (zum sich 
  914. merken) wieder das passende Delta addiert. Damit kann durchaus einmal 
  915. eine Verzögerung auftreten, aber normalerweise wird sie in den nächsten 
  916. Zyklen wieder aufgeholt, indem dann einfach etwas schneller wieder die 
  917. Aufgabe drankommt. Ob so ein Verfahren sinnvoll ist, muß aber bei jeder 
  918. Aufgabe extra entschieden werden.
  919.  
  920. Die ersten Versuche mit dem soweit kompletten Echtzeitsystem sind er-
  921. wartungsgemäß nur dem Test dieses Systems vorbehalten. Dazu wird ein 
  922. Testprogramm benutzt (siehe Listing, TEST02.PAS), das einmal alle mög-
  923. lichen Aufgaben in die Liste der Aufgaben einträgt und zum anderen die 
  924. Konstante für die Nullbelastung bestimmt. Für die Bestimmung dieser 
  925. Konstanten kann beliebig viel Aufwand getrieben werden, aber nachdem 
  926. schon bei der Erklärung des Debuggers darauf hingewiesen wurde, daß eine 
  927. Aussage zur Belastung doch nur relativ ist, genügt es vollkommen, den 
  928. Scheduler ca. 10 Minuten 'leer' laufen zu lassen, d. h. ohne jede Auf-
  929. gabe, und den Belastungszähler dann auszuwerten. Diese Auswertung ist 
  930. auch nicht sehr detailliert, man kann sehr einfach solange probieren, 
  931. bis als Endergebnis beinahe 0 herauskommt. Damit wird aber nur die Kon-
  932. stante für das System bestimmt, auf dem das Testprogramm lief; auf an-
  933. deren kann ein ganz anderer Wert herauskommen. Eine andere Möglichkeit 
  934. bestünde darin, bei InitSchedul durch einen kurzen leeren Testlauf von 
  935. beispielsweise 2 Sekunden diesen Wert grob bestimmen zu lassen (aber 
  936. dann in einer Variablen ablegen!).
  937.  
  938. Diese Aufgabe wird einmal durch das Testprogramm selbst ausgeführt, in-
  939. dem die periodische Aufgabe Nummer 1 in die Liste der Aufgaben einge-
  940. tragen wird, zum anderen durch diese Aufgabe (siehe Listing, File 
  941. PERIOD1 .PAS), indem die Nullbelastung berechnet und ins LogFile ausge-
  942. geben wird. Dabei wird in einer Aufgabe die Ausgabemöglichkeit des 
  943. Debuggers aufgerufen. Nachdem dabei für die NullBelastung bei meiner 
  944. Maschine der Wert von 2.57 (oder 257 für 100 mal) herausgekommen ist, 
  945. habe ich diese Zahl als Konstante ins File SCHEDUL.DCL übernommen.
  946.  
  947. Dieses Beispiel zeigt auch, daß in einem Programm zweimal nacheinander 
  948. Echtzeitteile drin sein können, aber dies wurde nur deshalb gemacht, 
  949. damit nur ein Testprogramm nötig ist; es hätten genauso gut zwei ge-
  950. trennte sein können.
  951.  
  952. Die LogFiles sind in diesem Fall das interessanteste, es sind TEST0 
  953. 2_1.LOG und TEST02_2.LOG. Bei Schwierigkeiten mit der Reihenfolge der 
  954. Verzweigungen sollten zusätzlich die Prozeduren des Debuggers zur 
  955. Verfolgung der Aufgabenlisten (PLogInhalt...) an verschiedenen Stellen 
  956. aufgerufen werden, siehe auch dazu die Hinweise beim Debugger.
  957.  
  958. Scheduling begonnen: 6950403 beendet: 6950843 Dauer:     440 (1/100 Sek)
  959. Anzahl Durchläufe des Schedulers:     936
  960. ergibt eine Belastung von     170 Promill
  961. Overflow bei hoher Priorität: 0
  962.                         Fifo: 0
  963.                     Periodic: 0
  964.   
  965.  
  966.           File TEST02_2.LOG
  967.  
  968. Periodic Nr. 1 mit Delta   60000 (7012639 abs) in Aufgabenliste eingetragen
  969. Periodic Nr. 1 SollZeit 7012644 IstZeit 7012645
  970. Belastungszaehler: Hoch 15
  971.                    Niedrig 4207
  972. Real Wert:  154207
  973. ergibt     257 Durchläufe in 1/10000 Sek. (=NullBelastung * 100)
  974.  
  975. Scheduling begonnen: 6952644 beendet: 7012645 Dauer:   60001 (1/100 Sek)
  976. Anzahl Durchläufe des Schedulers:  154207
  977. ergibt eine Belastung von      -3 Promill
  978. Overflow bei hoher Priorität: 0
  979.                         Fifo: 0
  980.                     Periodic: 0
  981.  
  982. Anwendung - Einführung
  983.  
  984.  
  985. Nachdem das Echtzeitsystem jetzt fertig ist, soll natürlich an einem 
  986. Beispiel dessen Anwendung und Funktionsfähigkeit demonstriert werden. 
  987. Doch was eignet sich dazu? Am einfachsten und sinnvollsten wäre sicher 
  988. eine mehr oder weniger komplexe Meßwerterfassung mit dazugehöriger Steu-
  989. erung, aber dazu ist extra Hardware erforderlich, die sicher nicht je-
  990. dem, der bis jetzt dieser Serie gefolgt ist, zur Verfügung steht. Das-
  991. selbe gilt sogar bei sogenannten Lehrsystemen zu demselben Thema, die 
  992. inzwischen relativ günstig zu bekommen sind, aber der Preis und die 
  993. Frage 'wozu?' sind meistens noch ein Hindernis. Deshalb wird hier ein 
  994. Spielprogramm als Anwendung vorgestellt, das nicht allzu einfach sein 
  995. soll, aber doch noch verständlich und gleichzeitig eine mehr oder we-
  996. niger sinnvolle Anwendung der Echtzeitprogrammierung darstellt. Unbe-
  997. stritten ist aber, daß ein ähnliches Spielprogramm auch ohne Echtzeit-
  998. system erstellt werden kann, aber ich hoffe, daß mit diesem der Ablauf 
  999. leichter zu verstehen (und evtl. zu ändern) ist. Zusätzlich ist zu er-
  1000. warten, daß dieses Spiel auf allen Systemen etwa gleich schnell abläuft, 
  1001. also nicht auf einem schnellen PC-AT nicht mehr zu spielen ist. Das 
  1002. einzige evtl. auftretende Problem könnte bei sehr langsamen Maschinen 
  1003. und Interpreter anstelle eines Compiler eine Überlastung werden, aber im 
  1004. Normalfall ist dies nicht zu erwarten.
  1005.  
  1006. Die erste Frage ist, welche Ein- und Ausgabemöglichkeiten der 'normale 
  1007. Leser' zur Verfügung hat. Hier wird davon ausgegangen, daß zur Eingabe 
  1008. die Tastatur ausreichen muß, da die sicher bei jedem vorhanden ist. Der 
  1009. Ausgabe dient der Bildschirm, den auch jeder hat, aber dazu kommt noch 
  1010. die Möglichkeit der akustischen Untermalung (bei PC's ebenfalls Stan-
  1011. dard) und eine Druckerausgabe. Die Druckerausgabe ist nicht unbedingt 
  1012. erforderlich, da sie bei dem Spiel kaum sinnvoll ist und nur zur Ausgabe 
  1013. der Resultate und zu Demonstrationszwecken eingebunden ist; zudem sind 
  1014. schon viele residente Druckerutilities in Umlauf oder veröffentlicht 
  1015. worden. Eine schöne Demonstration der Echtzeitfähigkeit liegt aber dar-
  1016. in, daß der Status des Druckers praktisch 'jederzeit' angezeigt und auch 
  1017. etwas ausgedruckt wird, obwohl nebenher ein Spiel abläuft. Dies ist aber 
  1018. leider in der vorgestellten Version nur bei PC's so zu realisieren, bei
  1019. anderen Systemen muß dieser Teil angepasst (oder weggelassen) werden; 
  1020. dasselbe gilt natürlich auch für die akustische Untermalung. Um die
  1021. Bildschirmausgaben 'allgemein' zu halten, wird nicht der Grafik-, 
  1022. sondern nur der Textmodus benutzt; hier sind Anpassungen wahrscheinlich 
  1023. nicht erforderlich.
  1024.  
  1025. Als nächstes stellt sich natürlich die Frage, wie das Spiel ablaufen 
  1026. soll. Um nicht wieder ein Schießspiel (egal ob wirklich neu oder ein 
  1027. altes in neuer Version) zu spielen, habe ich mir folgendes ausgedacht:
  1028.  
  1029. Der Rechner generiert zufällig eine Folge von Buchstaben, die am Bild-
  1030. schirm dargestellt werden. Der Bediener (Spieler) muß nun diese Buch-
  1031. staben eingeben, wobei aber beim ersten Fehler abgebrochen wird und für 
  1032. jede neue Folge steht etwas weniger Zeit zum Eingeben zur Verfügung.
  1033. Nach 15 Folgen ist das Spiel beendet. Auf dem Drucker wird jede Folge 
  1034. und auch eine Meldung ausgegeben, ob diese korrekt eingegeben wurde, ein 
  1035. Fehler war oder die Zeit überschritten wurde. Bei Spielende wird eine 
  1036. Art von kleiner Statistik auf dem Drucker und auf dem Bildschirm 
  1037. ausgegeben, die unter Umständen für eine Auswertung der 
  1038. Belastungsmöglichkeit des Spielers herangezogen werden könnte; aber dies
  1039. bleibt dem dafür ausgebildeten Personenkreis vorbehalten und wird nicht 
  1040. vom Programm übernommen. Der Zustand des Druckers wird parallel am 
  1041. Bildschirm angezeigt. Bei jeder Folge von Buchstaben wird ein Geräusch 
  1042. ausgegeben, dessen Frequenz von der verbleibenden Zeit (für die aktuelle
  1043. Folge) abhängig ist.
  1044.  
  1045. Anwendung - Design
  1046.  
  1047.  
  1048. Eine der schwierigsten Aufgaben bei den Echtzeitsystemen ist das Design. 
  1049. Darunter versteht man die Aufteilung in einzelne Komponenten (Software 
  1050. und Hardware), und die Definition deren Aufgabe; hier wird aber davon 
  1051. ausgegangen, daß die verwendete Hardware den Anforderungen genügt. Diese 
  1052. Aufteilung erfolgt meistens Top-Down, das bedeutet, daß zuerst eine 
  1053. grobe Aufteilung gemacht wird, die anschließend weiter verfeinert (jede 
  1054. Aufgabe wird wieder in mehrer Aufgaben aufgeteilt) wird. Ein großes 
  1055. Problem besteht darin, daß diese Aufteilung 'konsistent' sein muß, d.h. 
  1056. daß eine Aufgabe nur die Eingaben verarbeiten kann, die ihr auch zur 
  1057. Verfügung gestellt werden. Dies hört sich zuerst trivial an, aber wie 
  1058. sieht es nach der x-ten Änderung aus? Erfahrungsgemäß rühren sehr viele 
  1059. Fehler daher, daß bei einer Änderung ein Zustand verändert wird, der 
  1060. aber von einer anderen Komponente praktisch nebenher mitverwendet wird. 
  1061. Diese Fehler zu finden ist gar nicht mehr so einfach, es wird aber ver-
  1062. sucht, durch verschiedenartige Methoden, die beim Design und bei der 
  1063. Realisierung angewendet werden, diese Art Fehler zu vermeiden. In 
  1064. neuerer Zeit sind mehrere softwareunterstützte Tools auf dem Markt, die 
  1065. bei dieser Aufgabe helfen, aber bis heute hat sich noch keiner soweit 
  1066. durchsetzen können, daß von einem Standard die Rede sein kann. Dies 
  1067. liegt sicher auch daran, daß sich schon bei den Methoden, die ja auch 
  1068. manuell angewandt werden können, noch keine hat durchsetzen können. Al-
  1069. lein die Ansicht, daß eine grafische Darstellung günstig ist, hat sich 
  1070. so ziemlich durchgesetzt, aber schon bei den einzelnen Komponenten (de-
  1071. ren Bezeichnung und Darstellung) scheiden sich die Geister.
  1072.  
  1073. Auf diese Problematik soll aber im Rahmen dieser Serie über Echtzeit-
  1074. programmierung nicht weiter eingegangen werden, hier soll eine Anwendung 
  1075. entworfen werden. Dazu wird eine Art von strukturierter Analysis be-
  1076. nutzt, wie sie von de Marco [1] beschrieben wurde. Nicht auf Software be-
  1077. zogen, kann dasselbe als eine Art Blockschaltbild bezeichnet werden, 
  1078. wobei die Komponenten nicht aus Hardware, sondern hier aus Software be-
  1079. stehen.
  1080.  
  1081.  
  1082. Eine mögliche Aufteilung sieht folgendermaßen aus:
  1083.  
  1084.  
  1085. ┌─────────────────────────────────────────────────────────┐
  1086. │                     P E R I O D I C                     │
  1087. └────────┬─────────────────┬───────────────────┬──────────┘
  1088.          │                 │                   │           
  1089. ┌────────┴────────┐┌───────┴─────────┐┌────────┴──────────┐
  1090. │Folge            ││Tastatur         ││Drucker-Status     │
  1091. │generieren       ││abfragen         ││abfragen           │
  1092. │(Per1)           ││(Per2)           ││(Per3)             │
  1093. └────────┬────────┘└───────┬─────────┘└────────┬──────────┘
  1094.          │                 │                   │           
  1095. ┌────────┴────────┐┌───────┴─────────┐┌────────┴──────────┐
  1096. │Timeout für      ││Taste verarbeiten││Status darstellen  │
  1097. │diese Folge      ││und Geräusch     ││und Zeichen drucken│
  1098. │(Per1)           ││(Per2)           ││(Per3)             │
  1099. └─────────────────┘└─────────────────┘└───────────────────┘
  1100.  
  1101.  
  1102. Diese erste Aufteilung soll in diesem Fall auch schon gleichzeitig die 
  1103. letzte sein, da sie schon ziemlich dedailliert für die kleine Aufgabe 
  1104. ausgefallen ist. Normalerweise wird, zumindest bei größeren Projekten, 
  1105. eine Beschreibung mit den dazugehörigen Eingangs- und Ausgangsdaten er-
  1106. stellt, die dann als Grundlage für eine weitere Verfeinerung dient. Hier 
  1107. sagen die Bezeichnungen eigentlich schon genügend aus, so daß sogar auf 
  1108. eine Beschreibung verzichtet wird, vor allem aber auch deshalb, weil die 
  1109. Komponenten eigentlich sehr gut im Listing bzw. in den folgenden Details 
  1110. wiederzufinden sind.
  1111.  
  1112. Auf eine Besonderheit muß jedoch noch hingewiesen werden; hier wird 
  1113. ausschließlich die periodische Steuerung des Ablaufs benutzt, wobei jede 
  1114. Periodic nach dieser Aufteilung 2 Aufgaben wahrnimmt. Daß nur die 
  1115. Periodic verwendet wird, liegt erstens im Spiel (Art der Aufgabe), aber 
  1116. es wäre zweifellos angebracht, die Tastatur über Interrupt zu 
  1117. verarbeiten. Dies wurde aus dem inzwischen schon zur Genüge bekannten 
  1118. Grund der Inkompatibilität zwischen verschiedenen System (Hardware) und 
  1119. Compilern (Software) wieder einmal umgangen, und es funktioniert auch 
  1120. so. Daß eine Aufgabe gelegentlich mehrere, die doch irgendwie 
  1121. zusammenhängen (zumindest von der Logik her) wahrnimmt, kommt auch in 
  1122. größeren Systemen häufiger vor, zumindest spricht nichts dagegen, da ja 
  1123. eine normale Prozedur auch je nach Eingangswerten in verschiedene Zweige 
  1124. aufgeteilt werden kann.
  1125.  
  1126.  
  1127. Anwendung - Details
  1128.  
  1129.  
  1130. Nach diesen allgemeinen Überlegungen zu einer Anwendung in Form eines 
  1131. Spiels folgt nun die Erklärung der einzelnen Komponenten. Die 
  1132. Modularisierung wird so ähnlich wie bisher vorgenommen; es gibt ein 
  1133. Deklarationsfile (siehe Listing, SPIEL.DCL), das alle mehrfach 
  1134. verwendeten Konstanten und Variablen enthält, ein HauptFile (SPIEL.PAS), 
  1135. und für jede Aufgabe ein extra File (FOLGE.PAS, TASTATUR.PAS und 
  1136. DRUCKER.PAS). Hier kommt noch ein weiteres File zur Initialisierung 
  1137. (INITIAL.PAS) dazu, das neben der Spielerklärung alle Variablen 
  1138. vorbelegt; bei Turbo-Pascal könnte dies schon in der Deklaration 
  1139.  geschehen, aber das geht nicht bei allen Versionen von Pascal, deswegen 
  1140. wurde es extra in einer Prozedur gemacht.
  1141.  
  1142.  
  1143. Deklarationen:
  1144.  
  1145.  
  1146. Bei den Deklarationen wurde kein Wert auf eine ausgeklügelte 
  1147. Datenstruktur gelegt; es wurden nur Grundtypen verwendet. Die Kommentare 
  1148. müßten bei weitem ausreichen, um zu verstehen, wozu die jeweilige 
  1149. Variable verwendet wird, deshalb folgt hier keine genauere Erklärung.
  1150.  
  1151. Hauptprogramm (SPIEL.PAS):
  1152.  
  1153. Das Hauptprogramm enthält die schon bekannten Includes für die 
  1154. allgemeinen Files, danach folgen die speziell für diese Anwendung 
  1155. erforderlichen. Bei der Verarbeitung wird zuerst der Debugger 
  1156. initialisiert, dann der Scheduler und danach der Anwendungsteil. Um 
  1157. danach sofort Schedul aufrufen zu können, muß bei der Initialisierung 
  1158. des Anwendungsteils mindestens eine Aufgabe in den Scheduler eingetragen 
  1159. werden (siehe in PInit). Zum Schluß wird nur noch der Bildschirm 
  1160. gelöscht. Die Initialisierung des Debuggers erfolgt bei fertigen 
  1161. Programmen meistens ohne jegliches Protokollieren, auch ohne Abfrage, ob 
  1162. protokolliert werden soll, außer wenn dies eine spezielle Anforderung an 
  1163. das System ist. Während der Testphase wurde aber von Protokollen 
  1164. ausgiebig Gebrauch gemacht; es wurde teilweise an kritischen Stellen 
  1165. extra aufgezeichnet (siehe Debugger, Anwendungshinweise).
  1166.  
  1167. Initialisierung (INITIAL.PAS):
  1168.  
  1169. Es wird eine Spielerklärung ausgegeben, abgefragt, welcher Drucker 
  1170. verwendet werden soll, Variablen initialisiert und die Aufgaben fürs 
  1171. Generieren einer Folge sowie für Ausgabe auf den Drucker in die Liste 
  1172. der Aufgaben eingetragen.
  1173.  
  1174. Die Prozedur PErklaerung hat eine lokale Prozedur InitRandom, mit der 
  1175. eine eingebaute Zufallsfunktion wirklich zufällig wird. Dies ist bei 
  1176. vielen BASIC-Versionen dringend nötig, da nach dem Start Random immer 
  1177. die gleiche Folge von Zahlen erzeugt. Ob es in Pascal wirklich nötig 
  1178. ist, wurde nicht ausprobiert; dies ist auf jeden Fall der sichere Weg. 
  1179. Dem Zufall wird dadurch auf die Sprünge geholfen, indem solange die 
  1180. Zufallsfunktion aufgerufen wird, bis eine Taste gedrückt ist. Da dies 
  1181. nicht immer gleich schnell passiert, wird unterschiedlich oft die 
  1182. Funktion aufgerufen; also erzeugt sie zufälligere Werte. In der 
  1183. Hauptprozedur wird die Bedienungsanleitung ausgegeben, danach die 
  1184. Zufallsfunktion initialisiert und dann das Gerüst des Bildschirms 
  1185. aufgebaut.
  1186.  
  1187. Die Prozedur PInit initialisiert zuerst einige Werte (evtl auch anders 
  1188. möglich, siehe vorne), dann wird PErklaerung aufgerufen und zum Schluß 
  1189. die Aufgaben der Periodic 1 und 3 in die Liste der Aufgaben eingetragen.
  1190.  
  1191. Folge generieren und Zeit dafür überwachen (FOLGE.PAS):
  1192.  
  1193. Dieser Teil beinhaltet das generieren einer Folge sowie eine TimeOut 
  1194. Überwachung, ob die letzte Folge in der zur Verfügung stehenden Zeit 
  1195. eingegeben wurde. 
  1196.  
  1197. Zuerst wird die Prozedur FolgeAusgeben zur Verfügung gestellt, die eine
  1198. Folge auf dem Bildschirm und auf dem Drucker (durch Aufruf der 
  1199. Druckerprozeduren Druck...) ausgibt. 
  1200.  
  1201. Dann gibt es eine Prozedur Statistik, die eine kurze Statsitik über das 
  1202. abgelaufene Spiel erstellt und wieder auf beiden Ausgabemedien ausgibt. 
  1203. Für die weitere Ablaufsteuerung wird noch eine Variable (RestDrucken) 
  1204. auf True gesetzt, mit der angegeben wird, daß keine eigentliche 
  1205. Verarbeitung mehr passiert, sondern nur noch der Rest (von Folgen und 
  1206. Statistik) ausgedruckt werden soll.
  1207.  
  1208. Danach folgt die Hauptverarbeitung in der Prozedur PPeriodic3. Hier ist 
  1209. zu beachten, daß diese Prozedur aus dem File SONSTIGE.PAS gelöscht oder 
  1210. als Kommentar gekennzeichnet wird, da sonst Fehler während des 
  1211. Compilierens auftreten. Diese Prozedur hat 2 Hauptzweige, entsprechend 
  1212. den Aufgaben generieren und überwachen. 
  1213.  
  1214. Beim generieren werden zuerst einige Variablen gesetzt, dann die Folge 
  1215. mit der Randomfunktion generiert (bei nicht initialisierter Funktion 
  1216. könnte evtl. immer die gleiche Folge entstehen, siehe bei Initialisiere) 
  1217. und die Folge mit FolgeAusgeben auf dem Bildschirm ausgegeben. Bei der 
  1218. Generierung der Folge wird noch extra darauf geachtet, daß nicht 2 
  1219. gleiche Buchstaben direkt aufeinander folgen. Dies könnte evtl. dazu 
  1220. führen, daß mit Autorepeat der Tastenfunktion eine komplette Folge 
  1221. praktisch 'auf einmal' eingegeben werden könnte, aber viel 
  1222. entscheidender ist, daß sonst Autorepeat von der Tastaturverarbeitung 
  1223. selbst erledigt werden müßte. Warum denn das, wird sich mancher zuerst 
  1224. denken. Ganz einfach, weil die Zeit bis zur Wiederholung zwischen dem 1. 
  1225. und 2. Buchstaben sehr viel größer ist, als zwischen den folgenden. Wie 
  1226. könnte dann nach 4 gleichen Buchstaben beispielsweise ein anderer 
  1227. 'gezielt an dieser Stelle' eingegeben werden wenn vorher wiederholt 
  1228. wird? Beim ersten Aufruf der Aufgabe, also wenn die 1. Folge generiert 
  1229. wird, wird die Aufgabe mit der Periodic 2 (Tastaturabfrage) mit der 
  1230. aktuellen Zeit in die Liste der Aufgaben aufgenommen; sie kommt also 
  1231. sofort dran. Dann trägt sich diese Periodic selber in die Liste der 
  1232. Aufgaben ein, um den TimeOut zu überwachen, wobei die Zeit, die zur 
  1233. Verfügugn steht, am Anfang berechnet wurde.
  1234.  
  1235. Beim Überwachen der Zeit für die zuletzt generierte Folge wird zuerst 
  1236. überprüft, ob noch nicht alle Buchstaben eingegeben wurden und noch kein 
  1237. Fehler auftrat, um einen entsprechenden Hinweis auszugeben; falls alle 
  1238. korrekt eingegeben wurden oder ein Fehler entdeckt wurde, so wird dies 
  1239. bei der Tastaturabfrage mit bearbeitet. Wenn noch nicht alle 15 Folgen 
  1240. fertig sind, wird eine Variable FolgenGenerier, mit der die Verzweigung 
  1241. in generieren und überwachen erfolgt, zu True gesetzt und dieselbe 
  1242. Aufgabe wieder in die Liste aufgenommen, damit nach 5 Sekunden eine neue 
  1243. Folge generiert wird. Wenn aber alle Folgen beendet sind, wird Statistik 
  1244. aufgerufen, um eine kleine Auswertung zu machen.
  1245.  
  1246. Tastatur abfragen, auswerten und Geräusch (TASTATUR.PAS)
  1247.  
  1248. Die Aufgabe dieses Teils besteht darin, mit der Periodic 2 die Tastatur 
  1249. abzufragen, den Code einer gedrückten Taste auszuwerten und, wenn gerade 
  1250. eine Folge einzugeben ist, das entsprehende Zeichen darzustellen. 
  1251. Daneben wird dann noch festgestellt, ob bei der Eingabe ein Fehler 
  1252. auftrat oder die Folge schon komplett eingegeben ist, sowie ein Geräusch
  1253. ausgegeben, falls weder ein Fehler festgestellt wurde, noch die Folge 
  1254. fertig ist.
  1255.  
  1256. Zu diesen Aufgaben wird zuerst die Prozedur EingabeAuswerten 
  1257. bereitgestellt, die, wie ihr Name schon sagt, die Eingaben während einer 
  1258. Folge auswertet. Dazu wird zuerst untersucht, ob das Zeichen ein 
  1259. Großbuchstabe ist, da alle anderen nicht beachtet werden. Die gewählte 
  1260. Konstruktion der Abfrage eines geschlossenen Bereiches auf die Grenzen 
  1261. ist im allgemeinen erheblich schneller als die von Pascal vorgegebene 
  1262. Funktion IN; nur speziell auf Laufzeit getrimmte Compiler können bei 
  1263. solchen Spezialfällen mit IN den gleich schnellen Code erzeugen, wie es 
  1264. der Programmierer durch die gezielte Abfrage vorgibt. Danach wird das 
  1265. Zeichen dargestellt und mit dem nächsten der Folge verglichen. Falls 
  1266. eine Übereinstimmung festgestellt wird, dann wird noch untersucht, ob 
  1267. die Folge komplett fertig ist und, dalls dies der Fall ist, ein Hinweis 
  1268. ausgegeben und die Variable FolgeEingeben zu False gesetzt. Wenn ein 
  1269. Fehler auftrat, wird ebenfalls ein entsprechender Hinweis ausgegeben und 
  1270. dieselbe Variable gesetzt. 
  1271.  
  1272. In der Hauptprozedur PPeriodic2 wird zuerst nachgeschaut, ob eine Taste 
  1273. gedrückt ist. Falls keine Taste gedrückt ist, wird sofort die neue Zeit, 
  1274. zu der dieselbe Prozedur wieder aufgerufen werden sollte, berechnet und 
  1275. die Aufgabe wieder in die Liste der Aufgaben übernommen. Mit dieser 
  1276. etwas eigenwilligen Konstruktion, daß ein Delta zu der ursprünglichen 
  1277. Sollzeit addiert wird, kann sichergestellt werden, daß zumindest im 
  1278. Mittel diese Aufgabe mit der durch Delta festgelegten Frequenz 
  1279. aufgerufen wird, auch wenn die Genauigkeit der Uhr oder eine etwaige 
  1280. große kurzfristige Belastung dies normalerweise nicht zulassen würden. 
  1281. Mit der von den Utilities zur Verfügung gestellten Prozedur PutPerDelta 
  1282. kann höchstens die Frequenz erreicht werden, mit der sich die Uhr 
  1283. verändert, aber dies könnte bei PC's (Uhränderung mit ca. 18 Hz) evtl. 
  1284. zu einem nachlaufen der Tastaturverarbeitung führen. Falls aber eine 
  1285. Taste gedrückt ist, so wird zuerst deren Code gelesen, in Großbuchstaben 
  1286. umgewandelt, und, falls eine Folge eingegeben werden soll, die Prozedur 
  1287. EingabeAuswerten aufgerufen. Falls nur der Rest noch zu drucken ist, so 
  1288. wird, falls ein Leerzeichen eingegeben wurde, Ende zu True gesetzt, um 
  1289. das Scheduling zu beenden. Falls immer noch eine Folge eingegeben werden 
  1290. soll, so wird die nächste Frequenz berechnet und ein entsprechendes 
  1291. Geräusch ausgegeben.
  1292.  
  1293. Drucker Status und Drucken (DRUCKER.PAS)
  1294.  
  1295. Die Aufgabe dieses Moduls besteht darin, den Druckerstatus darzustellen 
  1296. und Zeichen auf dem Drucker auszugeben. Dazu muß der Status natürlich 
  1297. abgefragt werden und ein Druckerbuffer zur Verfügung gestellt werden, in 
  1298. dem Zeichen gespeichert werden können, wenn deren Ausgabe nicht schnell 
  1299. genug erfolgt. Hier wird jedes Zeichen zuerst in den Buffer aufgenommen, 
  1300. und die Ausgabe erfolgt dann später.
  1301.  
  1302. Zuerst werden einige Prozeduren (Druck...) zur Verfügung gestellt, die 
  1303. Zeichen in den Buffer (DruckBuffer) aufnehmen, solange darin noch Platz 
  1304. ist. Man kann diese Prozeduren relativ leicht ergänzen, so daß sie 
  1305. entweder als Parameter oder in einem Zähler, der erst am Ende 
  1306. ausgewertet wird, einen Overflow, also nicht mehr genügend Platz im 
  1307. DruckBuffer, melden. Die Schwierigkeit liegt darin, daß zumindest eine
  1308. x-beliebige Anwenderprozedur meistens nicht weiß, was sie tun soll, wenn 
  1309. der Platz nicht ausreicht. Bei Bedarf kann mit nur einer Änderung 
  1310. (Konstante DruckBufLen in SPIEL.DCL) der Speicher fast beliebig (Grenzen 
  1311. sind vom Compiler vorgegeben oder aber Systemgrenzen, also Hardware) 
  1312. verändert werden.
  1313.  
  1314. Die Hauptprozedur PPeriodic3 hat 2 lokale Prozeduren. Die erste, 
  1315. DruckStatus, fragt den Status des Druckers über einen Systemaufruf ab. 
  1316. Dieser Teil ist sehr von dem verwendeten System abhängig und muß bei 
  1317. anderen angepasst werden. Die zweite, DruckStatusAusgeben, gibt den 
  1318. Status auf dem Bildschirm aus. Hier wurden nicht alle Möglichkeiten 
  1319. ausgeschöpft, eine detailliertere Fehleranalyse kann mit einem der 
  1320. vielen Programme dafür (z. B. Druckerabfrage unter MS-DOS, PASCAL 4/88)
  1321. erfolgen, aber als grobe Richtung, welcher Fehler vorliegt, genügt diese 
  1322. Prozedur. In der Hauptprozedur wird dann solange der Status abgefragt 
  1323. und, wenn dieser keinen Fehler anzeigt, ein Zeichen auf dem Drucker 
  1324. ausgegeben, bis entweder eine maximale Anzahl erreicht ist der Buffer 
  1325. leer oder der Druckerstatus nicht mehr Ok ist. Der mehrfache Aufruf der 
  1326. Statusabfrage könnte entfallen, da bei der Ausgabe eines Zeichens der 
  1327. Status mitgeliefert wird, aber so bleiben wir auf der sicheren Seite. 
  1328. Danach wird dann der Status ausgegeben, und die Aufgabe setzt sich 
  1329. selber wieder in die Liste, wobei aber die Zeit bis zum nächsten Aufruf 
  1330. davon abhängt, ob noch was im Buffer steht oder nicht. Wenn der Buffer 
  1331. leer ist und nur noch der Rest gedruckt werden sollte, dann wird Ende zu 
  1332. True, um das Scheduling zu beenden.
  1333.  
  1334. Anwendungshinweise:
  1335.  
  1336. Bei realen Systemen, die wichtige Meßwerte verarbeiten und hart an der 
  1337. Grenze ihrer Belastung liegen, wird versucht, so viel Aufgaben wie 
  1338. möglich durch Interrupts zu initiieren und dann die Verarbeitung mit 
  1339. hoher Priorität zu erledigen; aber eben nur bei Bedarf. Hier bietet sich 
  1340. zuerst wieder die Tastaturabfrage an, aber auch Meßwerte können über ein 
  1341. entsprechendes Interface Interrupts liefern.
  1342.  
  1343. Der DruckBuffer, in dem Zeichen gespeichert werden, ist als Fifo 
  1344. ausgelegt, genau so wie der Fifo für Aufgaben. Die Deklaration sowie die 
  1345. Verwaltung von Fifos kann über allgemeine Prozeduren erfolgen, deren 
  1346. Parameter dann jeweils angepasst werden. Dies kann entweder über mehrere 
  1347. Include-Files geschehen (wird aber nicht von allen Versionen 
  1348. unterstützt) oder sogar Bestandteil der Sprache sein. Diese Methode wird 
  1349. als 'Generic' bezeichnet und setzt sich in letzter Zeit immer mehr 
  1350. durch.
  1351.  
  1352. Ein sinnvoller Ausbau des vorgestellten Systems zur Echtzeitverarbeitung 
  1353. wäre es, den Modul zur Druckerausgabe auch in Debug zu verwenden, 
  1354. anstatt direkt auszugeben. Dann kann ein Protokoll auf Drucker wirklich 
  1355. in Echtzeit, also 'nebenher', erstellt werden.
  1356.  
  1357. Dieses System oder ähnliche, die aber einfach zu verstehen sein sollten, 
  1358. eignet sich sehr gut für die Programmierung von jeder Art von 
  1359. Reaktionsspielen. Die damit realisierten Spiele können dann auf einem 
  1360. normalen XT genauso gespielt werden wie auf einem schnellen AT. Viele 
  1361. Spiele laufen auf einem schnelleren Rechner einfach zu schnell; mit 
  1362. Echtzeitsystemen wäre das nicht passiert. Es ist sehr mühsam, ein
  1363. Programm, das nicht für Echtzeit vorgesehen war, später darauf zu 
  1364. trimmen, daß es auf allen Maschinen fast gleich schnell läuft.
  1365.  
  1366.  
  1367. Literatur
  1368.  
  1369. [1]  Tom de Marco
  1370.      Structured Analysis and System Specification
  1371.      Jourdon Verlag
  1372.