Pentium II/III, Befehlstypen, Datentypen und Adressierungsarten
Einführung
Um eine Kompatibilität bei Hard- und Software zu gewährleisten sollte
bei der Entwicklung eines neuen Prozessors, der Befehlsatz der vorherigen übernommen
werden. Die Einführung neuer Befehle gestaltet sich dann für alte
Programme unkritisch. Die Prozessoren der Intel-x86-Familie sind aufwärtskompatibel.
Ein Programm, das auf einem 8086er lauffähig ist, funktioniert auch auf
einen Pentium II/III.
Die Implementierung von Befehlen in Hardware oder Mikroprogramm ist abhängig
von den Herstellungskosten und der Leistung, bzw. Nützlichkeit der Befehle.
Ein oft genutzter Befehl bringt bei festverdrahteter Logik mehr Leistung, erhöht
aber die Herstellungskosten.
Ein grosser Befehlssatz ermöglicht eine leichte Programmierung des Prozessors,
kann aber zu komplizierten Kontrollstrukturen durch eine hohe Komplexität
der Schaltkreise führen. Ein komplexer Befehl kann bei der Abarbeitung
durch den Prozessor mehr Zyklen benötigen, als eine programmierte Folge
von einfachen Befehlen.
Befehle können zu Klassen gruppiert werden:
Erläuterungen zu einigen Befehlstypen
Internes Befehlsformat
Das Befehlsformat der Pentium II/III – Prozessoren ist als die unterste
Stufe der Befehlsdarstellung anzusehen, da dies die Sprache ist, die die CPU
direkt steuert. Dargestellt wird sie logischerweise im binären Format,
mit Nullen und Einsen. Obwohl die Leistung der CPU‘s stetig steigt, ist
die Kodierung der Befehle in der Intel Architektur seit dem 8086 unverändert
geblieben.
Im vorangegangenen Abschnitt wurde ein kleiner Einblick in die Vielfalt der
Befehlstypen gewährt und es wurde deutlich, dass jeder Befehl ein für
ihn typisches „Aussehen“ hat. So gibt es z.B. Befehle, die Operanden
oder Adressen zur Ausführung benötigen. Andere kommen gänzlich
ohne weitere Parameter aus. Da manche Befehle unterschiedliche Operationen ausführen
können, ist es notwendig, dem Prozessor mitzuteilen, ob nun z.B. ein Register
oder eine Speicherzelle adressiert wird, somit ist es zwingend erforderlich,
dem Befehl selbst noch Angaben über Operandenadressen und dergleichen mitzuteilen.
Im Instruction Format sind der Befehl und die notwendigen Parameter kodiert.

Anhand der Abbildung soll nun die Kodierung verdeutlicht werden. Einem Befehl
können bis zu vier ein Byte lange Präfixe vorangestellt sein. Sie
sind optional und müssen, falls erwünscht, direkt auf Programmebene
angegeben werden. Mit Hilfe dieser Präfixe lassen sich einige Funktionen
sehr viel einfacher verwirklichen. Als Beispiel sei hier die Ergänzung
„REP“ vorgestellt. Es kann nur in Verbindung mit Stringinstruktionen
angewandt werden, und bewirkt eine Wiederholung des folgenden Befehls, so lange
wie das ECX-Register, welches bei jeder Ausführung dekrementiert wird,
den Wert ungleich Null hat.
Insgesamt gibt es vier verschiedene Gruppen von Präfixen. Diese seinen
nur am Rande erwähnt, ohne auf diese näher einzugehen:
Für jede Gruppe gibt es genau ein Byte, somit können manchen Befehle
mehrere Präfixe zugeordnet werden, wobei diese niemals aus der gleichen
Gruppe kommen können und die Stimmigkeit der damit entstehenden Aussage
gegeben sein muss.
Die nächsten ein bzw. zwei Bytes der Instruktion beinhalten den eigentlichen
Opcode (Operation Code). In seltenen Fällen reicht der Platz von 16 Bit
nicht aus. Dann werden 3 weitere Bits des folgenden Bytes zur Darstellung hinzugezogen.
Seine Länge hängt im wesentlichen von der Befehlsstruktur ab, also
ob noch Operanden, Adressen, Register oder ähnliches in der Instruktion
folgen werden. Der Prozessor kann erst nach der Dekodierung dieser Bytes erkennen
wie die folgenden Bytes zu interpretieren sind, und wo der Befehl endet bzw.
der nächste beginnt.
Verlangt ein Befehl die Angabe einer Adresse oder eines Registers, wird dies
im dritten möglichen Byte abgelegt. Das ModR/M-Byte ist dreigeteilt in
Mod, Reg/Opcode und R/M. Wie schon erwähnt, sind die Bits 3 bis 5 (Reg/Opcode)
für die zusätzliche Opcode-Kodierung reserviert. Wird Mod mit R/M
kombiniert, stehen zusammen 5 Bits (Bit 0-2 und Bit 6/7) zur Verfügung.
Es können in diesem Fall 32 verschiedene Zustände kodiert werden.
Sie stehen für die 8 Register und 24 mögliche Adressierungsmodi. Wird
das R/M-Feld allein genutzt, spezifiziert es ein Register als Operanden.

