Maschinensprache i8086/ Zahlensysteme

Aus Wikibooks

Wechseln zu: Navigation, Suche

Theorie:  Einleitung - Maschinensprache - Assembler - Zahlensysteme - RAM-Adressen - BWS - Debug - CPU-Register - Einfache Befehle - Stringbefehle - Interrupts - I/O-Ports
Versuch:  BWS1 - BWS2 - Hallo Welt - Bootsektor - MBR
Nützlich: Befehlsliste - PAUSE - Filter
Analyse:  Urlader


Inhaltsverzeichnis

[Bearbeiten] Zahlensysteme

Die Zahlensysteme dienen dazu, wie der Name andeutet, Zahlen darzustellen. Zur Darstellung von Zahlen werden in der Regel das Additions- oder das Stellenwertsystem benutzt.

Das Additionssystem kennen Sie vermutlich von den römischen Zahlen. So ist Beispielsweise die Zahl DXII äquivalent mit der Zahl 512 (500+10+2). Das Additionssystem hat den Nachteil, dass insbesondere komplexe Rechenoperationen wie Multiplikation oder Division nur sehr schwer möglich sind.

Deshalb ist heute anstelle des Additionssystems praktisch nur das Stellenwertsystem gebräuchlich. Es stellt Zahlen durch eine Reihe von Ziffern zi dar:

z_m z_{m-1} \ldots z_0\operatorname{,}z_{-1} z_{-2} \ldots z_{-n}

Den Zahlenwert X erhält man durch aufsummieren aller Ziffern zi, die mit ihrem Stellenwert bi multipliziert werden:

X = \sum_{i=-n}^m z_i \cdot b^i

Diese Definition wirkt auf viele Nichtmathematiker anfangs abschreckend. Sie zeigt aber recht übersichtlich, dass mit einem Stellenwertsystem jede Zahl als Summe dargestellt werden kann. Das ist für die folgenden Überlegungen wichtig.

Sehen wir uns als Beispiel die Dezimalzahl 234 an. Sie lässt sich auch so darstellen:

2 \cdot 10^2 + 3 \cdot 10^1 + 4 \cdot 10^0 = 234

Die auffällig wiederholte 10 in der Formel heißt Basis dieses Stellenwertsystems. Sie ist das b in der Formel darüber. Die Basis muß jedoch nicht 10 sein; jede andere ist genauso möglich. An das System mit der Basis 10 hat sich jedoch der Mensch gewöhnt; es ist unser Dezimalsystem. Außerdem wäre es, wenn die Basis kleiner wäre, schwierig, große Zahlen darzustellen. Wäre die Basis hingegen wesentlich größer als 10, so wäre dies für den Menschen ebenfalls ein ungeeignetes Zahlensystem, da er sehr viele Symbole lernen müsste. Beim Dezimalsystem müssen nur 10 Symbole gelernt werden, nämlich die Ziffern 0 bis 9. (Ein Zahlensystem mit der Basis 60 benutzten beispielsweise die Babylonier. Deshalb hat auch heute noch eine Stunde 60 Minuten und eine Minute 60 Sekunden).

Weil ein Computer nur 2 Zustände kennt: "Strom"(1) und "nicht Strom"(0), bietet es sich an, ein Zahlensystem zu wählen, das auf nur zwei Ziffern basiert, wenn wir ihm beibringen möchten, Daten zu speichern. So ein Zahlensystem hat die Basis 2 und heißt Dualsystem.

[Bearbeiten] Das Dualsystem

Möchten wir einen Wert im Dualsystem darstellen, haben wir eine Ziffer für jeden Zustand: 0 für Strom aus und 1 für Strom an. Nehmen wir als Beispiel die Dualzahl 111001.

Vermutlich können Sie mit diesem Wert wenig anfangen, da Sie das Rechnen im Dezimalsystem gewohnt sind. Gemäß unserer Definition oben können wir die Zahl 111001 folgendermaßen darstellen, wobei die Basis nun 2 ist:

1 \cdot 2^5 + 1 \cdot 2^4 + 1 \cdot 2^3 + 0 \cdot 2^2 + 0 \cdot 2^1 + 1 \cdot 2^0

Wenn wir das ausrechnen, erhalten wir den Wert im Dezimalsystem:

