Das Schneider CPC Systembuch

Grundlagen

Datenspeicherung und Datenstrukturen

Queues: First in - First Out

Gerade umgekehrt wie Stacks funktionieren Queues, oder auf deutsch: Der Key Manager: WarteschlangeWarteschlangen. Zwar kann man auch hier nur an einem Ende des 'Stapels' Daten auflegen, und an einem Ende Daten abholen, nur ist das Ende mit der Datenausgabe jetzt auf der anderen Seite des Stapels. Man legt also oben auf und holt unten ab.

Damit hat man beim Auslesen nicht den Zugriff auf das jeweils zuletzt auf den Stapel gelegte Datum, sondern auf das erste. Daraus resultiert auch die andere, für Queues gebrauchte Bezeichnung, FIFO-Speicher: First In - First out. Oder auf deutsch: Zuerst hinein, zuerst heraus.

Das System kann man sich am besten an einer Der Key Manager: WarteschlangeWarteschlange vor einer Ladenkasse veranschaulichen: Der Kunde, der zuerst an die Kasse kommt, wird zuerst abgefertigt. In der Zwischenzeit können noch weitere Kunden eintreffen, die sich schön hinten anstellen und dann nach und nach weiter vor rücken, um so in der Reihenfolge bedient zu werden, in der sie eintrafen.

Wie beim Stack benötigt man zwei Markierungen für das vordere und das hintere Ende. Da sich aber an beiden Enden 'was tut, kann man hier nicht eine Grenze als weniger wichtig unter den Tisch fallen lassen, wie das beim Hardware-Stack der Die ICs im Überblick: Die CPU Z80
Das Innenleben der CPC-Rechner: Die CPU Z80
Die Anschlussbelegungen der wichtigsten ICs im CPC: Die CPU Z80
Z80
der Fall war.

Außerdem ist der maximale Platzbedarf für solche Puffer, wenn sie als Datenspeicherung und Datenstrukturen: ArraysArray realisiert werden, nicht immer sicher zu bestimmen. Meist kann er sogar unendlich groß sein: Beispielsweise bei Druckerpuffern, in denen Zeichen, die der Computer ausdrucken will, solange zwischengespeichert werden, bis der Drucker sie abarbeiten kann. Denn der Computer kann normalerweise mit fast unbeschränkter Geschwindigkeit Zeichen abschicken. Der Drucker aber, der mit seiner Mechanik zu kaempfen hat, ist immer bedeutend langsamer.

Im Gegensatz zu Stacks, wo meist derjenige, der einen Wert auf dem Stapel ablegt, diesen zu einem späteren Zeitpunkt auch wieder abholt, sind an Queues (Der Key Manager: WarteschlangeWarteschlangen!!) meist zwei Partner beteiligt: Einer der nachschiebt (Computer) und einer, der abholt (Drucker).

Während man bei Stacks also hoffentlich immer weiß, "Es ist Platz da für meinen Eintrag", muss man bei der Queue erst einmal nachschauen, ob der Puffer nicht vielleicht voll ist, und dann warten. Ebenso ist es aber möglich, der der Abholer einmal schneller ist. Während man beim Stack wieder ganz genau weiß, "Da ist noch ein Wert auf dem Stapel, den ich holen kann", muss man bei Queues erst einmal nachschauen und gegebenfalls warten.

Queues als FLR-Array

Auch hier sind die verketteten Trees: ListenListen im Vorteil, weil das Einfügen und Entfernen von Datenspeicherung und Datenstrukturen: RecordsRecords hier vom Prinzip her besonders einfach ist. Bei Datenspeicherung und Datenstrukturen: ArraysArrays müsste man jedesmal, wenn ein neues Element angehängt werden soll, die komplette Der Key Manager: WarteschlangeWarteschlange um eine Position verschieben, weil sich die Kette ja langsam durch den reservierten Speicherbereich frisst:

Im folgenden Beispiel wird der 5 Zeichen langen Puffer in Array-Form zunächst mit Operationen: BD5B / 349A / 349A: FLO SUBA bis LOW KERNEL JUMPBLOCK: 000E: LOW PCBC INSTRUCTION
LOW KERNEL JUMPBLOCK: 001E: LOW PCHL INSTRUCTION
E
gefüllt, dann zwei Zeichen abgeholt und dann ein weiteres Zeichen nachgeschoben:

 +---+    +---+     +---+    +---+    +---+    +---+    +---+    +---+    +---+
 |   |    |   |     |   |    |   |    |   |    | LOW KERNEL JUMPBLOCK: 000E:  LOW PCBC INSTRUCTION