Einige Kodierungen des ModR/M-Bytes verlangen zusätzlich das SIB-Feld.
Es ist dann notwendig, wenn im ModR/M-Byte ein Adressierungsmodus gewählt
wurde. SIB steht für Scale Base Index und ermöglicht die Angabe notwendiger
Parameter, die für die erweiterte Adressierung von Bedeutung sind. Unterteilt
ist das SIB in das zwei Bit lange Scale-Feld, das Index- und das Base-Feld,
beide jeweils drei Bit gross. Auf die speziellen Adressierungsarten, die hiermit
ermöglicht werden, soll später im Abschnitt Operandenadressierung
näher eingegangen werden.
Moderne Betriebssysteme teilen den logischen Adressraum in Segmente auf, um
dann einen virtuellen Speicher zu realisieren. Da die Abbildung des logischen
Speichers auf den physikalischen nicht übereinstimmt, zeigen feste Adressangaben
nie auf die gewünschte Stelle. Um dies zu umgehen, kann noch ein Offset
mit angegeben werden. Dieses Offset, auch Addressdisplacement, gibt die Adresse
innerhalb des Segmentes an. Bis zu vier Byte lang kann die Angabe des Displacement
werden.
Handelt es sich um Befehle, die vom Programm vorgegebene absolute Werte behandeln,
werden diese in Form der Immediate Data in dem letzten bis zu 4 Byte grossen
Feld abgelegt.

Obenstehende Abbildung zeigt noch weitere Eigenschaften, die die Funktion der Befehle beeinflussen können. Werden sie von einem Befehl unterstützt, sind sie zusammen mit dem eigentlichen Befehl im bis zu 19 Bit langen Opcode-Feld kodiert.
Datentypen
In der INTEL-Architektur gibt es fundamentale Datentypen mit denen sich jede
noch so komplexe Datenstruktur darstellen lässt.
Zu den fundamentale Datentypen gehören das Byte. Auf diesem basieren sämtliche
Typen, es ist somit auch die kleinste adressierbare Einheit. Kombiniert man
in Form von Verdopplung spricht man von Wort, Doublewort und Quadwort. Das Byte
sind acht aufeinanderfolgenden Bits und kann an jeder logischen Adresse stehen.
Die Bits sind durchnumeriert von 0 bis 7. Das Bit 0 ist das LSB (niederwertigste
Bit).
Ein Wort besteht aus zwei Bytes (16 Bits), ein Doublewort aus vier Bytes (32
Bits) und ein Quadwort aus acht Bytes (64 Bits). Jedem Byte in einem Wort ist
eine eigene Adresse zugeordnet. Das Byte mit dem Bit 0 ist das low Byte und
dessen Adresse die Anfangsadresse des Wortes im Speicher. Erwähnenswert
ist noch ein im Pentium II/III-Prozessor neuer Floatingpoint Datentyp, er ist
aus vier Doublewords zusammengesetzt und erreicht somit eine Grösse von
immerhin 128 Bit.