1 \cdot 2^5 = 32
1 \cdot 2^4 = 16
1 \cdot 2^3 = 8
0 \cdot 2^2 = 0
0 \cdot 2^1 = 0
1 \cdot 2^0 = 1

Damit können wir unsere Dualzahl in eine Dezimalzahl umrechnen:

32 + 16 + 8 + 1 = 57

Um ein besseres Gefühl für das Dualsystem zu bekommen, wollen wir uns die Zahlen von 0 bis 7 ansehen (in Klammer ist jeweils der Dezimalwert angegeben):

0 (0)
1 (1)

Soweit ist alles wie gewohnt. Da wir aber nun keine weitere Ziffer zu Verfügung haben, müssen wir auf die nächste Stelle ausweichen:

10 (2)
11 (3)

Um die nächst höhere Zahl darzustellen, benötigen wir wiederum eine weitere Stelle:

100 (4)
101 (5)
110 (6)
111 (7)

usw.

Dies ist sicher gewöhnungsbedürftig. Mit Übung gelingt der Umgang mit Dualzahlen aber fast so gut wie im Dezimalsystem.

Jede Ziffer bei diesem System wird als "Bit" (Abkürzung für binary digit) bezeichnet. Das Bit äußerst rechts ist das "least significant bit" (lsb), das ganz linke heißt "most significant bit" (msb).

Und wie rechnen wir eine Dezimalzahl in eine Dualzahl um?
Ganz einfach!

Die Restwertmethode

Bei der Restwertmethode wird die Dezimalzahl so oft durch zwei geteilt, bis wir den Wert 0 erhalten. Der Rest der Division entspricht von unten nach oben gelesen der Dualzahl. Sehen wir uns dies am Beispiel der Dezimalzahl 25 an:

25 / 2 = 12 Rest 1 --> 1 <-lsb
12 / 2 = 6 Rest 0 --> 0
6 / 2 = 3 Rest 0 --> 0
3 / 2 = 1 Rest 1 --> 1
1 / 2 = 0 Rest 1 --> 1 <-msb

Wichtig: Rechnen Sie beim Restwertsystem immer bis 0 herunter.

Als Ergebnis erhalten wir von unten nach oben gelesen die Dualzahl 11001, was der Dezimalzahl 25 entspricht.

Bevor wir im Dualsystem weiterrechnen, folgt ein Exkurs in die Darstellung von Datenmengen und Speicherplatz: Jeweils 8 Bit ergeben ein Byte. 1024 Byte entsprechen wiederum einem Kilobyte. Diese auf den ersten Blick ungewöhnliche Zahl 1024 entspricht 210. Mit 10 Bits ist es möglich, 210 = 1024 verschiedene Zustände darzustellen. Deshalb ist es sinnvoll, in einem Kilobyte 1024 Byte und nicht 1000 Byte zu sehen.

Weitere häufig verwendete Größen:

Byte Größe
1024 Byte Kilobyte
1.048.576 Byte (1024 Kilobyte) Megabyte
1.073.741.824 Byte (1024 Megabyte) Gigabyte
1.099.511.627.776 Byte (1024 Gigabyte) Terabyte

Beispiel: Der Intel 80386 arbeitet mit einem Adressbus von 32 Bit. Wieviel Speicher kann er damit adressieren?

Lösung: Mit 32 Bit lassen sich 232 verschiedene Dualzahlen darstellen, eine für jede Speicheradresse. Das heißt, der Intel 80386 kann 232 = 4.294.967.296 Byte adressieren. Dies sind 4.194.304 Kilobyte oder 4096 Megabyte oder 4 Gigabyte. (Würde das Kilobyte als 1000 Byte definiert werden, so erhielte man 4,294967296 Gigabyte).

Die bisherige Abkürzung und immer noch weit verbreitete Abkürzung für das Kilobyte war KB, wenn es sich um 1024 Byte handelte. 1998 veröffentliche das IEC (International Electrotechnical Commission) einen neuen Standard, nachdem 1024 Byte einem KibiByte (kilobinary Byte) entspricht und mit KiB abgekürzt wird [Nat00] . Ein Kilobyte, das aus 1000 Byte besteht, wird mit KB abgekürzt. Damit soll in Zukunft die Verwechslungsgefahr verringert werden.

