Schlagwort-Liste: 8086 16-Bit-Rechner - Programme: Assembler, Beispiele, Verzögerungsschleife, Unterprogramm, FAR- und NEAR-UP

Assembler-Beispiel 4


Ausgabe "Hello World" 10 mal auf den Bildschirm - verzögert

Nehmen wir einmal an, wir hätten ein großes Programm zu schreiben und in diesem brauchen wir an mehreren Stellen Verzögerungen. Dann ist es sicher sinnvoll nicht jedesmal diese Schleifen zu schreiben, sondern sich einfach einen Programmblock zu schaffen, der die Verzögerung realisiert und der von allen notwendigen Stellen aus genutzt werden kann.
Das genau leistet die Programmstruktur Unterprogramm!

    Das Bild links soll das Prinzip verdeutlichen.
Im Hauptprogramm (das aufrufende Programm) wird an der Adresse i der Befehl zum Start des Unterprogramms (gerufenes Programm) ab Adresse j gefunden. In gleicher Weise würde auch von Adresse k das Unterprogramm ab Adresse j gestartet werden.
Die Bearbeitung wird somit ab Adresse j fortgesetzt. Irgendwann wird das Ende des Unterprogramms, die Adresse je erreicht. Nun gibt es ein Problem -
woher weiß das Unterprogramm an welcher Stelle das Hauptprogramm fortgesetzt werden soll, an Adresse i+1 oder k+1?

    Das Problem läßt sich relativ einfach mit dem Stack lösen!

Der Befehl an Adresse i bzw. k ist bekannt und man kann sofort die Adresse des nächsten Befehls berechnen (i+1 bzw k+1). Je nach Art des Unterprogramms wird nun diese Adress durch den Befehl in den Stack gespeichert und der Stack auch entsprechend verändert, bzw. es wird auch noch CS gespeichert (es werden 2 oder 4 Byte in den Stack gespeichert). Der Befehlszähler (IP) wird mit dem Wert, der im Befehl steht geladen.
Kommt das Unterprogramm an seinen Ende-Befehl, wird aus dem Stack die gespeicherte Adresse geholt und in den Befehlszähler (IP) gespeichert - schon setzt das Programm an dem Befehl nach dem Unterprogramm-Aufruf fort.
Aber nun wird noch einmal ganz deutlich, dass jegliche Änderung des SP erhebliche Konsequenzen haben kann.
Und das Unterprogramm braucht einen speziellen Ende-Befehl, weil der Stack gelesen und der Zeiger SP verändert werden muss.

Der Aufruf eines weiteren UP aus einem UP heraus ist so nun auch ohne weiteres möglich.

    Noch ein paar grundsetzliche Bemerkungen zur Unterprogramm-Nutzung.
Unterprogramme können gut wiederkehrende Aufgaben erledigen. Man sollte dabei jedoch bedenken, dass die ordnungsgemäße Weiterarbeit des Hauptprogramms aber nur dann funktioniert, wenn auch alle Register im Prozessor wieder die alten Werte haben - das organisiert das Unterprogramm nicht automatisch - dafür ist der Programmierer selbst zuständig!

Und so kann es funktionieren.
Alle Register, die man im UP benutzt werden am Anfang des UPs auf den Stack gerettet, dann beginnt man mit dem eigentlichen Ablauf des UPs.
Bevor man dann den Ende-Befehl im UP schreibt, holt man alle Werte aus dem Stack zurück. Nun kann der Prozessor ordnungsgemäß das HP weiter bearbeiten. Das Retten und Rückholen der Werte erfolgt wieder mit den PUSH und POP-Befehlen.
Solche Aktionen kann man aber auch im HP vor Aufruf des UP durchführen, es gibt bei beiden Varianten Vor- und Nachteile, man sollte sich jedoch auf eine Variante festlegen!

    Soviel zur Theorie der Unterprogramme

Nun zur praktischen Realisierung mit dem 8086-Assembler. Im Assembler werden sie allgemein als "Prozeduren (Procedure)" bezeichnet und es gibt zwei Typen:

  • NEAR-Procedure - Unterprogramme im gleichen Segment wie das Hauptprogramm
  • FAR-Procedure - Unterprogramme in einem anderen Segment

Im Bild links wird deutlich, dass mit dem Typ FAR nun die Möglichkeit besteht, den gesamten Speicher zu füllen. Es müssen nun aber zwei völlig getrennte Programme (oder auch mehr) bearbeitet und übersetzt werden, erst der Linker schafft die Verbindung.
Zur fehlerfreien Arbeit des Assemblers muss ihm irgendwie mitgeteilt werden, dass das UP später folgt, die Kommunikation über die Segmentgrenzen hinaus muss organisiert werden.
Das fällt natürlich beim Typ NEAR weg.

Wir wollen mit dem NEAR-UP beginnen. Es bedarf nun natürlich einer besonderen Bezeichnung, damit der Assembler den Bereich des UP auch findet:

Prozedur-Name    PROC NEAR
    Befehl 1
    ...
    Befehl n
Prozedur-Name    ENDP

Der Aufruf des UPs aus dem dem HP erfolgt mit dem CALL-Befehl:
CALL Prozedur-Name

    Und so sieht das Programm dazu aus!