LOW KERNEL JUMPBLOCK: 001E: LOW PCHL INSTRUCTION
E
| | LOW KERNEL JUMPBLOCK: 000E: LOW PCBC INSTRUCTION
LOW KERNEL JUMPBLOCK: 001E: LOW PCHL INSTRUCTION
E
| | LOW KERNEL JUMPBLOCK: 000E: LOW PCBC INSTRUCTION
LOW KERNEL JUMPBLOCK: 001E: LOW PCHL INSTRUCTION
E
| | F | | | | | | | | | | D | | D | | D | | D | | LOW KERNEL JUMPBLOCK: 000E: LOW PCBC INSTRUCTION
LOW KERNEL JUMPBLOCK: 001E: LOW PCHL INSTRUCTION
E
| | | | | | | | C | | C | | C | | C | | C | | D | | | | | | LOW KERNEL JUMPBLOCK: 000B: LOW KL LOW PCHL
LOW KERNEL JUMPBLOCK: 001B: LOW KL FAR PCHL
LOW KERNEL JUMPBLOCK: 003B: LOW EXT INTERRUPT
B
| | LOW KERNEL JUMPBLOCK: 000B: LOW KL LOW PCHL
LOW KERNEL JUMPBLOCK: 001B: LOW KL FAR PCHL
LOW KERNEL JUMPBLOCK: 003B: LOW EXT INTERRUPT
B
| | LOW KERNEL JUMPBLOCK: 000B: LOW KL LOW PCHL
LOW KERNEL JUMPBLOCK: 001B: LOW KL FAR PCHL
LOW KERNEL JUMPBLOCK: 003B: LOW EXT INTERRUPT
B
| | LOW KERNEL JUMPBLOCK: 000B: LOW KL LOW PCHL
LOW KERNEL JUMPBLOCK: 001B: LOW KL FAR PCHL
LOW KERNEL JUMPBLOCK: 003B: LOW EXT INTERRUPT
B
| | LOW KERNEL JUMPBLOCK: 000B: LOW KL LOW PCHL
LOW KERNEL JUMPBLOCK: 001B: LOW KL FAR PCHL
LOW KERNEL JUMPBLOCK: 003B: LOW EXT INTERRUPT
B
| | | | C | | | | Operationen: BD5B / 349A / 349A: FLO SUBA | | Operationen: BD5B / 349A / 349A: FLO SUBA | | Operationen: BD5B / 349A / 349A: FLO SUBA | | Operationen: BD5B / 349A / 349A: FLO SUBA | | Operationen: BD5B / 349A / 349A: FLO SUBA | | | | | | | +---+ +---+ +---+ +---+ +---+ +---+ +---+ +---+ +---+

Wie man sieht: Geht man nicht sowieso davon aus, dass der Queue-Inhalt immer an eine Puffergrenze angerückt wird, also dass im oberen Beispiel alle Zeichen soweit nach unten nachrutschen, bis sie alle unten aufsitzen, so muss man doch spätestens im letzten Fall den Queue-Inhalt im Puffer verschieben. Das ist aber sehr zeitintensiv.

Diesen prinzipiellen Nachteil der Datenspeicherung und Datenstrukturen: ArraysArrays bei der Bildung von Queues kann man aber umgehen, wenn man einen ringfoermigen Speicher benutzt. Da man im Computer aber meist beschränkte, lineare Adressraeume hat, muss man diese mit zusätzlichem Logik-Aufwand simulieren. Überschreitet einer der beiden Zeiger, die auf Ende und Anfang der Der Key Manager: WarteschlangeWarteschlange zeigen, den zur Verfügung gestellten Bereich, so macht er einfach am anderen Ende des Puffers weiter:

 +------------+    +------------+    +------------+    +------------+
 |Disketten   |    |      ten   |    |fwerk tenlau|    | werk       |
 +------------+    +------------+    +------------+    +------------+
  ^        ^              ^  ^             ^^            ^   ^
  Operationen: BD5B / 349A / 349A:  FLO SUBa        LOW KERNEL JUMPBLOCK: 000E:  LOW PCBC INSTRUCTION
LOW KERNEL JUMPBLOCK: 001E: LOW PCHL INSTRUCTION
e
Operationen: BD5B / 349A / 349A: FLO SUBa LOW KERNEL JUMPBLOCK: 000E: LOW PCBC INSTRUCTION
LOW KERNEL JUMPBLOCK: 001E: LOW PCHL INSTRUCTION
e
ea Operationen: BD5B / 349A / 349A: FLO SUBa LOW KERNEL JUMPBLOCK: 000E: LOW PCBC INSTRUCTION
LOW KERNEL JUMPBLOCK: 001E: LOW PCHL INSTRUCTION
e