Bisher konnte sich der neue Standard aber nicht durchsetzen. Deshalb wird auch in diesem Buch weiterhin die Abkürzung KB für 1024 Byte verwendet. Entsprechend gilt für Megabyte die Abkürzung MB, für Gigabyte GB und für Terabyte TB.

Nun geht es wieder an das Rechnen mit Dualzahlen.

[Bearbeiten] Das Zweierkomplement

Wenn wir negative Zahlen im Dualsystem darstellen wollen, so gibt es keine Möglichkeit, das Vorzeichen mit + oder - darzustellen. Man könnte auf die Idee kommen, stattdessen das Most Significant Bit (MSB, links) als Vorzeichen zu benutzen. Ein gesetztes MSB, also mit dem Wert 1, stellt beispielsweise ein negatives Vorzeichen dar. Dann sähe z.B. die Zahl -7 in einer 8-Bit-Darstellung so aus: 10000111

Diese Darstellungsform hat noch zwei Probleme: Zum einen gibt es nun zwei Möglichkeiten die Zahl 0 im Dualsystem darzustellen (nämlich 00000000 und 10000000). Weil Computer Entscheidungen scheuen, straucheln sie an dieser Stelle. Das zweite Problem: Wenn man negative Zahlen addiert, wird das Ergebnis falsch.

Um das klar zu sehen, addieren wir zunächst zwei positiven Zahlen (in Klammern steht der Wert dezimal):

 100011 (35)
+ 10011 (19)
Ü   11  
-----------
 110110 (54)

Die Addition im Binärsystem ist der im Dezimalsystem ähnlich: Im Dualsystem gibt 0 + 0 = 0, 0 + 1 = 1 und 1 + 0 = 1. Soweit ist die Rechnung äquivalent zum Dezimalsystem. Da nun im Dualsystem aber 1 + 1 = 10 ergibt, findet bei 1 + 1 ein Übertrag statt, ähnlich dem Übertrag bei 9 + 7 im Dezimalsystem.

In der Beispielrechnung findet ein solcher Übertrag von Bit 0 (ganz rechts) auf Bit 1 (das zweite von rechts) und von Bit 1 auf Bit 2 statt. Die Überträge sehen Sie in der Zeile Ü.

Wir führen nun eine Addition mit einer negativen Zahl genau wie eben durch und benutzen das Most Significant Bit als Vorzeichen:

    001000 (8)
+ 10000111 (-7)
Ü 
---------------
  10001111 (-15)

Das Ergebnis ist offensichtlich falsch.

Wir müssen deshalb eine andere Möglichkeit finden, mit negativen Dualzahlen zu rechnen. Die Lösung ist, negative Dualzahlen als Zweierkomplement darzustellen. Um es zu bilden, werden im ersten Schritt alle Ziffern der positiven Dualzahl umgekehrt: 1 wird 0, und umgekehrt. Dadurch entsteht das Einerkomplement. Daraus wird das Zweierkomplement, indem wir 1 addieren.

Beispiel: Für die Zahl -7 wird das Zweierkomplement gebildet:

  • 00000111 (Zahl 7)
  • 11111000 (Einerkomplement)
  • 11111001 (Zweierkomplement = Einerkomplement + 1)

Das Addieren einer negativen Zahl ist nun problemlos:

  00001000 (8)
+ 11111001 (-7, das Zweierkomplement von 7)
Ü 1111
  -------------
  00000001 (1) 

Wie man sieht, besteht die Subtraktion zweier Dualzahlen aus einer Addition mit Hilfe des Zweierkomplements. Weiteres Beispiel: Um das Ergebnis von 35 - 7 zu ermitteln, rechnen wir 35 + (-7), wobei wir einfach für die Zahl -7 das Zweierkomplement von 7 schreiben.

Eine schwer zu findende Fehlerquelle bei der Programmierung lauert hier: Beim Rechnen mit negativen Zahlen kann es nämlich zum Überlauf (engl. Overflow) kommen, d.h. das Ergebnis ist größer als die höchstens darstellbare Zahl. Bei einer 8-Bit-Zahl etwa, deren linkes Bit das Vorzeichen ist, lassen sich nur Zahlen zwischen -128 und +127 darstellen. Hier werden 100 und 50 addiert, das Ergebnis müsste eigentlich 150 sein, aber:

  01100100 (100)
