Der Kernel - Software-InterruptsDruckerspoolerAls Beispiel für diese doch recht komplexe Materie meine 'Lieblings-Anwendung': Ein per Software realisierter Drucker-Spooler. Dieses Programm hat die Aufgabe, Wartezeiten bei der Text-Ausgabe auf dem Drucker zu überbrücken. Dazu benutzt der Spooler einen Pufferspeicher, in den das laufende Programm auf der einen Seite hineinschreibt, und der auf der anderen Seite, via Alle noch folgenden Anschlüsse fallen unter die Rubrik STEUER- oder auch CONTROLBUS:: INT - InterruptInterrupt ausgelesen und zum Drucker geschickt wird. Dadurch kann das laufende Programm, wenn der Drucker 'mal gerade keine Zeichen empfangen kann (weil er möglicherweise gerade eine Zeile ausdruckt), trotzdem weiterschreiben. Die Zeichen werden vom Spooler nur erst einmal in seinem Puffer zwischengespeichert. Die hier vorgestellte Variante ist schon recht 'intelligent'. Gepatcht wird zunächst einmal Die Sprungleisten: die Indirectionsdie Indirection Die Indirections der Firmware-Packs: BDF1: IND MC WAIT PRINTERIND MC WAIT PRINTER, die normalerweise den Status der Busy-Leitung vom Drucker testet und ein Zeichen ausdruckt, sobald dieser bereit wird. Spätestens nach MAIN FIRMWARE JUMPBLOCK: CASSETTE MANAGER Zum Ausdruck werden letztlich die beiden Vektoren MACHINE PACK: BD2E: MC BUSY PRINTERMC BUSY PRINTER (Erklärung der Anschlussbelegung: TestTeste, ob Busy) und MACHINE PACK: BD31: MC SEND PRINTMC SEND PRINTER (Sende Zeichen ohne Busy-Test) aufgerufen. MACHINE PACK: BD2B: MC PRINT CHARMC PRINT CHAR, der Vektor, der beide Die Fließkomma-Routinen: FunktionenFunktionen ineinander vereint, kann nicht benutzt werden, da dieser nur die gepatchte Überblick: Die Indirections der Firmware-Packs Der alte Eintrag in der Überblick: Die Indirections der Firmware-Packs Da beim Aufruf einer Event-Routine das untere Erläuterung zu den Anschlüssen 40 bis 45: 42 - ROMEN (0)ROM immer eingeblendet ist, werden die beiden Routinen nicht über ihre Vektoren, sondern über eine Kopie, aus der der ROM-Konfiguration: RestartsRestart entfernt wurde, aufgerufen. Dadurch wird Rechenzeit gespart. Der Puffer ist, wenn man sich an die Länge im Listing hält, etwas über 1000 Zeichen lang und überschreibt die Initialisierungs-Routine. Die wird ja nur einmal aufgerufen, und ist danach uninteressant. Wird nun vom laufenden Programm ein Zeichen zum Drucker geschickt, so führt das zum Aufruf der Routine JOB. Die Ein/Ausgabe-Bedingungen, die von JOB befolgt werden müssen, sind von der Überblick: Die Indirections der Firmware-Packs • DE,HL,IX,IY dürfen nicht verändert werden und • ein gesetztes CY-Flag zeigt an, dass das Zeichen erfolgreich abgesetzt wurde. Der Pufferspeicher ist programmtechnisch gesehen natürlich eine QUEUE, die als Ringspeicher in einem Datenspeicherung und Datenstrukturen: ArraysArray für fixed lenght Datenspeicherung und Datenstrukturen: Recordsrecords realisiert ist. Verwaltet wird er von den beiden Zeigern ANF und ENDE. ANF zeigt dabei immer auf das erste (älteste) Zeichen im Puffer, das als nächstes gedruckt werden muss, und ENDE auf den ersten freien Platz im Puffer, an dem das nächste, eintreffende Zeichen gespeichert werden kann. Der zusätzliche Aufwand, einen Zeiger in einem Ringspeicher weiterzustellen, ist in dem Grundlagen: UnterprogrammeUnterprogramm INCRBC zusammengefasst. Zwei 'besondere' Zustände müssen beim Speichern erkannt werden: 1. Der Puffer ist noch leer. Dann muss der regelmäßige Alle noch folgenden Anschlüsse fallen unter die Rubrik STEUER- oder auch CONTROLBUS:: INT - InterruptInterrupt gestartet werden, der die Zeichen wieder aus dem Puffer ausliest und zum Drucker schickt. 2. Der Puffer ist voll. Dann muss die Routine warten, bis, via Alle noch folgenden Anschlüsse fallen unter die Rubrik STEUER- oder auch CONTROLBUS:: INT - InterruptInterrupt, ein Zeichen weggedruckt und ein Platz frei wird. Diese beiden Zustände sind gar nicht so leicht auseinanderzuhalten: Ist der Puffer leer, so zeigen ANF und ENDE auf die selbe Position. Zieht man sie voneinander ab, erhält man Real: NullNull (Auf diese Weise wird das im Programm getestet). Ist der Puffer ganz voll, so wurde ANF einmal durch den gesamten Ringspeicher rundgedreht und steht wieder an seinem alten Platz! Die einfachste Methode, dieses Problem zu umgehen, ist, den Puffer bereits per Definition als voll zu erklären, wenn de facto noch ein Platz frei ist. So wird das auch in diesem Programm gemacht. Um zu Erklärung der Anschlussbelegung: Testtesten, ob der Puffer leer ist, muss man ANF und ENDE vergleichen, bevor man ENDE weiterstellt. Um zu Erklärung der Anschlussbelegung: Testtesten, ob der Puffer voll ist, muss man ANF und ENDE vergleichen, nachdem man ENDE erhöht hat. War der Puffer vorher leer, so wird ein Die Speicherkonfiguration im Schneider CPC: BlockBlock auf dem Universal-Ticker eingehängt, der beim nächsten Ticker-Interrupt zum Aufruf von EVROUT führt. Der BCEF: KL INIT EVENT: EventblockEventblock ist normal, asynchron mit near address. Letzteres ist sinnvoll, da EVROUT im RAM liegt und zwei Routinen im unteren Erläuterung zu den Anschlüssen 40 bis 45: 42 - ROMEN (0)ROM aufrufen muss. Die 'Standard-' Einstellung beim Aufruf von Event-Routinen (unten Erläuterung zu den Anschlüssen 40 bis 45: 42 - ROMEN (0)ROM, oben unbekannt) ist also genau richtig. 'Asynchron' ist man vom Pollen des Basic-Interpreters unabhängig , andererseit aber nur 'normal', weil so EVROUT keine weiteren Hardware-Interrupts blockiert und MAIN FIRMWARE JUMPBLOCK: MACHINE PACK Ist der Puffer voll, so wird nicht nur gewartet, dass per Alle noch folgenden Anschlüsse fallen unter die Rubrik STEUER- oder auch CONTROLBUS:: INT - InterruptInterrupt ein Zeichen gedruckt wird, sondern ständig aktiv gekickt, weil: Jetzt pressiert's! EVROUT kann man aber nicht direkt aufrufen, weil dann ein zufälliger Parallel-Aufruf vom Ticker aus nicht auszuschließen ist! Wird das Der Kernel - Software-Interrupts: EventsEvent aber System-konform über KERNEL: BCF2: KL EVENTKL EVENT gekickt, so erhöht ein parallel erscheinender Kick vom Ticker nur das Zählbyte im BCEF: KL INIT EVENT: EventblockEventblock. EVROUT wird zwar einmal mehr, aber 'nacheinander' aufgerufen! EVROUT selbst ist leichter verständlich. Zunächst wird getestet, ob der Drucker überhaupt ein Zeichen empfangen kann, und nur dann weiter gemacht. Das Zeichen wird aus dem Puffer geholt und gedruckt. Der Zeiger ANF wird weitergestellt und abgespeichert. Dann wird getestet, ob der Puffer leer ist und in diesem Fall der BCEF: KL INIT EVENT: EventblockEventblock entschaerft (Count := -64 = &Der Zeichensatz des Schneider CPC: &C0 = 192C0) und der BCE9: KL ADD TICKER: TickerblockTickerblock ausgehängt. Wenn nicht, wird der Kickzähler in diesem Fall auf 20 erhöht! Die Eventroutine simuliert für sich selbst 20 weitere, angeblich in der Zwischenzeit eingegangene Kicks. Das führt dazu, dass sie sofort nach ihrem Abschluss mit RET vom MAIN FIRMWARE JUMPBLOCK: KERNEL (Wer den Verdacht hat, dass sein Drucker ein lahmerer Vertreter ist, kann den Sebstkick-Wert noch weiter erhöhen. Erkennbar wäre das daran, dass der Drucker zwischen dem Ausdruck zweier ganz normaler Textzeilen eine Pause von einer Sekunde oder länger einlegt.) Durch diesen Trick wird man pro Alle noch folgenden Anschlüsse fallen unter die Rubrik STEUER- oder auch CONTROLBUS:: INT - InterruptInterrupt nicht nur ein Zeichen, sondern gleich eine ganze Zeile los. 'Normale' CPC-Software-Spooler benutzen den Fast Ticker und senden (maximal) pro Sekunde 300 einzelne Zeichen. Speziell bei Grafiken umfasst eine Zeile aber mehr als 640 einzelne Datentypen: Bytes |