Die folgenden Assembler-Routinen realisieren einen Wartespeicher für ASCII-Zeichen (oder Datentypen: Bytes
Datenbreite: Bytes
Bytes
. Also FLR) in einem Datenspeicherung und Datenstrukturen: ArraysArray, der als Ringspeicher organisiert ist. Zeichen werden immer auf der Position von QUEEND (Schlangenende) angefügt. Der Schlangenkopf wird durch QUEANF angezeigt.

Der zusätzliche Aufwand beim Weiterstellen eines Zeigers ist im Grundlagen: UnterprogrammeUnterprogramm 'VOR' zusammengefasst.

Die PUT- und GET-Routinen müssen zuerst Erklärung der Anschlussbelegung: Testtesten, ob der Speicher voll bzw. leer ist. In diesem Fall können sie nur unverrichteter Dinge wieder zurückkehren. Das Problem ist aber zu erkennen, ob der Puffer nun voll oder leer ist:

Ist kein einziges Zeichen im Puffer, haben QUEANF und QUEEND den selben Wert. Wird der Puffer aber vollständig gefüllt, wird QUEEND genau einmal im Kreis weitergestellt und steht wieder auf seiner alten Position. Mithin lässt sich der Zustand 'ganz voll' und 'ganz leer' nicht so einfach unterscheiden.

Als Ausweg bietet sich an, den Puffer eben nie ganz zu füllen. Mindestens ein Zeichen muss frei bleiben. Der Zustand 'Puffer voll' wird also per Definition bereits erreicht, wenn noch ein Datentypen: Bytes
Datenbreite: Bytes
Byte
im Puffer unbenutzt ist.

Der Erklärung der Anschlussbelegung: TestTest auf 'Puffer voll' lässt sich also realisieren, indem QUEEND testweise erhöht und dann mit QUEANF auf Gleichheit verglichen wird. Ein leerer Puffer ist daran erkennbar, dass diese beiden Zeiger gleich sind, bevor man QUEEND erhöht.

BUFANF: DEFW #0000        ; Speicher für Bufferstartadresse
BUFEND: DEFW #0000        ; Speicher für Bufferende + 1 (Zeiger hinter Buffer)
;
QUEANF: DEFW #0000        ; Speicher für Zeiger auf erstes Zeichen der Queue
QUEEND: DEFW #0000        ; Speicher für Zeiger auf Queueende + 1
;                         ;                    (Zeiger auf ersten freien Platz)
;
; Unterprog.: Stelle DE innerhalb des Puffers weiter
;
VOR:    INC  DE           ; Stelle DE weiter
        LD   HL,(BUFEND)
        AND  Operationen: BD5B / 349A / 349A:  FLO SUBA            ; CY := 0
        SBC  HL,DE        ; Hat DE das Bufferende erreicht?
        RET  NZ           ; nein
        LD   DE,(BUFANF)  ; sonst DE auf Bufferanfang einstellen
        RET
;
PUT:    LD   DE,(QUEEND)  ; Zeiger auf 1. freien Platz am Ende der Schlange
        LD   (DE),Operationen: BD5B / 349A / 349A:  FLO SUBA       ; Zeichen Abspeichern
        Maschinencode über HIMEM: CALLCALL VOR          ; Zeiger weiterstellen
        LD   HL,(QUEANF)
        SBC  HL,DE        ; mit Zeiger auf Schlangenkopf vergleichen
        RET  Z            ; Puffer voll! QUEEND nicht weiterstellen. Das Zeichen
;                         ; wurde in dem Datentypen: Bytes
Datenbreite: Bytes
Byte
abgespeichert, das immer frei ; ; bleiben muss. Das Zeichen geht verloren. CY = 0. LD (QUEEND),DE ; o.k.: QUEEND weiterstellen. SCF ; Erfolg im CY-Flag anmerken. RET ; GET LD DE,(QUEANF) ; Zeiger auf Schlangenkopf LD HL,(QUEEND) ; Zeiger auf Schlangenende AND Operationen: BD5B / 349A / 349A: FLO SUBA ; CY := 0 SBC HL,DE ; vergleichen RET Z ; Puffer leer. CY = 0. LD Operationen: BD5B / 349A / 349A: FLO SUBA,(DE) ; sonst Zeichen vom Schlangenkopf nach Operationen: BD5B / 349A / 349A: FLO SUBA laden Maschinencode über HIMEM: CALLCALL VOR ; und den Schlangenkopf um eine Position LD (QUEANF),DE ; weiterstellen (nach hinten rücken). SCF ; Erfolg im CY-Flag anmerken. RET

Eine solcher Ringpuffer ist im Tastatur-Manager enthalten und 20 Tastendrücke lang. Auf der einen Seite kann sich das laufende Programm die Zeichen abholen, auf der anderen Seite schiebt der Anwender durch seine Aktivitaeten auf der Tastatur ständig Zeichen nach. Auch wenn das Programm kurzzeitig keine weiteren Zeichen abholen kann (weil es beispielsweise gerade einen besonders komplizierten Befehl abarbeitet), kann man noch eine Weile blind schreiben, ohne dass die Zeichen verloren gehen. Eine Warnung, wann der Puffer voll ist, erhält man allerdings nicht (manche andere Computer piepsen hier beispielsweise).

Ebenfalls nach dieser Methode sind die drei Puffer, die Die Abteilungen des Betriebssystems: Der Sound Managerder Sound Manager für jeden Tonkanal bereithält, aufgebaut. Diese sind jeweils vier Töne lang, wovon der erste Ton normalerweise gerade gespielt wird.

Queues als verkettete Liste

Verkettete Trees: ListenListen sind nicht nur besonders für Stacks, sondern auch für Der Key Manager: WarteschlangeWarteschlangen geeignet, weil hier ebenfalls nur an je einem Ende gelesen und geschrieben wird. Da sich bei Queues aber an beiden Enden etwas tut, benötigt man zwei Pointer. Einer zeigt auf den ersten und einer auf den letzten Datenspeicherung und Datenstrukturen: RecordsRecord der Der Key Manager: WarteschlangeWarteschlange.

Es wird aber trotzdem nur eine Hangelkette zwischen den einzelnen Datenspeicherung und Datenstrukturen: RecordsRecords benötigt, die vom letzten, sprich ältesten Element rückwärts bis zum Schlangenkopf führt:

(Schreibseite)                                      (Leseseite)
  ANFANG -------------+                                 ENDE ------------+
                      |                                                  |
                   <--+                                                  |
  NIL <--- Pointer <------- Pointer <------- Pointer <------ Pointer <---+
           Record0          Record1          Record2         Record3

Die im MAIN FIRMWARE JUMPBLOCK: KERNEL
Die Firmware des Schneider CPC: KERNEL
Kernel
verwendete Der Key Manager: WarteschlangeWarteschlange für die synchronous Der Kernel - Software-Interrupts: Eventsevent pending queue ist eine verkettete Der Key Manager: WarteschlangeWarteschlange. Sie ist aber etwas anders organisiert, weil hier die gekickten BCEF: KL INIT EVENT: EventblockEventblocks entsprechend ihrer Priorität eingereiht werden.

Die folgenden Assembler-Routinen realisieren eine nach obigem Muster 'verkabelte' Der Key Manager: WarteschlangeWarteschlange. Wie beim Stack sind diese Routinen wieder universell sowohl für FLR als auch VL-Records anwendbar. Die Datenspeicherung und Datenstrukturen: RecordsRecords werden ja nicht in einen reservierten Speicherbereich kopiert, sondern nur die Pointer verstellt.

Guenstig bei Der Key Manager: WarteschlangeWarteschlangen in 'Ketten-Technik' ist, dass man beim Nachschieben von Datensätzen nie abgewiesen werden kann, weil der Puffer voll ist. Diese Routine hängt ja nur einen bereits bestehenden Datenblock in die Kette ein. Es kann nur sein, dass irgendwann das gesamte System zusammenbricht, weil kein freier Speicher mehr vorhanden ist. Soll eine solche Der Key Manager: WarteschlangeWarteschlange tatsächlich als Puffer zwischen zwei asynchron arbeitenden Einheiten dienen, ist es bestimmt sinnvoll, einen Zähler zu installieren, mit dem die in der Queue eingereihten Datenspeicherung und Datenstrukturen: RecordsRecords auf eine bestimmte Hoechstzahl begrenzt werden.