Anordnung im Speicher
Die Lage eines Wortes (Doublewort / Quadwort) im Speicher ist nicht gebunden an eine gerade oder ungerade Adresse, dadurch kann durch „geschickte“ Anordnung der Speicherplatz optimal ausgenutzt werden.

Um jedoch den Speicherzugriff zu optimieren, sollten word Operanden an durch vier Byte teilbaren ganzzahligen Adressen beginnen. Doubleword Operanden an 8 Byte teilbaren Adressen, da dann bei einem 32-Bit-Bus-Zugriff nur ein Lese-Zyklus benötigt wird. Befinden sich Teile des Operanden ausserhalb der gelesenen 32-Bit, ist ein weiterer Speicherzugriff nötig.
Numerische Typen, Pointer, Bitfelder und Strings

Operandenadressierung
Im Instruction Set sind viele Befehle vorhanden, die mit Operanden arbeiten. Dem Prozessor muss nun mitgeteilt werden, wo er die Operanden finden kann. Es gibt nun mehrere Möglichkeiten, zum einen kann er als Wert direkt im Befehl mit kodiert sein, oder der Befehl verweist auf ein Register, in dem die für die Operation notwendige Information abgelegt ist. Im Abschnitt Instruction Format, wurde erklärt, wie der Befehl kodiert sein muss, damit er seinen Operanden findet. Hier sollen nun die unterschiedlichen Adressierungsarten dargelegt werden.


Die zweite Gruppe bilden die Segmentregister. Das Memory Mangement sieht Segmente
vor, in denen Daten und Programmteile stehen. Insgesamt sechs Segmente können
mit Hilfe der Register verwaltet werden. In der Abbildung sind die sechs Register
und ihre Funktionen zusammengefasst. Jedes Register beinhaltet einen Pointer,
der sich auf jeweils ein Segment bezieht. DS, ES, FS und GS haben praktisch
die gleiche Funktion, in ihnen werden Pointer auf Datensegmente gespeichert.
In SS liegt die Adresse des Stacks und in CS die des Segmentes in dem der Programmcode
liegt.
Die letzte Gruppe bilden die Status- und Kontrollregister. Sie geben Auskunft
über den aktuellen Zustand der Prozessors und ermöglichen die Steuerung
bestimmter Funktionen. Sobald z.B. bei einer mathematischen Operation das Ergebnis
Null ist, wird das Zero Flag (ZF) gesetzt. Kommt es bei einer Addition zu einem
Übertrag, wird das Carry Flag (CF) aktiviert. Desweiteren können hier
auch die Interrupts gesperrt werden.