+ 00110010 (50)
Ü 11
------------
  10010110 (-106, falls das Programm das als vorzeichenbehaftete Zahl ansieht)

Das korrekte Ergebnis 150 liegt nicht mehr zwischen -128 und +127. Wenn wir das Ergebnis als vorzeichenbehaftete Zahl, d.h. als Zweierkomplement, ansehen, beziehungsweise, wenn der Computer das tut, lautet es -106. Nur wenn wir das Ergebnis als positive Binärzahl ohne Vorzeichen ansehen, lautet es 150. Unser Programm muss den Computer wissen lassen, ob er das Ergebnis als Zweierkomplement oder als vorzeichenlose Binärzahl ansehen muss. Dieses Beispiel legt nahe, dass die Kenntnis dieser Grundlagen unentbehrlich ist. Andernfalls verbringt man Nächte mit erfolgloser Fehlersuche.

[Bearbeiten] Das Hexadezimalsystem (Sedezimalsystem)

Im Dualsystem sind schon zur Darstellung kleiner Zahlen viele Ziffern nötig. Für die Dezimalzahl 200 erhält man beispielsweise eine 8-ziffrige Dualzahl (11001000). Prozessoren, die mit 32 oder mit 64 Bit rechnen, gestalten den Umgang mit Dualzahlen noch unübersichtlicher. Wie im letzten Kapitel zu sehen, ist auch die Umrechnung einer Dezimalzahl in eine Dualzahl unhandlich.

Aus diesen Gründen begegnen Sie bei der Programmierung mit Assembler dem Hexadezimalsystem. Es ist ein Stellenwertsystem mit der Basis 16. Damit gelingt die Umrechnung ins Dualsystem und umgekehrt leichter.

Beim Hexadezimalsystem werden die Zahlen 0 bis 9 wie gewohnt dargestellt, die Ziffern von 10 bis 15 (Dezimal) werden durch Buchstaben dargestellt:

10 = A
11 = B
12 = C
13 = D
14 = E
15 = F

Nun rechnen wir eine Hexadezimalzahl in eine Dezimalzahl um:
 [2BD]_{16} = 2 \cdot  16^2 + B \cdot  16^1 + D \cdot 16^0
 2 \cdot 16^2 = 512
 11 \cdot16^1 = 176
 13 \cdot 16^0 = 13
Summe: 701

Die Umrechnung vom Dualsystem in das Hexadezimalsystem ist sehr einfach: Die Dualzahl wird dabei rechts beginnend in Viererblöcke unterteilt und dann blockweise umgerechnet. Beispiel: Die Zahl 1010111101 soll in das Hexadezimalsystem umgerechnet werden.

Zunächst teilen wir die Zahl in 4er-Blöcke ein:

10 1011 1101

Anschließend rechnen wir die Blöcke einzeln um:

10 = 2
1011 = B
1101 = D
[1010111101]2 = [2BD]16

Wichtig: Die Viererblöcke müssen von "hinten", also vom least significant bit her, abgeteilt werden.

Stolperfalle beim Programmieren später: Einige Prozessoren schreiben das least significant bit rechts, andere links. Das ist noch nicht wichtig, aber wer schon jetzt nachschlagen will, suche die Stichwörter "little endian" und "big endian".

Üblich und daher auch im Assembler NASM ist die Schreibweise von Hexadezimalzahlen mit einem vorangestellten 0x (z. B. 0x2DB)oder mit der Endung h (z. B. 2DBh). Wir verwenden die Schreibweise mit h.

[Bearbeiten] Der ASCII-Zeichensatz und Unicode

Ein Rechner speichert alle Informationen numerisch, d.h. als Zahlen. Um auch Zeichen wie Buchstaben und Satzzeichen speichern zu können, muss er jedem Zeichen einen eindeutigen Zahlenwert geben.

Die ersten Kodierungen von Zeichen für Computer gehen auf die Hollerith-Lochkarten (benannt nach deren Entwickler Herman Hollerith) zurück, die zur Volkszählung in den USA 1890 entwickelt wurden. Auf diesen Code aufbauend entwickelte IBM den 6-Bit-BCDIC-Code (Binary Coded Decimal Interchange Code), der dann zum EBCDIC (Extended Binary Coded Decimal Interchange Code), einem 8-Bit-Code, erweitert wurde.

1968 wurde der American Standard Code for Information Interchange (ASCII) verabschiedet, der auf einem 7-Bit-Code basiert und sich schnell gegen andere Standards wie dem EBCDIC durchsetzte. Das achte Bit des Bytes wurde als Prüf- oder Parity-Bit zur Sicherstellung korrekter Übertragungen verwendet.

Im Unterschied zum heute praktisch nicht mehr verwendeten, konkurrierenden EBCDI-Code gehört beim ASCII-Code zu jedem möglichen Binärzeichen ein Zeichen. Das macht den Code ökonomischer. Außerdem liegen die Groß- und die Kleinbuchstaben jeweils in zusammenhängenden Bereichen, was für die Programmierung deutliche Vorteile hat, mit einem Pferdefuß: Werden Strings (Zeichenfolgen, wie etwa Wörter) nach ASCII sortiert, geraten die klein geschriebenen Wörter hinter alle groß beginnenden. Die lexikalische Sortierung erfordert daher etwas mehr Aufwand.

Neben den alphabetischen Zeichen in Groß- und Kleinbuchstaben erhält der ASCII-Code noch eine Reihe von Sonderzeichen und Steuerzeichen. Steuerzeichen sind nicht druckbare Zeichen, die ursprünglich dazu dienten, Drucker oder andere Ausgabegeräte zu steuern. Die 128 Zeichen des ASCII-Codes lassen sich grob so einteilen (in Klammern die Nummern in hexadezimaler Notierung):

  • Zeichen 0 bis 31 (1Fh): Steuerzeichen, wie z. B. Tabulatoren, Zeilen- und Seitenvorschub
  • Zeichen 48 (30h) bis 57 (39h): Ziffern 0 bis 9
  • Zeichen 65 (41h) bis 90 (5Ah): Großbuchstaben A bis Z
  • Zeichen 97 (61h) bis 122 (7Ah): Kleinbuchstaben a bis z

Leer- und Interpunktionszeichen, Klammern, Dollarzeichen usw. verteilen sich in den Zwischenräumen.

Bei diesem Standard fehlen allerdings Sonderzeichen wie beispielsweise die Umlaute. Mit dem Erscheinen des PCs erweiterte IBM den ASCII-Code auf 8 Bit, so dass nun 128 weitere Zeichen darstellbar waren. Neben einigen Sonderzeichen besaß der "erweiterte ASCII-Code" auch eine Reihe von Zeichen zur Darstellung von Blockgrafiken.

Mit MS-DOS 3.3 führte Microsoft die sogenannten "Code Pages" ein, die den Zeichen von 128 bis 255 eine flexible auf den jeweiligen Zeichensatz angepasst werden konnte. Damit konnte beispielsweise auch der griechische Zeichensatz dargestellt werden.

Das eigentliche Problem lösten die Code Pages indes nicht: die Verwendung unterschiedlicher Codepages machte zwar die für die europäischen Sprachen benötigten Zeichen verfügbar, führt aber bis heute dazu, dass z. B. Umlaute in E-Mails nicht korrekt übertragen werden, falls das sendende Mail-Programm nicht korrekt konfiguriert oder fehlerhaft programmiert ist. Noch problematischer war, dass japanische oder chinesische Zeichen nicht dargestellt werden konnten, da ihre Anzahl die 256 möglichen Zeichen bei weitem überstieg. Dazu wurden DBCS (double byte character set) eingeführt, der den Zeichensatz auf 16 Bit erweitert, allerdings auch noch den 8 Bit ASCII-Zeichensatz unterstützt.

Mit dem Unicode schließlich wurde ein Schlussstrich unter die Wirrungen in Folge der verschiedenen Erweiterungen gezogen und ein einheitlicher 16-Bit-Zeichensatz eingeführt. Mit Unicode ist es möglich, 65536 verschiedene Zeichen und sämtliche vorhandenene Sprachen in einem Zeichensatz darzustellen.

Persönliche Werkzeuge
Buch erstellen