ANFANG: DEFW #0000         ; Zeiger auf ersten (neuesten) Datenspeicherung und Datenstrukturen: RecordsRecord. Schreibseite.
ENDE:   DEFW #0000         ; Zeiger auf letzten (ältesten) Datenspeicherung und Datenstrukturen: RecordsRecord. Leseseite.
;
;
; Schiebe einen Datenspeicherung und Datenstrukturen: RecordsRecord nach. Eingabe: HL zeigt auf LSB des Pointers.
;
PUT:    EX   DE,HL         ; Pointer auf neuen Datenspeicherung und Datenstrukturen: RecordsRecord nach DE tauschen.
        LD   HL,(ANFANG)   ; Pointer auf den bisher letzten Datenspeicherung und Datenstrukturen: RecordsRecord nach HL.
;
        LD   (HL),LOW KERNEL JUMPBLOCK: 000E:  LOW PCBC INSTRUCTION
LOW KERNEL JUMPBLOCK: 001E: LOW PCHL INSTRUCTION
E
; In den Pointer des bisher letzten Datenspeicherung und Datenstrukturen: RecordsRecords INC HL ; die Adresse des neuen Datenspeicherung und Datenstrukturen: RecordsRecords eintragen. LD (HL),D ; ; EX DE,HL ; Pointer auf neuen Datenspeicherung und Datenstrukturen: RecordsRecord wieder nach HL tauschen LD (ANFANG),HL ; und in den Anfangszeiger eintragen. ; PUT1: LD (HL),0 ; Pointer des neuen Datenspeicherung und Datenstrukturen: RecordsRecords auf NIL einstellen. INC HL ; LD (HL),0 ; RET ; ; Hole einen Record aus der Queue. Ausgabe: HL zeigt auf LSB des Pointers. ; GET: LD HL,(ENDE) ; HL := Pointer auf letzten Datenspeicherung und Datenstrukturen: RecordsRecord. LD Operationen: BD5B / 349A / 349A: FLO SUBA,H OR L RET Z ; zurück, wenn Queue leer. ; LD LOW KERNEL JUMPBLOCK: 000E: LOW PCBC INSTRUCTION
LOW KERNEL JUMPBLOCK: 001E: LOW PCHL INSTRUCTION
E
,(HL) ; Pointer auf bisher vorletzten Datenspeicherung und Datenstrukturen: RecordsRecord nach DE holen INC HL ; LD D,(HL) ; DEC HL ; ; LD (ENDE),DE ; und als neuen letzten Datenspeicherung und Datenstrukturen: RecordsRecord eintragen ; LD Operationen: BD5B / 349A / 349A: FLO SUBA,D ; Queue jetzt leer? OR LOW KERNEL JUMPBLOCK: 000E: LOW PCBC INSTRUCTION
LOW KERNEL JUMPBLOCK: 001E: LOW PCHL INSTRUCTION
E
SCF ; Erfolg anmerken. RET NZ ; Nur zurück, wenn die Queue jetzt nicht leer ist! ; ; Initialisieren ; INITQU: LD DE,ENDE ; Adresse des Lesezeigers in den Schreibzeiger LD (ANFANG),DE ; eintragen (siehe Queues als verkettete Liste: Anmerkung:Anmerkung). ; LD DE,#0000 ; NIL in den Lesezeiger eintragen LD (ENDE),DE RET ; Queue ist leer.
Anmerkung:

Schwierig an der Behandlung der verketteten Queue ist, dass der Schreibpointer ANFANG immer auf den ersten, jüngsten Datenspeicherung und Datenstrukturen: RecordsRecord zeigen muss. Ist die Queue aber leer, existiert dieser nicht.

Eine leere Der Key Manager: WarteschlangeWarteschlange muss deshalb drei zusätzliche Bedingungen erfüllen:

Erstens muss der Lesepointer ENDE auf NIL zeigen, damit kein weiterer Lesezugriff erfolgen kann. Zweitens darf ANFANG nicht irgendwo in die Pampa zeigen, da die hiermit adressierten Speicherzellen mit der Adresse des Datenspeicherung und Datenstrukturen: RecordsRecords beschrieben werden, wenn der nächste (also erste) Datenspeicherung und Datenstrukturen: RecordsRecord eingetragen wird.

Drittens muss beim nächsten Nachschieben die Adresse des Datenspeicherung und Datenstrukturen: RecordsRecords auch in den Lesepointer ENDE eingetragen werden, weil der Datenspeicherung und Datenstrukturen: RecordsRecord als einziges Element in der Queue nicht nur der jüngste, sondern auch gleichzeitig der neue älteste wird. Die beiden letzten Fliegen werden mit einer Kappe geschlagen, und bei leerem Puffer den Schreibzeiger ANFANG auf ENDE eingestellt.

Valid HTML   Valid CSS