Wie schon bekannt werden zuerst die Assembler-Direktiven ".MODEL SMALL", ".STACK 256", das ".DATA"-Segment mit dem Text und dann das ".CODE"-Sement für HP und UP vereinbart.
Aus alter Erfahrung bei der Programmierung in einer höheren Programmiersprache heraus, wird zuerst mit dem UP begonnen.
   warten PROC NEAR
Das UP hat somit den Namen "warten" erhalten.
Da wir in der Folge mit dem "LOOP"-Befehl arbeiten wollen, brauchen wir das Register CX, also retten wir es mal. Bevor dann die innere Schleife beginnt, wird CX der äußeren Schleife ebenfalls gerettet. Man beachte, dass die innere Schleife nur aus dem "LOOP"-Befehl besteht.
Nach Durchlauf der Schleifen und dem Rückholen des CX-Wertes beenden wir das UP.
Da muss man aufpassen, denn mit
   warten ENDP
wird nur dem Assembler mitgeteilt, dass hier das UP zu Ende ist, für Abarbeitung hat der Befehl keine Wirkung, da ist ganz wichtig der Befehl:
   ret
der ist für die Organisation des Stack beim Rücksprung zum HP notwendig!

Dann folgt mit der Marke "start:" das HP.
Nach der Textausgabe kommt dann der Befehl zum Aufruf des UP:
   CALL warten
Der weitere Verlauf des Programms ist uns schon bekannt!


   

Nun zur 2.Variante der Unterprogramme, dem
   FAR Unterprogramm

Links ist das Programm dargestellt und eigentlich ist der Unterschied zum NEAR-Unterprogramm gar nicht so groß. Es beginnt wieder mit
   warten PROC FAR
lediglich NEAR wurde durch FAR ersetzt und es endet wieder mit:
   warten ENDP

Der Aufruf im Hauptprogramm ist mit:
   CALL warten
völlig identisch!

Der entscheidende Unterschied ist, dass sowohl das Haupt- als auch das Unterprogramm völlig eigenständige Programme sind, eigene Modellvereinbarung, eigene Segmente!
Sie werden mit eigenem Programmnamen einzel behandelt.
Davor die jeweilige Zeile ist neu und für den "Assembler" und "Linker" notwendig. Im Hauptprogramm wird durch die Zeile:
   EXTRN warten:FAR
(EXTRN ist so richtig geschrieben) dem Assembler mitgeteilt, dass diese "Marke" hier nicht zu finden ist und der Linker sucht nach dem Symbol in den anderen Programmen und ermittelt eine Adresse im Speicher.
Das Unterprogramm enthält die Zeile:
   PUBLIC warten
Das Symbol (Marke) "warten" wird als öffentlich erklärt, der Linker sucht danach und darf die Verbindung herstellen.
alles andere ist mit den bekannten Programmen identisch. Beim Unterprogramm darf man nicht vergessen, dass es nun 3 Endekennungen hat:
   ret
   ENDP
   END
ret: organisiert den STACK beim Rücksprung; ENDP: ist die Endekennung des Unterprogramms für den Assembler; END: ist das Ende der Assembler-Direktiven!

Richtig ist nun, dass wir mit zwei Programmen arbeiten müssen, diese müssen jeweils editiert und assembliert werden. Danach muss vom Hauptprogramm (Objektdatei) aus die Objektdatei vom Unterprogramm dazu gelinkt werden -

hier kommt nun unsere Bedienoberfläche wieder ins Spiel!

    Hier wurde in bekannter Weise das 1.Programm "H10UE41.asm", das Hauptprogramm, vereinbart und bearbeitet.
Hinter Quelle1 steht (Basis), das bedeutet, dass zu diesem Programm andere dazu gelinkt werden. Das Programm wurde editiert und mit <a> assembliert. Damit entsteht die Objektdatei "H10UE41.obj". Die Datei "H10UE41.asm" wird gleichfalls dem System eigenen Editor zugeordnet.

    Im nächsten Schritt wird nun mit <z> die zweite Quelle "H10UE42.asm" festgelegt und mit <2> übersetzt. Es entsteht als Link-Datei die Objektdatei "H10UE42.obj"

    Nun kann der Linker mit <l> gestartet werden.
Intern kommt folgender Befehl zustande:
   TLINK H10UE41.obj H10UE42.obj
Das Ergebnis wird sichtbar, da als Start-Datei nun "H10UE41.exe" eingtragen wird. Mit <r> kann nun das Programm gestartet werden.

Mit <d> könnte man für ein anderes Projekt die gesamte Liste aller zu linkenden Dateien festlegen (sie wird durch Leerzeichen getrennt und die erste Datei ist die Basis).
Wechselt man mit <w> die Editor-Datei, werden die Bezüge nicht geändert, "H10UE41.asm" bleibt Basis-Datei und wird mit <a> übersetzt, die andere mit <2>.


    . . .
    Links ist der Hex-Dump vom erzeugten Maschinen-Programm zu sehen.

Das Programm (h10ue41) wird von Adresse 00200h bis 0021Hh in den Speicher geladen, das UP (h10ue42) von 00220h bis 0022Eh und die Daten (Text) von 00230h bis 0023Eh.

Es funktioniert genau wie das Programm im Beispiel 3!

Im Beispiel 5 sollen kurz Makros, eine weitere Leistung des Assemblers, beschrieben werden.

zurück zur Start-Seite (Beispiele)   /   weiter Beispiel 5
zurück zur Start-Seite