Nach dieser kurzen Übersicht soll nun die eigentliche Registeradressierung
beschrieben werden.
Man unterscheidet zwischen expliziter und impliziter Registeradressierung. Im
Fall Register werden sowohl für die einzelnen Operanden als auch für
das Ergebnis Register angegeben. Ist ein Befehl implizit, ist die Angabe bestimmter
Register, meistens für das Ergebnis nur indirekt möglich. Das folgende
Beispiel verdeutlicht das ganze.
In der Regel sind die Operanden dynamische Werte und können somit nicht
im Quellcode implementiert sein. Statt dessen werden sie in den Standardregistern
(32 Bit: EAX, EBX, ... 16 Bit: AL, AH, BL, ...) abgelegt. Genauso ist es mit
den Segmentregistern, sie erhalten bei jedem Prozesswechsel des Prozessors die
aktuellen Parameter der zu bearbeitenden Segmente.
Da es im Grunde genommen (fast) egal ist, welches Register zum Zwischenspeichern
von Operanden verwendet wird, ist es notwendig dem Befehl das entsprechende
Register mitzuteilen. Der MOV-Befehl z.B. ist nicht an ein einziges Register
gebunden, in das er den Operanden schreiben soll. Mit dem Lesen verhält
es sich genauso, Quelle und/oder Ziel kann fast jedes Register sein. Wobei selbstverständlich
auch hier Einschränkungen vorhanden sind. Es wäre fatal, wenn man
mit MOV Daten in das Statusregister schieben könnte. Um einige Registerinhalte
zu verändern gibt es spezielle Befehle, die unter anderem unter Bitmanipulation.
Besonders die arithmetischen Operationen verlangen grosse Datenworte um eine
möglichst hohe Genauigkeit zu erreichen. Leider sind die Register maximal
32 Bit lang. Will man nun aber eine Division (DIV) oder Multiplikation (MUL)
mit quadwords (64 Bit) durchführen, muss man sich eines Tricks behelfen.
Es gibt die Möglichkeit, Register zu paaren. Aus EAX und EDX wird durch
folgende Anweisung EDX:EAX ist 64 Bit-Register. Die Loworderbits werden in EAX
gespeichert und die Highorderbits in EDX. Eine solch geartete Operation, sollte
dann so aussehen: MUL EDX:EAX, 5. Hier wird dann der 64 Bit-Wert mit 5 multipliziert,
das Ergebnis liegt dann, wie gewohnt, in EDX und EAX.
Memory Operand
Im vorherigen Abschnitt wurden Register adressiert, geht man einen Schritt
weiter, kommt die Speicheradressierung. Sie unterscheidet sich zwar wesentlich
vom vorherigen, macht aber einen ähnlichen Ansatz. Die Operanden sind wieder
dynamisch und ändern sich zur Laufzeit. Mit dem grossen Unterschied, dass
diese nicht in Registern, sondern im Hauptspeicher abgelegt werden.
Einen grossen Nachteil hat dies natürlich. Die Zugriffszeit auf den Hauptspeicher
ist im Vergleich zum Register ein Vielfaches höher. Doch leider sind diese
äusserst schnellen Register nur begrenzt verfügbar. Verständlicherweise
haben die Register auch eine ganz andere Aufgabe. Sie dienen dem schnellen Zugriff
auf Operanden und Daten, die für die aktuelle Operation notwendig sind.
Würde man aufwendige Berechnungen durchführen, und immer wiederkehrende
Operanden oder Schleifenzähler jedesmal aus dem Ramspeicher holen, würde
dies auf Kosten der Performance gehen. Interessant wird der Hauptspeicher, wenn
zum einen die Daten vorübergehend nicht mehr gebraucht werden und Platz
für neue Befehlsausführungen benötigt wird und zum anderen, wenn
mit grossen Datenmengen gearbeitet wird. Das Stringformat lässt Datengrössen
von gigantischen 4GB (!!!) zu. Natürlich hat man selten so grosse einzelne
Strings, aber selbst wenige Bytes lassen sich schon nicht mehr als Ganzes in
den Registern ablegen oder bearbeiten.
Um das im Ram abgelegte Datum wiederzufinden, wird die Adresse, an der es sich
befindet, in einem Register gespeichert. Leicht nachvollziehbar ist dies noch
bei einzelnen Wörtern, handelt es sich allerdings um Arrays oder gar um
Stringfolgen, müssen andere Strategien angewandt werden.
Im folgenden Bild ist die Vorschrift zur Berechnung von Speicheradressen gegeben.
Natürlich ist nicht jede Methode der Adressierung so umfangreich.

Die errechnete Offsetadresse wird auch als effektive Adresse bezeichnet. Das Displacement ist ein optionaler fester vom Befehl gegebener Wert und liegt zwischen 8 und 32 Bit. Base und Index stehen für einen Wert in den Standardregistern, mit der Ausnahme, dass ESP nur als Basisregister angegeben werden darf. Scale ist ein Skallierungsfaktor, der Werte zwischen eins und vier annimmt.
I/O Operand
Heutige Computersysteme zeichnen sich häufig ausserhalb des Prozessors
aus (Sound, Video, ...). Da dies externe Geräte sind, müssen sie über
den Bus angesprochen werden. Hierfür stehen spezielle Befehle zur Verfügung.
Es gibt zwei Möglichkeiten, die Ein-/Ausgabeports zu adressieren:
