Skript

Transcription

Skript
1
2
3
4
Programmierung 2
Sebastian Hack
Base revision b267af6
Tue May 15 00:34:47 2012 +0200
5
6
7
Inhaltsverzeichnis
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
5
5
6
10
14
29
2. Maschinensprache
2.1. Aufbau von Rechnern . . . . . . . . . . . . . . . . . .
2.1.1. Der von-Neumann Rechner . . . . . . . . . .
2.1.2. Der MIPS Prozessor . . . . . . . . . . . . . .
2.2. Der Assembler . . . . . . . . . . . . . . . . . . . . .
2.2.1. Adressierung . . . . . . . . . . . . . . . . . .
2.2.2. Das Datensegment . . . . . . . . . . . . . . .
2.3. Der Linker . . . . . . . . . . . . . . . . . . . . . . . .
2.4. Das Laden des Programms und die Speicheraufteilung
2.4.1. Speicherverwaltung auf der Halde . . . . . . .
2.5. Aufruf von Unterprogrammen . . . . . . . . . . . . .
2.5.1. Verwendung der Registerbank . . . . . . . . .
2.6. Einführung in den MIPS Befehlssatz . . . . . . . . . .
2.6.1. Rechenwerk . . . . . . . . . . . . . . . . . . .
2.6.2. Steuerwerk . . . . . . . . . . . . . . . . . . .
2.6.3. Unterstützung vom Assembler . . . . . . . . .
2.6.4. Schnittstelle zum Betriebssystem . . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
15
15
15
16
18
21
22
24
24
24
25
28
28
28
30
31
31
30
3. C
33
31
4. Verifikation
35
32
5. Testen
37
37
6. Java
6.1. Strukturierte Programmierung . .
6.2. Objektorientierte Programmierung
6.3. Einfache Datenstrukturen . . . . .
6.4. Entwurfsmuster . . . . . . . . . .
39
39
39
39
39
38
A. Notationen
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
33
34
35
36
1. Arithmetik im Rechner
1.1. Bits und Bytes . . . .
1.2. Dualzahlen . . . . .
1.2.1. Ganze Zahlen
1.3. Zusammenfassung .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
41
3
Inhaltsverzeichnis
39
40
41
42
43
44
45
B. Syntax und Semantik der wichtigsten MIPS-Befehle
B.1. Arithmetik . . . . . . . . . . . . . . . . . . . . . . . .
B.1.1. Mit drei Registeroperanden . . . . . . . . . . .
B.1.2. Bitschifts mit konstantem Schift . . . . . . . .
B.1.3. Arithmetik mit konstantem Operand . . . . . .
B.2. Lade- und Speicherbefehle . . . . . . . . . . . . . . .
B.3. Sprungbefehle . . . . . . . . . . . . . . . . . . . . . .
4
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
Revision: b267af6
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
43
43
43
43
43
44
44
Tue May 15 00:34:47 2012 +0200
46
1. Arithmetik im Rechner
52
Digitalrechner sind elektrische Schaltkreise. Information wird in einem solchen Rechner durch das
Vorhandensein, beziehungsweise durch die Abwesenheit einer bestimmten Spannung repräsentiert.
Durch das Vorhandensein/die Abwesenheit von Spannung können also zwei Zustände (L = Spannung an,
O = Spannung aus) kodiert werden. Eine Variable, die diese zwei Werte annehmen kann, nennt man
ein Bit. In diesem Kapitel werden wir Folgen von Bits, und Operationen darauf, verwenden um die
Arithmetik der ganzen Zahlen im Rechner nachzubilden.
53
1.1. Bits und Bytes
54
Sei
47
48
49
50
51
Bn := B
· · × B}
| × ·{z
B := {O, L}
n
55
56
57
Ein Element b ∈ B heißt ein Bit. Das Komplement b eines Bits b ist definiert durch
(
O b=L
b :=
L b=O
Darüberhinaus führen wir hier noch drei assoziative und kommutative Operationen auf Bits ein, die
später wichtig werden. Wir definieren sie durch die Angabe einer Wertetabelle:
a
O
O
L
L
58
59
60
61
62
63
65
a and b a or b a xor b
O
O
O
O
L
L
O
L
L
L
L
O
Ein Tupel von Bits (bn−1 , . . . , b0 ) ∈ Bn nennen wir auch eine Bitfolge der Länge n und schreiben
sie bn−1 . . . b0 . Das Bit bn−1 heißt most significant bit (MSB) und das das Bit b0 heißt least significant bit (LSB). Ein Element aus B8 heißt ein Byte. Man schreibt eine Folge von vier Bit häufig als
Hexadezimalzahl, indem man jeder Bitfolge aus B4 ein Zeichen zuordnet. Hierzu definieren wir die
Abbildung
h : B4 → {0, . . . , 9, a, . . . , f}
mit
OOOO
OOOL
OOLO
OOLL
64
b
O
L
O
L
7→
7
→
7
→
7
→
0
1
2
3
OLOO
OLOL
OLLO
OLLL
7→
7→
7
→
7
→
4
5
6
7
LOOO
LOOL
LOLO
LOLL
7→
7→
7→
7→
8
9
a
b
LLOO
LLOL
LLLO
LLLL
7→
7
→
7
→
7
→
c
d
e
f
Eine Bitfolge b ∈ B4n schreibt man der Übersichtlichkeit halber oft durch hintereinander hängen der
einzelnen Hexadezimalzeichen. Gewöhnlich schreibt man noch, um die Hexadezimalzahl nicht mit
5
1. Arithmetik im Rechner
67
einer Dezimalzahl zu verwechseln, ein Kennzeichen davor. Oft ist dies 0x oder aber auch $. Zum
Beispiel schreibt man:
OOOLLOOLLLLLLOLO ∈ B16 als 0x19fa
68
Zwei Bitfolgen können durch den Operator · konkateniert werden:
66
am−1 . . . a0 · bn−1 . . . b0 = am−1 . . . a0 bn−1 . . . b0
69
Oft lassen wir den Punkt weg und schreiben die Bitfolgen direkt aneinander. Wir schreiben kurz
bk := b| ·{z
· · }b
k mal
70
71
Negation, logisches Und (and), Oder (or) und das exklusive Oder (xor) erweitern wir auf Bitfolgen
bitweise. So ist für ◦ ∈ {and, or, xor}:
an−1 . . . a0 ◦ bn−1 . . . b0 = (an−1 ◦ bn−1 ) · · · (a0 ◦ b0 )
72
73
Desweiteren führen wir eine Schreibweise ein, um eine Bitfolge aus einer größeren Bitfolge zu extrahieren:
(bn−1 . . . bm . . . bk . . . b0 )k:m = bm . . . bk
für 0 ≤ k ≤ m < n
und kurz
74
Aufgabe 1 (M): Zeigen Sie, dass and, or, xor kommutativ und assoziativ sind.
75
Lösung: Assoziativität mit Wertetabelle aufstellen. Kommutativität kann man direkt ablesen
bk := bk:k
76
78
Aufgabe 2 (MM): Welche Operationen distribuieren mit welchen? Sprich für welche ◦, ◦0 ∈ {and, or, xor} gilt: b ◦ (c ◦0
d) = (b ◦ c) ◦0 (b ◦ d)
79
Lösung: or mit and und umgekehrt, xor ist bezgl. and distributiv. Außerdem sind or und and jeweils distributiv mit sich
80
selbst.
77
81
82
Aufgabe 3 (M): Drücken Sie x mittels xor aus.
83
Lösung: x = x xor L
84
85
86
87
1.2. Dualzahlen
Eine Bitfolge an sich ist keine Zahl. Daher ordnen wir jeder Bitfolge eine natürliche Zahl zu. Beginnen
wir mit den Bitfolgen der Länge 1:
u1 : B → {0, 1}
(
1 falls b0 = L
b0 7→
0 falls b0 = O
88
89
Wir benutzen u1 , um rekursiv jeder Bitfolge b ∈ Bn eineindeutig eine natürliche Zahl un (b) zuzuweisen, sprich sie in den natürlichen Zahlen zu interpretieren.
6
Revision: b267af6
Tue May 15 00:34:47 2012 +0200
1.2. Dualzahlen
Definition 1 (Interpretation von Bitfolgen):
un : Bn → Un = {0, . . . , 2n − 1}
bn−1 . . . b0 7→ u1 (bn−1 ) · 2n−1 + un−1 (bn−2 . . . b0 )
91
Überzeugen wir uns nun davon, dass un tatsächlich die Bitfolgen aus Bn eineindeutig auf Un abbildet.
Wir zeigen mehr:
92
Lemma 1: Für jede Zahl k ∈ {0, . . . , 2n − 1} gibt es eine Bitfolge b der Länge n mit un (b) = k.
93
Beweis. Durch Induktion über die Länge der Bitfolge:
90
94
95
96
97
98
99
100
1. Induktionsanfang n = 1. Für jede Zahl k ∈ {0, 1} existiert eine Bitfolge b mit u1 (b) = k. Folgt
direkt aus der Definition von u1 .
2. Induktionsschritt. Angenommen zu jeder natürlichen Zahl k ∈ {0, . . . , 2n−1 − 1} gibt es eine
Bitfolge b mit un−1 (b) = k. Dann gibt es auch zu jeder natürlichen Zahl k 0 ∈ {0, . . . , 2n − 1}
eine Bitfolge b0 mit un (b0 ) = k 0 . Falls k 0 < 2n−1 dann folgt dies direkt aus der Induktionsannahme. Für 2n−1 ≤ k 0 < 2n gilt mit Definition ??: u(L · c) = k 0 für die Bitfolge c mit
u(c) = k 0 − 2n−1 , die nach Induktionsannahme existiert.
101
102
Korollar 1: un ist eine Bijektion zwischen Bn und Un .
103
Im folgenden werden wir öfters folgendes Lemma benötigen:
104
Lemma 2: 20 + · · · + 2n−1 = 2n − 1
105
Beweis. Durch Induktion.
106
1. Induktionsanfang n = 1: 20 = 21 − 1
107
2. Induktionsschritt:
⇐⇒
⇐⇒
2n−1
20 + · · · + 2n−2 = 2n−1 − 1
Induktionsannahme
+ 20 + · · · + 2n−2 = 2n−1 + 2n−1 − 1 | + 2n−1
20 + · · · + 2n−1 = 2n − 1
108
109
110
111
112
113
114
115
116
Aufgabe 4 (MM): Beweisen Sie Lemma 2.
Analog zum Additionsalgorithmus von Dezimalzahlen, den man im Alltag verwendet, definieren
wir die Addition von zwei Bitfolgen. Betrachten wir zunächst die Summe a add b zweier Bits a
und b. Sie wird mit xor gebildet: Die Summe ist genau dann L, wenn exakt einer der Operanden L
ist. Die Addition produziert einen Übertrag (auf englisch Carry genannt) c := a and b genau dann,
wenn a = b = L. Anstelle einzelner Bits möchte man jedoch meist zwei Bitfolgen an−1 . . . a0 und
bn−1 . . . b0 addieren. Hierzu muss man die Überträge fortschalten. Sprich, die Addition zweier Bits an
Stelle i muss den Übertrag ci , der potentiell von Stelle i − 1 kommt, berücksichtigen.
ai add bi := ai xor bi xor ci
Revision: b267af6
Tue May 15 00:34:47 2012 +0200
(1.1)
7
1. Arithmetik im Rechner
117
118
Dieser hat möglicherweise auch einen Einfluss auf den Übertrag der an Stelle i produziert wird. Der
Übertrag an der Stelle i ist genau dann L, wenn mehr als eines der Bits ai , bi , ci den Wert L hat:
ci+1 := (ai and bi ) or (ai and ci ) or (bi and ci )
119
120
121
122
123
124
125
(1.2)
Wir nehmen an, dass kein Übertrag zur untersten Stelle gelangt und setzen daher c0 := O. Tritt an
der Stelle n − 1 ein Übertrag auf, sprich ist cn = L, dann kann ihr Ergebnis nicht mehr mit n Bits
dargestellt werden und wir sagen, dass die Addition überläuft. Die stellenweise Addition kann nun auf
Dualzahlen erweitert werden. Hierzu verlangen wir, dass die beiden addierten Bitfolgen gleich lang
sind. Die Addition zweier gleichlanger Bitfolgen ist definiert als paarweise Addition der Bits:
Definition 2 (Addition von Bitfolgen): Sei a = an−1 . . . a0 und b = bn−1 . . . b0 . Die Addition von a
und b ist dann definiert durch:
a addn b := (an−1 add bn−1 ) · · · (a0 add b0 )
126
Lemma 3 (Assoziativität der Addition von Bitfolgen): a addn (b addn c) = (a addn b) addn c
127
Beweis.
128
Aufgabe 5 (MMM): Zeigen Sie Lemma 3. Hinweis: Induktion über die Bitfolge.
130
Betrachten wir nun die Addition einer Bitfolge b ∈ Bn mit On−1 L. Wie wir sehen werden entspricht
sie der Nachfolgerfunktion der natürlichen Zahlen im Bereich {0, . . . , 2n − 2}.
131
Lemma 4: Für alle Bitfolgen b ∈ Bn mit un (b) < 2n − 1 gilt: un (b) + 1 = un (b addn On−1 L)
129
132
133
Beweis. Betrachten wir die Bitfolge b genauer. Aus b ≤ 2n − 1 folgt, dass b 6= Ln . Sprich b enthält
mindestens eine O. Somit endet b mit k ∈ {0, . . . , n − 1} L-Bits.
b = bn−1 . . . bk+1 OLk
134
Dann ist nach der Definition der Addition von Bitfolgen (Gleichungen 1.1 und 1.2)
bn−1 . . . bk+1 OLk addn On−1 L = bn−1 . . . bk+1 LOk
135
wir haben also:
u(b) = S +
k−1
X
2
i
und
n−1
u(b addn O
L) = S + 2
k
mit S =
i=0
Pk−1
2i = 2k − 1. Damit folgt die Behauptung.
Nach Lemma 2 ist
137
Aufgabe 6 (MM): Machen Sie sich die Randfälle k = 0 und k = n − 1 in obigem Beweis klar.
i=0
Lemma 4 macht eine Aussage für alle Bitfolgen der Länge n ausser Ln . In der Tat ist
138
Ln addn On−1 L = On
140
141
142
u1 (bi ) · 2i
i=k+1
136
139
n−1
X
(1.3)
Sprich, für Ln fällt die Addition mit On−1 L nicht mit der Nachfolgerfunktion der natürlichen Zahlen
zusammen. Das ist klar, da es nur endlich viele Bitfolgen der Länge n gibt.
Die Addition mit On−1 L entspricht aber der Addition mit 1 in der Restklassengruppe Z/2n Z. Die
Schreibweise nZ steht für die Menge {z ·n | z ∈ Z}. Die Menge Z/nZ besteht aus Äquivalenzklassen
8
Revision: b267af6
Tue May 15 00:34:47 2012 +0200
1.2. Dualzahlen
143
144
der Relation ≡, die die ganzen Zahlen gemäß ihrer Teilbarkeit durch n partitioniert. Zwei Elemente
sind äquivalent, wenn Sie bei einer Division durch n denselben Rest ergeben:
j ≡ k genau dann wenn z, z 0 ∈ Z existieren mit j + z · 2n = k + z 0 · 2n
145
146
Wir definieren die Funktion [·]n (geschrieben als eckige Klammer um ihr Argument), die einer ganzen
Zahl ihre Restklasse zuordnet:
Definition 3:
[·]n : Z → Z/nZ
k 7→ {k + z · n | z ∈ Z}
Wenn das n aus dem Kontext ersichtlich ist, lassen wir es der Übersichtlichkeit halber weg. Zum
Beispiel besteht Z/4Z aus folgenden Elementen:
[0] = {. . . , −8, −4, 0, 4, 8, . . . }
[1] = {. . . , −7, −3, 1, 5, 9, . . . }
[2] = {. . . , −6, −2, 2, 6, 10, . . . }
[3] = {. . . , −5, −1, 3, 7, 11, . . . }
147
Die Addition ⊕ von Elementen in Z/nZ ist nun wie folgt definiert:
Definition 4 (Addition in Z/nZ):
[j] ⊕ [k] := [j + k]
148
Aufgabe 7 (M): Zeigen Sie, dass in Z/nZ gilt: [k] = [k + n] für alle k ∈ Z.
149
Aufgabe 8 (M): Definieren Sie ≡ explizit.
150
Aufgabe 9 (MM): Zeigen Sie, dass ≡ eine Äquivalenzrelation ist.
151
Aufgabe 10 (MMM): Warum wählen wir die Halbgruppe Z/nZ und nicht die Menge N/nN?
152
Aufgabe 11 (MM): Zeigen Sie, dass (Z/nZ, ⊕) eine Halbgruppe ist, indem Sie zeigen, dass ⊕ assoziativ ist.
153
Es gilt nun, dass die Addition einer Bitfolge mit On−1 L der Addition mit 1 in Z/2n Z entspricht.
154
Lemma 5: [un (b) + 1]2n = [un (b addn On−1 L)]2n
155
156
Beweis. Für b 6= Ln folgt dies aus Lemma 4. Sei nun b = Ln . Wir wissen, dass Ln addn On−1 L = On
und
[un (Ln ) + 1] = [2n − 1 + 1]
Definition 1 und Lemma 2
= [0]
Definition 3
n
= [u(O )]
Definition 1
= [u(Ln addn On−1 L)] Gleichung 1.3
157
159
Dieses Ergebnis lässt sich leicht auf die Addition mit einer beliebigen Bitfolge der Länge n verallgemeinern:
160
Lemma 6: [un (a addn b)]2n = [un (a)]2n ⊕ [un (b)]2n
161
Beweis. Durch Induktion über un (b):
158
162
163
1. Induktionsanfang un (b) = 0: Aus der Voraussetzung und aus Definition 1 folgt, dass b = On .
Somit [u(a)] ⊕ [un (b)] = [un (a)] = [un (a addn On )] = [un (a addn b)]
Revision: b267af6
Tue May 15 00:34:47 2012 +0200
9
1. Arithmetik im Rechner
2. Induktionsschritt. Angenommen für un (b) gilt [un (a addn b)] = [un (a) + un (b)]. Dann gilt:
[u(a)] ⊕ [u(b) + 1] = [u(a)] ⊕ [u(b)] ⊕ [1]
Definition und Assoziativität von ⊕
= [u(a addn b)] ⊕ [1]
Induktionsannahme
n−1
= [u((a addn b) addn O
n−1
= [u(a addn (b addn O
L)]
Lemma 5, Definition 4
L))]
Assoziativität von addn
Nach Lemma 5 ist [u(b addn On−1 L)] = [u(b) + 1].
164
165
166
167
168
169
Jetzt haben wir un als Halbgruppen-Isomorphismus zwischen (Z/2n Z, ⊕) und (Bn , addn ) bewiesen. Wir erweitern Z/2n Z zur Gruppe, indem wir inverse Elemente hinzufügen. Diese geben uns dann
die Subtraktion. Das inverse Element a∗ eines Elementes a ist so definiert, dass a⊕a∗ = [0]. Für jedes
Element [k] ∈ Z/2n Z gibt es ein solches inverses Element:
[k]∗ = [2n − k]
170
171
Aufgabe 12 (M): Machen Sie sich das durch Einsetzen der Definition von [k] klar.
172
Analog gibt es inverse Elemente in der Halbgruppe der Bitfolgen: Für jedes b ∈ Bn gilt:
b addn (b addn On−1 L) = On
173
174
Aufgabe 13 (MM): Zeigen Sie: Für alle b ∈ Bn gilt b∗∗ = b.
175
Aufgabe 14 (M): Machen Sie sich durch Ausrechnen klar, dass On∗ = On .
177
Aufgabe 15 (MM): Neben [0] gibt es in Z/2n Z noch ein weiteres Element z mit z = z ∗ . Welches? Welchem Element
entspricht dies in Bn ?
178
Inverse Elemente geben uns Subtraktion in (Z/2n Z, ⊕)
176
[j] [k] := [j] ⊕ [k]∗ = [j] ⊕ [2n − k] = {j + 2n − k + z · 2n | z ∈ Z} = [j − k]
179
und (Bn , addn ):
a subn b := a addn (b addn On−1 L)
180
181
Aufgabe 16 (MM): Zeigen Sie:
(bn−1 . . . bk+1 LOk )∗ = bn−1 . . . bk+1 LOk
182
Trainieren Sie mit dieser Regel das Bilden von Bitfolgen, die negativen Zahlen aus {−2n−1 , . . . , −1} entsprechen.
183
1.2.1. Ganze Zahlen
184
185
186
187
188
Im letzten Abschnitt haben wir Bitfolgen fester Länge einer Äquivalenzklasse zugeordnet. Dies war
ausreichend um die Addition (und Subtraktion) so zu definieren, dass sie mit der Addition auf den
ganzen Zahlen (in der Restklassengruppe Z/2n Z) zusammenfällt. Andere Operationen lassen sich
jedoch nur schlecht auf Restklassen definieren. Wollen wir zum Beispiel zwei Bitfolgen vergleichen,
so ist die Modellierung mit Restklassen mehrdeutig. Betrachten wir zum Beispiel B2 bzw. Z/4Z:
u(OL) = [1] = [−3] und
10
u(LL) = [3] = [−1]
Revision: b267af6
Tue May 15 00:34:47 2012 +0200
1.2. Dualzahlen
b ∈ Bn
O . . . OO
O . . . OL
..
.
u ∈ Un
0
1
..
.
s ∈ Sn
0
1
..
.
OL . . . L
LO . . . O
..
.
2n−1 − 1
2n−1
..
.
2n−1 − 1
−2n−1
..
.
L . . . LL 2n − 1
−1
b ∈ B3 u ∈ U3 s ∈ S3
OOO
0
0
OOL
1
1
2
2
OLO
OLL
3
3
LOO
4
−4
LOL
5
−3
LLO
6
−2
LLL
7
−1
Abbildung 1.1.: Zuordnung von vorzeichenlosen/-behafteten Zahlen zu Bitfolgen
189
190
191
192
193
194
195
196
Es ist nun nicht klar, wie man den Vergleich von ganzen Zahlen auf die Restklassen in Z/4Z sinnvoll
überträgt, denn es ist zwar 1 < 3, aber da [3] ≡ [−1] wäre auch 1 < −1, was nicht sinnvoll ist.
Daher reicht es nicht aus, sich bei der Vergleichsoperation auf Restklassen zu beziehen. Man muss
sich auf eine konkrete Menge von Zahlen festlegen. Da man sowohl an vorzeichenlosen als auch an
vorzeichenbehafteten Zahlen interessiert ist, verwendet man in der Praxis die folgenden Mengen:
Un := {0, . . . , 2n − 1}
Sn := {−2n−1 , . . . , 2n−1 − 1}
Vorzeichenlose Zahlen
Vorzeichenbehaftete Zahlen
Abbildung 1.1 zeigt die Bitfolgen und die entsprechenden Werte aus Sn und Un . Bei den vorzeichenbehafteten Zahlen wählt man für Zahlen > 2n−1 die größten negativen Zahlen der Äquivalenzklasse.
Man beachte, dass für jede Zeile der Tabelle folgender Zusammenhang besteht:
s = u − u1 (bn−1 ) · 2n
197
198
Analog zur Interpretation un definieren wir uns die Funktion sn , die Bitfolgen vorzeichenbehaftet
interpretiert.
Definition 5 (Vorzeichenbehaftete Interpretation von Bitfolgen, two’s complement number):
sn : Bn → Z
bn−1 bn−2 . . . b0 7→ un−1 (bn−2 . . . b0 ) − u1 (bn−1 ) · 2n−1
199
200
Aufgabe 17 (M): Zeigen Sie: k ∈ {−2n−1 , . . . , −1} ⇐⇒ bn−1 = L für un (b) = [k]. Sprich, ist das MSB einer Bitfolge
L, so ist ihre vorzeichenbehaftete Interpretation negativ.
203
Elegant ist nun, dass die Addition auf Bitfolgen unabhängig von deren Interpretation funktioniert.
Dies wird durch folgendes Lemma klar, das sagt, dass sowohl die vorzeichenlose als auch die vorzeichenbehaftete Interpretation derselben Bitfolge in derselben Restklasse liegen:
204
Lemma 7: [u(b)] = [s(b)].
201
202
Beweis.
u(b) − s(b) = u1 (bn−1 ) · 2n−1 + un−1 (b0 . . . bn−2 ) + u1 (bn−1 ) · 2n−1 − un−1 (b0 . . . bn−2 )
= u1 (bn−1 ) · 2n
205
Also ist:
[u(b)] = [s(b) + (u(b) − s(b))] = [s(b) + u1 (bn−1 ) · 2n ] = [s(b)]
206
Revision: b267af6
Tue May 15 00:34:47 2012 +0200
11
1. Arithmetik im Rechner
207
208
209
210
Ist nun also OL < LL wahr oder nicht? Das hängt davon ab, ob < den Bitfolgen die vorzeichenlose oder vorzeichenbehaftete Bedeutung gibt. Es ist also nicht eine Eigenschaft der Bitfolge, ob sie
als Zahl aus Sn oder Un zu interpretieren ist, sondern der Operation! Daher schreiben wir für den
Vergleich, der seine Operanden vorzeichenbehaftet (vorzeichenlos) interpretiert1 :
a ltn b := sn (a) < sn (b)
a ltun b := un (a) < un (b)
211
212
213
Für die Implementierung der Vergleichsoperation ist diese Definition aber ungeeignet, da sie auf die
ganzen Zahlen zurückgreift. In einem Rechner werden diese Operationen durch Bitoperationen wie
and, or, . . . implementiert. Im Falle des vorzeichenlosen Vergleichs zum Beispiel:
a ltun b := (a and b) or (a xor b) and (a subn b)
214
215
216
(1.4)
Gleichung 1.4 ist im MSB genau dann L, wenn u(a) < u(b).
Aufgabe 18 (MM): Vollziehen Sie Gleichung 1.4 nach. Stellen Sie eine Wertetabelle mit den Teilergebnissen der Teilterme
auf.
218
Aufgabe 19 (MMM): Geben Sie einen Ausdruck mit Bitoperationen auf zwei Bitfolgen a, b an, der im MSB genau dann L
ist, wenn s(a) < s(b).
219
Bereichsüberschreitung
217
220
221
222
223
224
225
226
Englisch auch overflowTodo: engl index genannt, daher auch im Deutschen oft Überlauf . Seien a, b
zwei Zahlen aus N ∈ {Sn , Un }. Eine Bereichsüberschreitung tritt auf, wenn a + b 6∈ N . Beispielsweise ist 3 + 6 eine Bereichsüberschreitung in U3 und 3 + 1 eine solche in S3 .
Interessant ist nun die Frage, wie wir den Bitfolgen, die wir mit diesen Zahlen assoziieren die
Bereichsüberschreitung bei der Addition ablesen. Das hängt nun davon ab, ob wir die Bitfolgen vorzeichenlos oder vorzeichenbehaftet interpretieren. Die Bereichsüberschreitung ist also nicht eine inhärente Eigenschaft der Addition, sondern
1. N = Sn : Die Bereichsüberschreitung tritt auf, wenn beide Zahlen x, y ∈ Sn dasselbe Vorzeichen haben und das Vorzeichen des Additionsergebnisses vom Vorzeichen der Operanden
abweicht.
227
228
229
230
Aufgabe 20 (MM): Beweisen diese Aussage.
231
Aufgabe 21 (MM): Geben Sie einen Ausdruck an, der genau dann zu L auswertet, wenn die Addition zweier Bitfolgen bei vorzeichenbehafteter Interpretation eine Bereichsüberschreitung erzeugen würde.
232
2. N = Un : Die Bereichsüberschreitung tritt auf, wenn bei der Addition im MSB ein Übertrag
auftritt. Da in Hardware ein Addier-Schaltkreis auch immer den Übertrag produziert, wird das
Bit, das die Bereichsüberschreitung anzeigt, von der Hardware immer „mitberechnet“.
233
234
235
236
237
238
239
Bemerkung 1: Prozessoren mit Statusregister (engl.: flag register) haben zur Anzeige der Bereichsüberschreitung zwei Bits (carry und overflow) in diesem Register, die nach jeder Addition den Übertrag des MSB und den Wert aus Aufgabe 21 enthalten. Hat ein Prozessor kein Statusregister, so muss
man sich die Werte selbst berechnen.
1
lt steht für „less than“, ltu für „less than unsigned“
12
Revision: b267af6
Tue May 15 00:34:47 2012 +0200
1.3. Zusammenfassung
240
241
242
243
244
245
246
247
248
Bereichserweiterung
Es kommt häufig vor, dass man kürzere Bitfolgen in längere umwandeln will. Beispielsweise arbeitet
ein Prozessor intern mit einer gewissen Wortbreite von w Bits (in der Praxis ist 8 ≤ w ≤ 64). Lädt
man nun beispielsweise ein Byte b in einen Prozessor mit w = 32, so muss b zu einer Bitfolge b0 ∈ B32
umgewandelt werden. Der Programmierer hat natürlich das Interesse, dass b0 derselben Zahl entspricht
wie b, sprich, dass entweder s8 (b) = s32 (b0 ) oder u8 (b) = u32 (b0 ). Die beiden Gleichungen sind nicht
äquivalent. Abhängig davon, ob b und b0 vorzeichenlos oder vorzeichenbehaftet interpretiert werden
sollen, müssen die „neuen“ Bits 8–31 in b0 anders gesetzt werden. Betrachten wir Abbildung 1.2,
in der alle Bitfolgen der Länge zwei und drei mit den entsprechenden vorzeichenlosen/-behafteten
Zahlen aufgeführt sind. Will man nun eine Bitfolge b0 ∈ B2 in eine Bitfolge b ∈ B3 umwandeln, so
b ∈ B3 s(b) u(b)
OOO
0
0
OOL
1
1
OLO
2
2
OLL
3
3
LOO −4
4
LOL −3
5
LLO −2
6
LLL −1
7
b0 ∈ B2 s(b) u(b)
OO
0
0
OL
1
1
LO −2
2
LL −1
3
Abbildung 1.2.: Alle Bitfolgen der Längen 2–4 mit den entsprechend zugeordneten Zahlen
249
250
251
252
dass u3 (b) = u2 (b0 ), so ist b = O · b0 , wie man der Definition von u (Definition 1) leicht abliest. Ist
man nun aber daran interessiert, dass s3 (b) = s2 (b0 ), so reicht eine Erweiterung mit O nicht aus. Nach
Abbildung 1.2 ist
s2 (LO) = −2 = s3 (LLO) 6= s3 (OLO)
253
Es gilt:
254
Lemma 8: sn+1 (bn−1 · bn−1 . . . b0 ) = sn (bn−1 . . . b0 )
Beweis.
sn+1 (bn−1 · bn−1 . . . b0 ) =
=
=
=
un (bn−1 . . . b0 ) − u1 (bn−1 ) · 2n
Definition 5
n−1
n
un−1 (bn−2 . . . b0 ) + u1 (bn−1 ) · 2
− u1 (bn−1 ) · 2 Definition 1
un−1 (bn−2 . . . b0 ) − u1 (bn−1 ) · 2n−1
sn (b)
Definition 5
255
256
257
Im folgenden verwenden wir die folgenden beiden Operationen, um die Bereichserweiterung auszudrücken:
zextnm : Bm → Bn
sextnm : Bm → Bn
n−m
n−m
b 7→ O
·b
b 7→ bm−1
·b
„Zero-Extension“
„Sign-Extension“
Revision: b267af6
Tue May 15 00:34:47 2012 +0200
13
1. Arithmetik im Rechner
258
259
260
261
262
263
264
265
1.3. Zusammenfassung
In diesem Kapitel haben wir gesehen, wie wir mit Bits und drei einfachen Operationen darauf die
Arithmetik der ganzen Zahlen in einem Rechner nachbauen können. Legen wir eine endliche Menge
von Zahlen zugrunde, koinzidiert die Bitoperation addn mit der Addition in der Restklassengruppe
Z/nZ und zwar unabhängig davon, ob wir die Bitfolgen vorzeichenlos oder vorzeichenbehaftet interpretieren. Für andere Operationen, wir haben den Vergleich, die Prüfung auf Bereichsüberschreitung
und die Bereichserweiterung diskutiert, ist aber die Implementierung mit Bitoperationen abhängig
von der Interpretation der Bitfolge.
14
Revision: b267af6
Tue May 15 00:34:47 2012 +0200
266
2. Maschinensprache
289
Wir wollen zunächst mit einer sehr einfachen imperativen Programmiersprache beginnen, der Maschinensprache für den Befehlssatz der MIPS Prozessorfamilie. Dies hat mehrere Gründe. Erstens
haben Maschinensprachen wesentlich weniger Sprachelemente wie ausgewachsene objektorientierte
Sprachen, wie C++ oder Java. Daher ist eine Maschinensprache überschaubar und leicht erlernbar.
Des Weiteren gibt es keine Abstraktionen, die Details der Programmausführung verbergen. Insofern
ist das was man programmiert, das was tatsächlich auf dem Rechner läuft.
In der Praxis sind die Abstraktionen, die von imperativen Hochsprachen bereit gestellt werden
selbstverständlich hilfreich, da sie Arbeit sparen, indem sie den Programmierer von den Details des
Rechners, auf dem das Programm laufen soll, fernhalten. Diese Anpassung nimmt dann ein Übersetzer
(engl.: Compiler) vor.
Trotzdem erschließt sich durch das Erlernen von Maschinensprache ein grundlegendes Verständnis
dafür, wie ein Rechner ein Programm ausführt. Ferner können wir durch die Angabe der Übersetzung eines Hochsprachenkonstruktes in Maschinensprache dessen Bedeutung genau erklären. Aus
Gründen der formalen Stringenz verwendet man zur Definition der Semantik einer Sprache in der
Wissenschaft eine Abbildung der Sprache auf mathematische Konstrukte. Dies ist wichtig, wenn
man über Programme einer solchen formalisierten Sprache mathematisch argumentieren will. Jedoch
sind solche Formalisierungen für Sprachen wie sie uns hier interessieren schon recht komplex, so
dass der Nutzen für unsere Zwecke fragwürdig ist. Der interessierte Leser sei hier an das Einsteigerbuch [Nielson and Nielson(1992)] verwiesen.
Darüberhinaus ist die Kenntnis dieses Übersetzungsprozesses wichtig, wenn Programme geschrieben werden müssen, bei denen Laufzeit oder Speicherverbrauch kritisch sind. Hier ist es unerlässlich
zu wissen, wie das Hochsprachen-Programm auf die Maschine abgebildet wird, um ineffizienten Code
zu vermeiden.
290
2.1. Aufbau von Rechnern
291
2.1.1. Der von-Neumann Rechner
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
292
293
294
295
296
297
298
299
300
301
302
303
Betrachten wir zunächst die Rechenanlagen, welche für die imperative Programmierung charakteristisch sind. Die Konzepte dieser Rechner sind seit 70 Jahren die gleichen. Erstmals ausführlich dokumentiert wurden sie von dem ungarisch-amerikanischen Mathematiker John von Neumann in seinem
bahnbrechenden Bericht über den Rechner EDVAC [von Neumann(1993)]. Abbildung 2.1 zeigt den
Entwurf schematisch. Der Entwurf hat sich bis heute kaum geändert. Ein Rechner besteht aus einem
(oder auch mehreren) Prozessoren. Ein Prozessor (engl.: CPU, central processing unit) ist aber nichts
anderes als ein Busteilnehmer, der über den Bus mit anderen Komponenten kommuniziert. Beispielsweise kann er Daten in den Speicher schreiben oder aus ihm lesen.
Der Bus dient als zentrales Kommunikationsmittel der Komponenten der Rechenanlage. Jede Komponente, auch der Prozessor, kann Anfragen an den Bus richten, beispielsweise: Hole 4 Bytes von
Adresse 0xe0000000. Dies meint nicht notwendigerweise eine Hauptspeicheradresse. Der BusController verwaltet eine Liste, die jeder Komponente einen Speicherbereich zuordnet. So könnte
15
2. Maschinensprache
CPU
Registerbank
Rechenwerk
Steuerwerk
Bus
E/A
Speicher
Abbildung 2.1.: Der von-Neumann Rechner
307
obige Adresse beispielsweise zu einem Puffer in einer im System vorhandenen Netzwerkkarte gehören. Nicht in jeder Bus-Kommunikation ist der Prozessor involviert. Beispielsweise kann das Betriebssytem jener Netzwerkkarte auftragen, 4 Kilobyte Daten über den Bus in den Hauptspeicher zu
transferieren.
308
2.1.2. Der MIPS Prozessor
304
305
306
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
Der Prozessor besteht aus mehreren Einheiten, die wir hier unterscheiden wollen: Zunächst verfügt der
Prozessor über einen eigenen kleinem Speicher, die Registerbank. Die Existenz dieser Registerbank
hat im wesentlichen zwei Gründe, auf die wir später eingehen werden. Das Rechenwerk implementiert
die herkömmlichen logischen und arithmetischen Operationen auf Bitfolgen. Diese werden aus der
Registerbank gelesen und in die Registerbank geschrieben. Das Steuerwerk steuert die Abarbeitung
der Befehle. Es sorgt dafür, dass die Befehle in den Prozessor geladen werden und entscheidet, welcher
Befehl als nächstes geladen und ausgeführt wird.
Das Programm, das der Prozessor ausführt liegt hierbei auch im Speicher. Es ist nichts anderes
als eine Ansammlung von Bits. In jedem Rechenschritt liest der Prozessor ein Befehlswort aus dem
Speicher. Verschiedene Prozessoren unterscheiden sich in der Länge ihrer Befehlswörter. Der hier
besprochene MIPS-Prozessor hat eine Befehlswortlänge von 32 Bits. Die Adresse des als nächstes zu
lesenden Befehls steht in einem speziellen Register pc, genannt Befehlszeiger (engl.: program counter,
instruction pointer). In jedem Rechenschritt führt der Prozessor nun einen Befehl aus. Dieser Befehl
ändert den Zustand s ∈ S des Rechners. Diesen Zustandsübergang modellieren wir mathematisch
als Relation ⊂ S × S.
setzt alle Zustände, die durch die Ausführung eines Befehls ineinander
übergehen können, in Beziehung. Prozessoren sind meist deterministisch, das heißt, dass ein Zustand
genau einen oder keinen Nachfolgezustand hat. Sprich ∀s, s0 , s00 ∈ S : s
s0 ∧s
s00 =⇒ s0 = s00 .
Der Zustand S besteht aus dem Inhalt der Register R, des Speichers M und des Befehlszeigers pc:
s = (R, M, pc) ∈ S
327
328
329
MIPS hat 32 Register von denen jedes 32 Bits enthalten kann. Der Zustand der Registerbank ist
modelliert als Abbildung, die jedem Register, gegeben durch seine Nummer, seinen Inhalt zuweist.
Der Speicher ist eine Abbildung von 232 Adressen auf Bytes und der Befehlszeiger ist eine Adresse:
R : B5 → B32
16
M : B32 → B8
pc : B32
Revision: b267af6
Tue May 15 00:34:47 2012 +0200
2.1. Aufbau von Rechnern
330
331
Ein Befehl ist nun eine Folge von 32 Bits. So interpretiert der MIPS Prozessor beispielsweise die
Bitfolge
b = OOOOOO
| {z } · OOOLL
| {z } · OOLOO
| {z } · OOOLO
| {z } · OOOOO
| {z } · LOOOOL
| {z }
opcode
332
333
334
335
336
337
338
339
340
341
342
343
344
addu $rd $rs $rt
346
rt
rd
unbenutzt
funct
so, dass er den Inhalt des Registers 3 zum Inhalt des Registers 4 addiert, das Ergebnis in Register 2 speichert und den Befehlszeiger um 4 erhöht. Die Bitfolge b kodiert all diese Informationen
in verschieden Feldern, die oben kenntlich gemacht sind. Die Felder opcode und funct kodieren die
Operation die auszuführen ist. Der MIPS Prozessor ordnet den konkreten Bitfolgen opcode = OOOOOO
und funct = LOOOOL die Operation „addiere“ zu. Wie schon gesagt, ist die konkrete Zuordnung willkürlich; es gibt keinen Grund gerade dieser Bitfolge „addiere“ zuzuordnen. Da es sinnlos ist, sich
die Bitfolgen der Operationen einzuprägen, gibt man jeder Operation einen Namen (auch Mnemonic)
genannt, den man dann stellvertretend für die Bitfolge verwendet. Die Felder rs, rt, rd kodieren die
Operanden und das Zielregister der Operation.
Mathematisch modellieren wir die Semantik einen Befehl als Regelschema, das beschreibt, welche
Zustände dieser Befehl ineinander überführen kann. Die Parameter des Befehls sind seine Operanden
(meist Register). Zum Beispiel ist das Regelschema für den Befehl addu, der die Inhalte zweier
Register rs, rt addiert und das Ergebnis in einem Register rd speichert, das folgende:
R0
345
rs
load _word (M, pc) = OOOOOO · rs · rt · rd · OOOOO · LOOOOL
= R[rd 7→ R(rs) add32 R(rt)][0 7→ 0] M 0 = M pc 0 = pc add32 4
(R, M, pc)
(R0 , M 0 , pc 0 )
Die Notation R[x 7→ y] steht hier für eine Funktion, die auf derselben Urbildmenge definiert ist wie R,
für x den Wert y liefert und für z 6= x den Wert R(z) liefert, also
R[x 7→ y] = λz. if z = x then y else R(z)
347
348
Die Funktion load _word (M, p) lädt vier Bytes aus dem Speicher beginnend ab Adresse p und liefert
eine Bitfolge aus 32 Bits.
load _word (M, p) = M (p) · M (p add32 1) · M (p add32 2) · M (p add32 3)
349
Die Parameter des Schemas sind rd , rs, rt die für Register stehen. Für einen konkreten Befehl1
addu $2 $3 $4
350
351
ist die Regel (das instantiierte Regelschema):
load _word (M, pc) = OOOOOO · OOOLL · OOLOO · OOOLO · OOOOO · LOOOOL
R0 = R[2 7→ R(3) add32 R(4)][0 7→ 0] M 0 = M pc 0 = pc add32 4
addu $2 $3 $4
(R, M, pc)
(R0 , M 0 , pc 0 )
352
In Worten: Steht im Speicher an der Adresse, die der Befehlszähler enthält, die Bitfolge
OOOOOO · OOOLL · OOLOO · OOOLO · OOOOO · LOOOOL
1
Die Dollarzeichen sind nur syntaktische Konvention und signalisieren im wesentlichen, dass der Operand ein Register
ist.
Revision: b267af6
Tue May 15 00:34:47 2012 +0200
17
2. Maschinensprache
353
354
dann geht der Zustand (r, m, pc) über in einen neuen Zustand (r0 , m0 , pc 0 ) mit
r0 = r[2 7→ r(3) add32 r(4)][0 7→ 0]
Addiere die Bitfolgen der Register 3 und 4 und setze
Register 2 auf das Ergebnis. Das Register 0 behält
den Wert 0.
m0 = m
Der Speicher bleibt unverändert.
pc 0 = pc add32 4
Inkrementiere den Befehlszähler um 4. Er zeigt dann
auf den Befehl hinter dem aktuellen (Jeder Befehl ist
4 Byte lang).
2.2. Der Assembler
359
Wie oben bereits erwähnt, sind Prozessorbefehle nichts anderes als Bitmuster. Natürlich ist nicht
jedes Bitmuster ein Befehl (obwohl es interessante Forschungsarbeiten zu diesem Thema gibt). Jedoch
ist die Programmierung des Prozessors auf dem Niveau von Bitmustern mühsam. Daher gibt es zu
jeder Maschinensprache noch eine Assemblersprache, die eine textuelle Repräsentation der Bitfolgen
mitbringt. Beispielweise lautet der MIPS-Befehl
360
1000 1100 0000 1000 0001 0000 0000 0000
355
356
357
358
361
in Assemblersprache
lw $t0 0x1000($0)
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
Ein Befehl in Assemblersprache besteht hier aus zwei Teilen (es gibt noch kompliziertere Assemblersprachen für kompliziertere Prozessoren, aber das wollen wir hier ignorieren). Der erste Teil (hier lw
für load word) ist der Befehlsname, auch mnemonic genannt, da man sich ihn besser merken kann, als
seine Zahlendarstellung (hier wäre das die 6-Bit Zahl 23). Der Rest des Befehls sind seine Operanden.
Hier bestehen Sie aus einem Zielregister $t0 und einer Adressierung, die sich aus dem Register $0
und dem Offset 0x1000 zusammensetzt. Der Befehl bewirkt, dass die Konstante 0x1000 auf den
Inhalt des Registers $0 addiert wird. Von der entstehenden Adresse werden dann vier Bytes geladen,
die dann im Register $t0 platziert werden.
Diese textuelle Darstellung des Maschinencodes wird von einem Programm, dem Assembler in die
binäre Darstellung übersetzt. Hierzu liest der Assembler eine Assemblerdatei ein, die neben Befehlen für den Prozessor noch eine Beschreibung des Datensegments und Direktiven für den Assemblierungsvorgang selbst enthalten kann. Der Assembler verarbeitet diese Datei dann zu einer so genannten
Binärdatei (auch oft Objektdatei, vom englischen object code). Diese enthält die binäre Darstellung
der Befehle und Daten, sowie Informationen für den Linker, der mehrere solche Objektdateien zu
einem ausführbaren Programm zusammen führt. Dieser Vorgang ist in Abbildung 2.2 dargestellt.
Betrachten wir die Assemblierung an einem Beispiel. Die untenstehenden (hexadezimal kodierten)
Bytes sind, in der Interpretation der MIPS-Maschinensprache, ein Programm, das die Fakultät des
Wertes berechnet, der zu Beginn in Register $a0 steht. Für Menschen ist das kaum ersichtlich. Verwendet man allerdings die textuelle Darstellung der Befehle (mnemonics), so sieht das wie folgt aus:
Zahlen mit vorangestelltem Dollarzeichen bezeichnen Prozessorregister; Zahlen ohne Dollarzeichen
sind Konstante, die in den Befehlsstrom eingebaut sind. Wir sehen, dass jeder Befehl auf einem oder
mehreren Registern operiert und manche Befehle auch Konstante mit einbeziehen. Beispielsweise addiert der Befehl addiu $0 $2 1 die Konstante 1 auf den Inhalt des Registers $0 und legt das
18
Revision: b267af6
Tue May 15 00:34:47 2012 +0200
2.2. Der Assembler
Assembler
Datei
Assembler
Objektdatei
Assembler
Datei
Assembler
Objektdatei
Linker
Assembler
Datei
Assembler
Objektdatei
Bibliothek
Ausführbarer
Code
Abbildung 2.2.: Erstellung einer ausführbaren Datei
2c
15
24
10
00
00
24
10
03
1
2
3
4
5
6
7
8
9
88
00
02
80
44
00
84
00
e0
00
00
00
00
00
10
ff
ff
00
01
07
01
05
18
12
ff
fc
08
Abbildung 2.3.: Eine Bytefolge im Speicher
394
Ergebnis in Register $2 ab.2 Der Befehl bne spricht das Steuerwerk an: Er vergleicht die beiden
angegebenen Register. Sind ihre Inhalte ungleich, so wird der Befehlszeiger n Befehle weiter gesetzt,
wobei n die angegebene Konstante ist (in unserem Beispiel n = 7).
Nun sind gerade solche Sprungbefehle auch in dieser Darstellung mühsam vom Menschen zu kodieren. Fügt man nach dem bne beispielsweise einen Befehl ein, so muss der konstante Parameter des
bne von Hand angepasst werden. Die Konstante selbst muss durch Abzählen der Befehle ermittelt
werden. Da dies mühselig ist, wird das vom Assembler übernommen. Hierzu bietet der Assembler sogenannte Sprungmarken, oder auch nur Marken (labels) an. Das Einsetzen der korrekten Werte
übernimmt dann der Assembler. Hier nun obiges Programm, geschrieben für einen MIPS-Assembler3 .
395
1
396
2
386
387
388
389
390
391
392
393
397
3
398
4
399
5
400
6
.text
.globl fact
fact:
sltiu $t0 $a0 1
bnez $t0 end
li
$v0 1
; Setze $t0 auf 1, wenn $a0 kleiner 1, sonst 0
; Verzweige nach end, wenn $t0 ungleich 0
; Lade Konstante 1 nach $v0
2
In diesem Beispiel entspricht die Reihenfolge der Operanden der Reihenfolge der Bitfelder in ihrer Kodierung im Befehlswort. In der Assemblersprache unten verwenden wir eine intuitivere Reihenfolge.
3
Beachten Sie, dass die Reihenfolge der Operanden im Assemblertext von der Disassemblierung abweichen kann. Dies
liegt daran, dass der Disassembler die Operanden in der Reihenfolge ausgibt, wie sie im Befehlswort kodiert sind,
wohingegen der Assemblertext eine dem Menschen eingängige Reihenfolge wählt.
Revision: b267af6
Tue May 15 00:34:47 2012 +0200
19
2. Maschinensprache
1
2
3
4
5
6
7
8
9
sltiu $4
bne $8
addiu $0
beq $4
mult $2
mflo $0
addiu $4
beq $0
jr $31
$8
$0
$2
$0
$4
$0
$4
$0
$0
1
7
1
5
$0
$2
-1
-4
$0
Abbildung 2.4.: Disassemblierung der Bytes aus Abbildung 2.3
401
7
402
8
403
9
404
10
405
11
406
12
407
13
408
14
409
410
411
412
413
414
415
416
417
418
419
420
1. .text erklärt dem Assembler, dass nun ausführbarer Code folgt. Die Markierung, ob es sich
beim folgenden Inhalt um Code oder Daten handelt ist wichtig, da beim Laden des Programms
durch das Betriebssystem Code und Daten an bestimmte, festgelegte Adressen geschrieben werden.
422
423
424
2. .globl fact macht die Marke fact für andere Übersetzungseinheiten sichtbar. Das heißt,
dass der Linker in anderen Objektdateien, die Referenzen auf eine Marke fact enthalten, die
Adresse des Befehls der in dieser Übersetzungseinheit mit fact markiert ist, einsetzt. In diesem Beispiel markiert fact den Beginn der Routine, die Fakultät berechnet. Diese kann dann
von anderen Übersetzungseinheiten gerufen wird. Die Marken end und loop sind nicht global.
Ihre Namen können also in anderen Übersetzungseinheiten in anderem Kontext wiederverwendet werden, ohne dass es einen Einfluss auf diese Übersetzungseinheit hat.
425
426
427
428
429
430
431
432
434
$a0 end
; Verzweige nach end, wenn $a0 gleich 0
$v0 $v0 $a0 ; Multipliziere $a0 mit $v0 und speichere Ergebnis in $v0
$a0 $a0 -1 ; Dekrementiere $a0 um 1
loop
; Verzweige nach loop
Die Sprungmarken sind durch Bezeichner gegeben, denen ein Doppelpunkt folgt (hier im Beispiel
fact, loop und end). Sie stehen für die Adresse des markierten Datums (hier ein Befehl). Der
Assembler kann nun den passenden Versatz (Offset) zum Sprungziel selbst berechnen.
Des Weiteren fallen die Registernamen auf. Der MIPS-Prozessor hat 32 allgemeine Register, die
jeweils eine 32-Bit Zahl speichern können. Neben den 32 Allgemeinen gibt es noch 3 Spezialregister,
darunter der Befehlszeiger pc (program counter). Das erste der 32 allgemeinen Register ($0 oder
$zero in der Assemblersprache) enthält immer den Wert 0. Schreiboperationen darauf haben keine
Wirkung. Die anderen 31 haben in der Assemblersprache einen speziellen Namen (neben der bloßen Nummer), welcher der Rolle des Registers in der sogenannten Aufrufkonvention (später darüber
mehr) geschuldet ist. Beispielsweise enthält das Register $4 das erste Argument bei einem Aufruf
eines Unterprogramms. Daher hat es auch den Namen $a0 (argument 0).
Des Weiteren sind in obigem Beispiel noch zwei Assembler-Direktiven zu sehen:
421
433
loop:
beqz
mult
addiu
b
end:
ret
Weiter fällt auf, dass die mnemonics leicht abweichend sind. Beispielsweise ist die letzte Anweisung im Assembler-Text ret. Diese führt einen Rücksprung zum Aufrufer der Routine durch (englisch: return). In der Disassemblierung (siehe Abbildung 2.4 steht dort aber jr $31 $0 $0. Der
20
Revision: b267af6
Tue May 15 00:34:47 2012 +0200
2.2. Der Assembler
450
Befehl ret ist eine Pseudo-Instruktion, die auf der echten Maschine nicht existiert. Es ist ein Kurzname, den der Assembler bereit stellt, um dem Programmierer das Leben angenehmer zu machen.
Der Befehl jr entnimmt dem ersten angegebenen Register (hier $31) eine Adresse und setzt den Befehlszeiger darauf. Die anderen angegebenen Register werden ignoriert. Das Register $31 enthält, per
Konvention, die Rücksprungadresse und heißt daher in der Assemblersprache $ra (englisch: return
address).
Ein weiteres Beispiel ist der Befehl bnez $t0 end; in der Disassemblierung zuvor steht aber
bne $8 $0 7. Die 7 ist der relative Versatz für die Sprungmarke end: Man sieht leicht, dass die
Anweisung, die mit end markiert ist in der Disassemblierung sieben Befehle weiter steht. bnez
ist auch eine Pseudo-Instruktion, die den Wert des angegebenen Registers mit 0 vergleicht und bei
positivem Ausgang verzweigt. Dieser Befehl wird häufig benötigt, existiert jedoch im MIPS Befehlssatz nicht. Es existiert nur ein Befehl bne (branch not equal) der zwei Register auf Ungleichheit prüft und dann verzweigt. Da das erste Register $0 aber immer 0 enthält, bietet der Assembler
bnez reg marke als Abkürzung für bne reg $0 marke an.
Zuletzt geben wir noch ein Hauptprogramm, das unsere Routine fact aufruft. Nehmen wir an, wir
wollen beispielsweise die Fakultät von 10 auf der Konsole ausgeben.
451
1
452
2
453
3
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
454
4
455
5
456
6
457
7
458
8
459
9
460
10
461
11
.text
.globl main
.extern fact
main:
li
$a0 10
jal fact
move $a0 $v0
li
$v0 1
syscall
li
$v0 10
syscall
;
;
;
;
;
;
;
Lade 10 in Register $a0
Rufe Unterprogramm fact
Ergebnis ist in Reg $v0 -> verschiebe nach $a0
Lade Nummer des Systemaufrufs "print_int" in Reg $v0
Rufe das Betriebssystem
Lade Nummer des Systemaufrufs "exit" in Reg $v0
Dieser Systemaufruf beendet das Programm
472
Die Direktive .extern fact sagt dem Assembler, dass die Marke fact in einer anderen Datei
zu finden sein wird. Der Assembler kann also beim assemblieren dieser Datei die Adresse der Marke nicht herausfinden. Dies bewerkstelligt der Linker beim zusammenfügen der Objektdateien. Die
Marke main dient als Einsprungspunkt in das Programm. Der Befehl syscall ruft das Betriebssystem. Die Operation, die das Betriebssystem ausführen soll, wird im Register $v0 zuvor abgelegt.
Ein Wert von 1 bedeutet hier beispielsweise, dass das Betriebssystem die Zahl, die in Register $a0
steht, auf der Konsole ausgeben soll. Der Systemaufruf 10 beendet das Programm. Der Befehl li ist
eine Pseudo-Instruktion, die vom Assembler in potentiell mehrere Befehle übersetzt wird, die dann
eine 32-Bit Konstante im angegebenen Register ablegen. jal setzt den Befehlszeiger auf die Adresse
der angegebenen Marke und speichert zudem die Adresse des auf jal folgenden Befehls im Register
$ra. Somit kann die aufgerufene Routine zurückkehren, sprich die Ausführung nach jal fortführen.
473
2.2.1. Adressierung
462
463
464
465
466
467
468
469
470
471
474
475
476
477
478
479
480
Wir haben bereits gesehen, dass man mittels Marken Adressen Namen geben kann. Ob die konkrete
Adresse von Bedeutung ist, hängt vom Kontext der Marke ab. Beispielsweise verwenden bedingte
Sprungbefehle in MIPS (wie bne und beq) relative Adressen. Das heißt, sie ändern den Befehlszeiger
relativ zur aktuellen Position, in dem ein Versatz (englisch: Offset) auf ihn addiert wird. Verwendet
ein Befehl relative Adressierung, so ist die absolute Adresse irrelevant. Sind das Sprungziel und alle
Verwender der Marke in einer Übersetzungseinheit, so kann der Assembler den korrekten Versatz
direkt eintragen. Nochmals: Es ist irrelevant, an welcher konkreter Adresse der Code letztlich steht.
Revision: b267af6
Tue May 15 00:34:47 2012 +0200
21
2. Maschinensprache
502
Alle Sprungbefehle in MIPS, die relativ adressieren beginnen mit einem b für branch (verzweige).
Bei der absoluten Adressierung existiert kein Versatz, sondern eine konkrete Adresse. Beispielsweise verwenden einige Sprungbefehle bei MIPS, die mit j beginnen, absolute Adressen. Der Befehlszeiger wird einfach auf die gegebene Adresse gesetzt und nicht relativ inkrementiert. Hierbei ergibt sich
das Problem, dass der Assembler zum Zeitpunkt der Assemblierung nicht weiß, an welcher Stelle das
referenzierte Stück Code letztlich landen wird. Das Programm könnte sich aus vielen Übersetzungseinheiten (in Form von Bibliotheken) zusammensetzen. Erst beim Laden des Programms durch das
Betriebssystem steht die absolute Adresse fest. Daher füllt der Assembler die Stellen, die absolute
Adressen erfordern zunächst mit Nullen und vermerkt diese in der Objektdatei in Form einer Tabelle
(man nennt diese auf englisch auch relocation table. Der Lader setzt die absoluten Adressen dann
gemäß der Tabelle ein.
Allen Adressierungsarten ist gemein, dass die Reichweite des adressierbaren Speichers mitunter
begrenzt ist. So kann man bei MIPS beispielsweise 215 − 1 Befehle nach oben und 215 Befehle nach
unten verzweigen. Absolute direkte Sprünge erlauben bei MIPS einen Umfang von 226 Instruktionen. (entspricht einem Speicherbereich von 256 Megabyte). Sprungziele außerhalb dieses Bereichs
können nicht direkt angesprungen werden und müssen anders implementiert werden: Beispielsweise
durch mehrere hintereinander geschaltete Sprünge, oder durch das Laden der absoluten Adresse in
ein Register und einen anschließenden indirekten Sprung. Eine weitere Möglichkeit sind sogenannte
Sprungleisten. Dies sind Reihungen von Adressen, die im Datensegment liegen und die Sprungziele
beinhalten. Der Code, der den Sprung ausführt, lädt die entsprechende Adresse aus dem Datensegment und führt dann einen indirekten Sprung (bei MIPS der Befehl jalr) aus. Diese Technik kommt
vor allem beim Binden von dynamsichen Bibliotheken zur Ladezeit des Programmes zum Einsatz.
503
2.2.2. Das Datensegment
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
515
Neben dem Code enthalten viele Programme auch statische Daten. Statisch bedeutet hier, dass der
Speicherplatz jener Daten zur Übersetzungszeit bekannt ist, also nicht zur Laufzeit des Programms
variiert. Der Speicherplatz wird beim Assemblieren und Binden schon veranschlagt, sprich in der
Objektdatei vermerkt. Beim Laden des Programms wird er angefordert und bleibt bis zum Ende des
Programms reserviert. Oft sind statische Daten schon mit Initialwerten belegt. Ein typisches Beispiel
sind im Programm auftretende Zeichenketten oder Konstante größeren Umfangs (beispielsweise Reihungen von Zahlen). Zum Beispiel muss ein Programm, das die Zeichenkette Hallo Welt ausgibt,
die Zeichenkette natürlich irgendwo enthalten.
In der Assemblerdatei können statische Daten nach der Direktive .data vereinbart werden. Zum
reinen Anfordern von Platz (undefinierten Inhaltes) dient die Direktive .space n, wobei n die Anzahl der zu reservierenden Bytes ist. Um später auf die Adressen dieses Speichers Bezug nehmen zu
können, können diese Bereiche mit Marken versehen werden. Beispielsweise legt
516
1
517
2
518
3
504
505
506
507
508
509
510
511
512
513
514
519
520
521
522
523
524
525
.data
some_bytes:
.space 1000
einen Bereich von 1000 Bytes undefinierten Inhaltes an, auf dessen Adresse mit der Marke some_bytes
Bezug genommen werden kann.
Zur Bequemlichkeit des Programmierers existieren weitere Direktiven, um das Anlegen von statischem Speicher mit vordefiniertem Inhalt anzulegen. Die Direktiven .byte, .half und .word
legen Bytes, Halbwörter (MIPS Slang für 2 Bytes lange Ganzzahlen) und Wörter (4 Byte lange Ganzzahlen) an. .ascii str und .asciiz str legt ein Bereich an, der initial mit den Bytes gefüllt ist, die
den ASCII-Werten der Zeichen der Zeichenkette str entsprechen. Im Falle von .asciiz wird noch
22
Revision: b267af6
Tue May 15 00:34:47 2012 +0200
2.2. Der Assembler
527
ein Null-Byte angehängt. Diese Darstellung von Zeichenketten ist vor allem in der Programmiersprache C verbreitet. Zum Beispiel entspricht
528
1
529
2
530
3
531
der Vereinbarung
532
1
533
2
534
3
526
.data
hello:
.asciiz "Hello World"
.data
hello:
.byte 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x20, 0x57, 0x6f, 0x72, 0x6c, 0x64, 0x00
541
Für den Zugriff der Daten ist auch die sogenannte Ausrichtung (engl.: Alignment) wichtig. Die Ladebefehle des MIPS-Prozessors schreiben dies vor: Wird ein Wort (4 Bytes) geladen, so muss dessen Adresse durch 4 teilbar sein. Wird ein Halbwort geladen, so muss dessen Adresse entsprechend
durch 2 teilbar sein. Um im Datensegment eine gewisse Ausrichtung herzustellen, verwendet man die
Direktive .align n. Dies lässt das nächste Element im Datensegment an einer Adresse beginnen,
die durch 2n teilbar ist.
Zum Beispiel würde folgendes Programm
542
1
543
2
544
3
545
4
546
5
547
6
548
7
535
536
537
538
539
540
549
8
550
9
.data
.ascii "Hallo"
x:
.word 8
.text
.globl main
main:
lw $t0 x
553
zu einer Prozessorausnahme4 beim Ausführen des Ladebefehls führen, da die Adresse von x, in diesem konkreten Beispiel 0x10000005, nicht durch 4 teilbar ist. Mit korrekter Ausrichtung liest sich
das Programm so:
554
1
555
2
556
3
551
552
557
4
558
5
559
6
560
7
561
8
562
9
563
10
564
565
566
567
568
569
570
.data
.ascii "Hallo"
.align 2
x:
.word 8
.text
.globl main
main:
lw $t0 x
Es sei abschließend bemerkt, dass man in der Praxis die statischen Daten noch weiter unterteilt:
Einerseits in konstante Daten, deren Wert nicht mehr verändert werden darf. Dies wird meist dadurch
sicher gestellt, dass diese in einen Adressbereich geladen werden, der dann vom Betriebssystem als
nicht änderbar gesetzt wird. Das Einhalten dieser Einschränkung geschieht mit Hilfe des Prozessors,
genauer der virtuellen Speicherverwaltung. Andererseits unterscheidet man bei den Daten, die initialisiert und änderbar sind, solche, die zu 0 oder zu anderen Werten initialisiert werden sollen. Erstere
brauchen in der Objektdatei und der ausführbaren Datei keinen Platz zu belegen, da sie entsprechend
4
Der Prozessor kommt in einen Zustand, in der die Fortführung der Programmausführung nicht mehr möglich ist. Er
unterbricht dann die Ausführung und springt an eine spezielle Fehlerbehandlungsroutine im Betriebssystem.
Revision: b267af6
Tue May 15 00:34:47 2012 +0200
23
2. Maschinensprache
574
markiert werden. Das Betriebssystem setzt dann beim Laden des Programms den Speicher an den
entsprechenden Adressen auf 0.
Aus der Existenz der statischen Daten erklärt sich auch das Schlüsselwort static in den Sprachen
C/C++ und Java.
575
2.3. Der Linker
571
572
573
585
Der Linker fügt mehrere Objektdateien zusammen. Hierbei können auch noch Bibliotheken von dritten ins Spiel kommen (siehe Abbildung 2.2). Seine Hauptaufgabe ist die Auflösung der globalen
Marken und das Einsetzen von absoluten Adressen. Wie oben erwähnt, kann in einer Objektdatei eine Marke referenziert werden, die in einer anderen Datei vereinbart wurde. Der Linker ordnet nun
den Code und die Daten der einzelnen Objektdateien hintereinander an und erstellt somit eine Speicherabbildung. Bis auf Code und Daten aus sogenannten dynamisch gebunden Bibliotheken (engl.:
dynamic linked libraries) steht nun die Adresse jedes Befehls und jedes Datums fest. Der Code wird
vom Linker dann so angepasst, dass an den entsprechenden Stellen die korrekten Marken referenziert
werden. Dies kann bedeuten, dass er die Konstantenfelder in den Instruktionsworten umschreibt, oder
gar mehrere Befehle einfügt, um entsprechende Adressen zu generieren.
586
2.4. Das Laden des Programms und die Speicheraufteilung
576
577
578
579
580
581
582
583
584
600
Beim Laden des Programms durch das Betriebssytem werden nun Daten und der Code an die vom
Betriebssystem vorgegebenen Adressen geladen. Oft sind diese konstant: In unserem MIPS-Szenario
beginnt der Code ab Adresse 0x400000 und die Daten ab Adresse 0x10000000. Kommen dynamisch gebundene Bibliotheken zum Einsatz, so muss das Betriebssytem beim Laden des Programms
den Linker erneut starten um die entsprechenden Bibliotheken an das Programm zu binden. Dies soll
uns hier jedoch nicht weiter kümmern. Der Bereich oberhalb der statischen Daten, sprich der Bereich
beginnend ab Adresse 0x10000000+n wobei n die Größe der statischen Daten ist, ist die sogenannte Halde (engl.: Heap). Sie kann vom Programm frei genutzt werden. Eventuell muss das Programm
das Betriebssytem über den benutzten Bereich der Halde (in Form eines Pegelstands) unterrichten.
Gewöhnlich ist das bei UNIX-Systemen so. Am Ende der Halde beginnt der Laufzeitkeller, mit dem
die Aufrufschachteln (engl.: call frames) für Unterprogramm-Aufrufe verwaltet werden (siehe nächstes Kapitel). Halde und Keller laufen sich also entgegen. Je nach Beschaffenheit des Betriebssytems
erzeugt dieses eine Ausnahme, wenn die Halde den Keller übertritt. Abbildung 2.5 zeigt die Speicherabbildung.
601
2.4.1. Speicherverwaltung auf der Halde
587
588
589
590
591
592
593
594
595
596
597
598
599
602
603
604
605
606
607
608
609
610
Die detaillierte Verwaltung der Halde übernimmt das Laufzeitsystems er Programmiersprache, nicht
das Betriebssystem. Das Laufzeitsystem der Sprache, das meist in einer Bibliothek mit dem Übersetzer ausgeliefert wird, verwaltet die belegten und freien Speicherbereiche darin. Herkömmlicherweise
kann der Programmierer mittels Unterprogrammaufruf (in C zum Beispiel malloc) oder eigenem
Sprachkonstrukt (in C++ und Java der Operator new) Speicher in der Halde belegen. Die Freigabe
des Speichers erfolgt entweder auch über Unterprogrammaufrufe in die Laufzeibibliothek (In C/C++
via free und delete) oder automatisch (wie in Java), wenn das Laufzeitsystem erkennt, dass ein
einst angeforderter Speicherblock nicht mehr benötigt wird (da keine Referenzen mehr auf ihn existieren). Eine solche automatische Speicherbereinigung setzt eine starke Typisierung der Sprache voraus,
24
Revision: b267af6
Tue May 15 00:34:47 2012 +0200
2.5. Aufruf von Unterprogrammen
611
612
613
614
615
616
617
da das Laufzeitsystem von jeder Variable klar sagen können muss, ob es sich um eine Referenz (Zeiger) handelt, oder nicht. In unserer Assemblersprache haben wir überhaupt keine Verwaltung des
Speichers auf der Halde; wir müssen selbst dafür sorgen, dass kein Unterprogramm Speicher auf der
Halde überschreibt, der noch in Benutzung ist. Dies ist in größeren Programmen mit vielen Unterprogrammen, die teils nur in Form von Bibliotheken vorliegen (sprich, der Quelltext nicht vorhanden
ist), extrem fehleranfällig und ohne zentralisierte Speicherverwaltung (à la malloc/free oder sogar
automatischer Speicherbereinigung) fast nicht machbar.
0x7fffffff
Keller
Dynamische Daten
Statische Daten
0x10000000
Code
0x400000
reserviert
Abbildung 2.5.: Speicheraufteilung
618
2.5. Aufruf von Unterprogrammen
631
Sobald wir größere Programme schreiben, werden wir sie modularisieren. Das bedeutet, dass wir
sie in viele Unterprogramme aufteilen, die wir in mehrere Übersetzungseinheiten gruppieren. Dies
ist technisch nicht notwendig (man könnte alles auch in eine Riesendatei schreiben und auf Unterprogramme verzichten, was dann zum berüchtigten Spaghetti-Code führt). Jedoch sind solche Programme
nicht mehr überblickbar, testbar, daher nicht wartbar und für die Fehlersuche ungeeignet. Insbesondere wenn mehrere Personen an einem Programm arbeiten führt dies unweigerlich zur Katastrophe.
Daher versuchen wir ein größeres Programm in kleinere Moduln zu unterteilen. Dies gilt natürlich
für Programme in Hochsprachen aber erst recht auch für die Assemblerprogrammierung. Damit die
einzelnen Moduln zusammenspielen, brauchen wir eine klar definierte Schnittstelle zwischen den einzelnen Unterprogrammen, die systemweit eingehalten wird. Dies betrifft vor allem die Aufteilung der
Registerbank (siehe unten).
Betrachten wir wieder unseren MIPS-Assembler. Als einführendes Beispiel betrachten wir ein kleines Unterprogramm, das ein anderes aufruft:
632
1
619
620
621
622
623
624
625
626
627
628
629
630
.text
Revision: b267af6
Tue May 15 00:34:47 2012 +0200
25
2. Maschinensprache
633
2
634
3
635
4
636
5
637
6
638
639
640
641
642
643
644
645
646
647
648
649
foo:
addu $t0 $a0 $a1
jal bar
addu $v0 $v0 $t0
ret
Nehmen wir an, das gerufene Unterprogramm bar ist in einer anderen Überseztungseinheit vereinbart, die wir nicht kennen. Wir wissen also nichts über die Routine bar. Wie können wir also davon
ausgehen, dass im Register $t0 nach dem Aufruf an bar immer noch der Wert steht, den wir zuvor
dort abgelegt haben, sprich, dass bar das Register $t0 nicht selbst verwendet? Konservativerweise
müssen wir davon ausgehen, dass der Inhalt von $t0 zerstört ist. Da wir bar nicht kennen, müssen
wir annehmen, dass es $t0 selbst verwendet. Also müssen wir den Inhalt von $t0 retten, bevor wir
bar aufrufen. Dasselbe gilt auch für die Rücksprungadresse von foo, die im Register $ra liegt. Ein
Möglichkeit dies zu tun, wäre, den Inhalt von $t0 an eine fest vereinbarte Speicherstelle im statischen
Bereich des Datensegments zu schreiben. Dies funktioniert allerdings nicht für rekursive Funktionen:
Wird eine Routine rekursiv gerufen (dies kann auch indirekt geschehen: Routine f ruft g, die dann
wieder f ruft), so sind mindestens zwei Instanzen der Routinen gleichzeitig aktiv. Diese beiden Instanzen würden sich dann den einen Speicherplatz streitig machen, wie zu sehen in Abbildung 2.6. Ein
zweiter Aufruf an f überschreibt das an Stelle p gesicherte $t0. Wir müssen also für jeden Aufruf
f
...
sw $t0 p
jal g
g
f
...
jal f
...
sw $t0 p
jal g
lw $t0 p
...
ret
...
ret
lw $t0 p
...
ret
Abbildung 2.6.: Rekursiver Aufruf von f
650
657
der Routine foo einen separaten Speicherplatz zur Sicherung von $t0 bereitstellen. Da eine gerufene
Routine immer vor ihrem Aufrufer zurückkehrt, können wir diesen Speicher als Keller (engl.: Stack)
organisieren. Sobald foo angesprungen wird, erzeugt es auf diesem Keller eine Region, genannt die
Aufrufschachtel (engl.: stack frame), in der durch Aufrufe potentiell zerstörte Registerinhalte abgelegt
werden können. Dieses Anlegen geschieht durch dekrementieren des Kellerpegels (engl.: stack pointer) $sp; der Keller wächst nach unten. Bevor foo zurückkehrt, wird die Aufrufschachtel beseitigt,
indem der Kellerpegel auf den Stand bei Eintritt zurückgesetzt wird:
658
1
659
2
660
3
661
4
662
5
663
6
664
7
651
652
653
654
655
656
26
.text
foo:
addu
addu
sw
sw
jal
$sp
$t0
$t0
$ra
bar
$sp -8
$a0 $a1
0($sp)
4($sp)
; Dekrementieren des Kellerpegels, Anlegen der Schachtel
; Sichern des Inhalts von $t0 in die Schachtel
; Sichern der Ruecksprungadresse
Revision: b267af6
Tue May 15 00:34:47 2012 +0200
2.5. Aufruf von Unterprogrammen
665
8
666
9
667
10
668
11
669
12
670
671
672
673
674
675
lw
lw
addu
addu
ret
$ra
$t0
$v0
$sp
4($sp)
0($sp)
$v0 $t0
$sp 8
; Restaurieren der Ruecksprungadresse
; Restaurieren des Werts von $t0
; Zuruecksetzen des Kellerpegels
Da der Kellerpegel sich innerhalb eines Unterprogramms auch weiter verändern kann (aufgrund weiterer Speicherallokation und Unterprogrammaufrufe), ist es manchmal zweckmäßig, einen eigenes Register für die Aufrufschachtel zu veranschlagen. Dies ist der sogenannte Schachtelzeiger (engl.: frame
pointer). Dieses Register ist vom MIPS-Assembler mit dem Namen $fp belegt. Der Kellerpegel zeigt
somit immer an den unteren Rand des Kellers, der Schachtelzeiger auf die erste Adresse oberhalb der
Aufrufschachtel. Die Aufrufschachtel enthält folgende Elemente (siehe auch Abbildung 2.7):
677
• An das Unterprogramm übergebene Werte (bei MIPS werden die ersten vier Argumente in den
Registern $a0–$a3 übergeben. Die restlichen Argumente werden auf dem Keller übergeben.
678
• Speicherplatz um Inhalte von Registern zu sichern, die
681
682
683
684
685
2. die potentiell von aufgerufenen Unterprogrammen zerstört würden. Hier spricht man von
caller-saved Registern, da der Aufrufer die Inhalte retten muss.
• Platz für weitere lokale Variablen des Unterprogramms, die nicht in Register passen, da zu
wenig Register verfügbar sind, oder die Variablen zu groß sind oder adressierbar sein müssen.
Argument #5
Gesicherte Register
Argument #4
Schachtelzeiger
Lokale Variablen
Richtung des
Kellerwachstums
680
1. die Routine ändern möchte, wobei aber der Aufrufer aber erwartet, dass die Inhalte jener
Register aber unverändert bleiben (man spricht hier auch von callee-saved Registern, da
sie der Aufgerufene sichern muss.
Aufrufer
679
Aufgerufener
676
Weiteres
Kellerpegel
Abbildung 2.7.: Aufrufschachtel
686
687
688
Die Aufrufschachtel wird teils vom Aufrufer (er legt Argumente auf den Keller), teils vom Aufgerufenen aufgebaut. Der Aufruferteil der Aufrufschachtel wird im Prolog auf- und im Epilog wieder
abgebaut. Für den MIPS-Assembler sieht das so aus:
Revision: b267af6
Tue May 15 00:34:47 2012 +0200
27
2. Maschinensprache
f:
689
1
690
2
691
3
692
4
693
5
694
6
695
7
696
2.5.1. Verwendung der Registerbank
697
698
; Prolog
addiu $sp $sp -platz
...
; Epilog
addiu $sp $sp platz
ret
; reserviere Platz auf dem Keller
; freigeben des Platzes
Ein wesentlicher Teil der Aufrufkonvention betrifft die Verwendung der Registerbank. Hierbei werden
die 32 vorhanden Allzweckregister wie folgt unterteilt:
• Die Register $at, $k0, $k1 sind dem Assembler und dem Betriebssystem vorbehalten und
dürfen vom Programmierer nicht verwendet werden.
699
700
• Die Register $a0-$a3 werden dazu verwendet, die ersten vier Argumente eines Unterprogrammes bei dessen Aufruf zu enthalten. Weitere Argumente werden auf dem Keller übergeben
(siehe unten).
701
702
703
• Die Register $t0-$t9 überleben einen Unterprogrammaufruf nicht notwendigerweise. Werte, die einen Unterprogrammaufruf überdauern wollen müssen vom Aufrufer vorher gesichert
werden.
704
705
706
• Die Register $s0-$s7 überleben einen Unterprogrammaufruf. Verwendet der Aufgerufene eines dieser Register, so muss er dessen Inhalt vorher sichern. Bei der Rückkehr eines Unterprogramms haben diese Register denselben Inhalt wie vor dem Aufruf.
707
708
709
712
• Das Register $gp zeigt auf die Adresse 0x10008000. Somit können die ersten 64 Kilobyte
des Datensegments leicht relativ zu $gp angesprochen werden, ohne absolute Adressen mit
mehreren Befehlen generieren zu müssen. Siehe auch 2.2.2.
713
• Das Register $sp ist der Kellerpegel. Er zeigt auf die untere Grenze des Laufzeitkellers.
710
711
• Das Register $ra enthält die Adresse zu der die aufgerufene Routine zurückkehren soll. Es
wird von den Sprunginstruktionen jal und jalr gesetzt. Ruft eine Routine weitere Unterprogramme auf, so muss sie $ra zwangsläufig sichern.
714
715
716
717
2.6. Einführung in den MIPS Befehlssatz
723
Wie bei jedem Mikroprozessor lassen sich die Befehle bei MIPS in Befehle für das Rechen- und
Befehle für das Steuerwerk aufteilen. Die Befehle operieren auf den 32 Allzweckregistern, sowie
auf den beiden Spezialregistern lo und hi, die bei Multiplikation und Division eine Rolle spielen.
Zu beachten ist, dass Register $0 fest auf den Wert 0 verdrahtet ist; jede Schreiboperation darauf
hat keinen Effekt. Somit ist die Konstante 0 immer leicht als Operand zu erhalten. Dies ist nur eine
Kurzübersicht. Eine vollständige Dokumentation der Befehle findet sich in [Price(1995)].
724
2.6.1. Rechenwerk
718
719
720
721
722
725
726
Die Befehle des Rechenwerks lassen sich in Bitoperationen, normale Arithmetik, Vergleichsbefehle
und Speicherbefehle kategorisieren.
28
Revision: b267af6
Tue May 15 00:34:47 2012 +0200
2.6. Einführung in den MIPS Befehlssatz
727
Arithmetik
747
Die Additions- und Subtraktionsbefehle sind in mehreren Varianten verfügbar. add addiert zwei Register und legt das Ergebnis in einem dritten ab. Tritt hierbei ein Überlauf auf, das heißt, dass das
Ergebnis außerhalb {−231 , . . . , 231 − 1} liegt, so löst der Prozessor eine Ausnahme aus. Dies kann
durch die Verwendung des Befehls addu (add unsigned) vermieden werden. Die ausgeführte Operation ist dieselbe, es wird nur keine Ausnahme beim Überlauf ausgelöst. Für beide Befehle existieren
noch zwei Varianten (addi und addiu), die eine ins Befehlswort eingebettete 16-Bit Konstante
auf ein Register addiert und das Ergebnis in einem zweiten ablegt. Die eingebettete Konstante wird
auf 32 Bit vorzeichenerweitert (siehe Anhang ??). Selbiges gilt für sub, subu, subi, subiu. So
läst sich mittels addiu auf das Null-Register $0 eine vorzeichenbehaftete 16-Bit Konstante in ein
Register laden.
Die Befehle für die Multiplikation und Division weichen leicht vom Drei-Register Schema ab.
Sie nehmen zwei Register als Operand schreiben ihre Ergebnisse aber in die Spezialregister lo und
hi. Die Multiplikation mulu legt in hi die oberen 32 Bit und in lo die unteren 32 Bit des potentiell
64 bittigen Ergebnisses der Multiplikation. Die Division div legt in lo das Ergebnis der Division und
in hi den Rest der Division ab. Beide Befehle interpretieren ihre Operanden als vorzeichenbehaftete
Zweierkomplement-Zahlen (siehe Anhang ??). Da für die vorzeichenlose Multiplikation und Division
andere Algorithmen notwendig sind, wie für die vorzeichenbehaftete, existieren mit divu und multu
zwei Befehle, die ihre Operanden als vorzeichenlose Zahlen interpretieren. Mit den Befehlen mflo,
mfhi kann man den Inhalt von lo und hi in ein Allzweckregister kopieren; mittels mtlo, mthi
kann man den Inhalt eines Allzweckregisters nach lo und hi kopieren (dies sollte fast nie nötig sein).
748
Bitoperationen
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
763
Es existieren Befehle für die Standard-Operationen and, or, xor. Eine Spezialität ist wohl der Befehl
nor, der das bitweise Komplement einer Veroderung berechnet (sprich a ∨ b). Durch ein nor von a
mit 0 lässt sich dann das einfache Bitkomplement (a) herstellen.
Für and, or und xor existieren auch die Versionen andi, ori, xori, die ein Register mit einer
in das Befehlswort eingebetteten 16-Bit Konstanten verknüpfen. Diese Konstante wird, im Gegensatz
zu den Arithmetikbefehlen, nicht vorzeichenerweitert. So läst sich mittels ori auf das Null-Register
$0 eine vorzeichenlose 16-Bit Konstante in ein Register laden.
Um das Laden von 32-Bit Konstanten zu vereinfachen, existiert noch der Befehl lui, der die 16Bit Konstante in die oberen 16 Bit des Zielregisters schreibt und dessen untere 16 Bit auf 0 setzt. Mit
einem anschließenden ori kann dann die untere Hälfte gesetzt werden.
Zuletzt sind noch die Schift-Operationen (engl.: bit shifts) zu erwähnen, die die herkömmlichen Bitschifts durchführen. sll, srl und sra shiften um eine (5-Bit Konstante) nach links, nach rechts mit
Nullen füllend und nach rechts, das Vorzeichenbit erweiternd. Zu jedem Befehl gibt es eine Version
(sllv, srlv, srav), bei der der Schiebebetrag aus einem Register – nicht aus einer im Befehlswort
eingebauten Konstanten – gelesen wird.
764
Vergleiche
749
750
751
752
753
754
755
756
757
758
759
760
761
762
765
766
767
768
769
Der Befehl slt vergleicht den Inhalt des ersten Registeroperanden mit dem Inhalt des zweiten Registeroperanden. Ist dieser kleiner, so wird 1 in das Ergebnisregister geschrieben, andernfalls 0. Weitere
Varianten dieses Befehls existieren: slti vergleicht mit einer vorzeichenerweiterten Konstante aus
dem Befehlswort. sltu interpretiert die Operanden als vorzeichenlose Zahlen und sltiu kombiniert die beiden letzten Eigenschaften.
Revision: b267af6
Tue May 15 00:34:47 2012 +0200
29
2. Maschinensprache
770
771
772
773
774
775
776
777
778
779
Speicheroperationen
Die Operationen lw, lh,lb laden ein Wort (4 Bytes), Halbwort (2 Bytes) oder Byte aus dem Speicher. Es findet bei lh und lb eine Vorzeichenerweiterung des geladenen Wertes auf 32 Bit statt. Des
Weiteren muss die Adresse an der entsprechenden Größe ausgerichtet sein: Werden n Bytes geladen,
muss die Adresse durch n teilbar sein. Die Adresse kommt durch ein Basisregister und einen 16 bittigen, vorzeichenbehafteten Versatz zustande. Soll keine Vorzeicherweiterung stattfinden, so stehen die
Befehle lhu und lbu zur Verfügung.
Die Operationen sw, sh, sb speichern jeweils ein Wort, Halbwort oder Byte. Auch hier müssen
die Adressen ausgerichtet sein.
Folgende Adressierungen können eingesetzt werden:
Adressierung
(Register)
Versatz
Versatz(Register)
Marke
Marke ± Versatz
Marke ± Versatz(Register)
780
781
Einige Beispiele:
782
1
783
2
784
3
785
4
786
5
787
6
788
7
789
8
790
791
9
792
10
793
11
794
12
Adressberechnung
Inhalt des Registers
Versatz
Registerinhalt + Versatz
Adresse von Marke
Adresse der Marke ± Versatz
Adresse der Marke ± Registerinhalt + Versatz
.data
p:
.word 0, 4, 8, 12
.text
; Lade von Adresse p+4, sprich den Wert 4.
lw
$t0 p+4
; Addiere auf p den Inhalt von $t0 und lade.
Laedt den Wert 4.
lw
$t1 p+0($t0)
; Addiert 4 auf p und dann den Inhalt von $t0. Also wird 8 geladen.
lw
$t2 p+4($t0)
799
Eine komplexere Adressierung wie lw $t2 p+4($t0) kann vom Assembler in mehrere Befehle
übersetzt werden: Nehmen wir an, die Marke p markiert die Adresse 0x10000000. Die oberen
16 Bit von 0x10000000 werden per lui in ein temporäres Register ($at) geladen. Der Assembler
reserviert dieses Register für genau diesen Zweck. Anschließend wird $t0 auf $at addiert. Die
Addition von 4 kommt dann durch die Konstante im Ladebefehl zu stande:
800
1
801
2
802
3
795
796
797
798
lui $at 0x1000
addu $at $at $t0
lw
$t2 4($at)
804
Die Konstanten in den Befehlen lui und lw können erst zur Zeit des Linkens vom Linker eingesetzt
werden, da er erst die finale Adresse der Marke p festlegt.
805
2.6.2. Steuerwerk
806
Bedingte Sprünge
803
807
808
Die Befehle beq und bne (branch if equal, not equal) vergleichen zwei Operandenregister auf Gleichheit bzw. Ungleichheit und verzweigen bei positivem Vergleichsergebnis. Die Befehle bltz, blez,
30
Revision: b267af6
Tue May 15 00:34:47 2012 +0200
2.6. Einführung in den MIPS Befehlssatz
817
bgtz, bgez (branch if less than zero, less than or equal . . . ) vergleichen ein Operandenregister gegen
0 (kleiner, kleiner gleich, größer, größer gleich) und verzweigen bei positivem Vergleichsergebnis.
Das Sprungziel ist durch den aktuellen Befehlszeiger und den Versatz, der aus der im Befehlswort
eingebauten Konstante gebildet wird, gegeben. Die 16-Bit Konstante aus dem Befehlswort wird vorzeichenerweitert und um 2 nach links geschiftet, da das Sprungziel an einer durch 4 teilbaren Adresse
stehen muss: Jeder Befehl ist 4 Byte lang und muss an einer durch 4 teilbaren Adresse stehen. Das
Ergebnis wird auf den Befehlszeiger addiert, der Sprung ist relativ. Mit den b... Befehlen kann
32768 Befehle nach oben und 32767 Befehle nach unten gesprungen werden. Mittels beq $0 $0
marke kann leicht ein unbedingter, relativer Sprung gebaut werden.
818
Unbedingte Sprünge
809
810
811
812
813
814
815
816
821
Der Befehl j springt unbedingt an eine absolute Adresse. Diese ist als 26-Bit Konstante im Befehlswort dieses J-Form Befehls eingebaut. Der Befehl jr (jump register) liest eine Adresse aus einem
Allzweckregister und springt dorthin.
822
Unterprogrammaufruf
819
820
832
Der Befehl jal (jump and link) springt an eine absolute Adresse (sie ist als eingebaute 26-Bit Konstante des J-Form Befehls gegeben), schreibt jedoch die Adresse des auf ihn folgenden Befehls in das
Register $31, das im Assembler auch mit $ra (return address) angesprochen werden kann. jalr
liest eine Adresse aus einem Register, springt dorthin und sichert die Adresse des auf ihn folgenden
Befehls in einem anderen Register (nicht notwendigerweise $31).
Die beiden J-Form Befehle j und jal arbeiten mit absoluten Adressen. Die 26-Bit Konstante
wird zunächst um 2 Bits nach links geschoben, da man nur an Instruktionen springen kann, die an
Adressen ausgerichtet sind, die durch 4 teilbar sind. Die obersten vier Bit werden vom Befehlszähler
des Sprungbefehls übernommen. Somit wird der Speicher in 16 Blöcke à 256 Megabyte unterteilt
innerhalb derer man springen kann.
833
2.6.3. Unterstützung vom Assembler
823
824
825
826
827
828
829
830
831
839
Der Assembler bietet zusätzlich zu den in Hardware vorhandenen Befehle noch weitere Pseudoinstruktionen, die er aus den vorhandenen konstruiert. Im Anhang ?? sind die unterstützten Pseudoinstruktionen aufgeführt.
Des Weiteren hilft er bei der Adressierung: Sprungmarken können anstelle von relativen und absoluten Adressen verwendet werden. Marken, die Daten im Datensegment beschreiben, werden vom
Assembler in die entsprechenden Operanden für die Lade- und Speicherbefehle umgesetzt.
840
2.6.4. Schnittstelle zum Betriebssystem
834
835
836
837
838
841
842
843
844
845
Um aus einem Programm in das Betriebssystem zu springen, verfügt der MIPS-Prozessor über die
Instruktion syscall. Das Spielzeugbetriebssytem unseres MIPS-Simulators hat folgende Konventionen für einen Systemaufruf: Die auszuführende Operation liegt in Register $v0. Die Register $a0
und $a1 werden zur Übergabe weiterer Argumente verwendet. Die Tabelle in Anhang ?? zeigt die
Liste der in unserem Simulator vorhanden Systemaufrufe.
Revision: b267af6
Tue May 15 00:34:47 2012 +0200
31
846
A. Notationen
f [x 7→ y]
847
if c then x else y
(
y
falls x0 = x
x0 7→
f (v) andernfalls
(
x
y
falls c
andernfalls
33
848
849
B. Syntax und Semantik der wichtigsten
MIPS-Befehle
opcode, funct ∈ B6
rd , rs, rt, sa ∈ B5
850
B.1. Arithmetik
851
B.1.1. Mit drei Registeroperanden
funct $rd $rs $rt
imm ∈ B16
addr ∈ B26
load _word (m, pc) = OOOOOO · rs · rt · rd · OOOOO · funct
r0 = r[rd 7→ f (r(rs), r(rt))][0 7→ 0] m0 = m pc 0 = pc add32 4
(r, m, pc)
(r0 , m0 , pc 0 )
852
853
funct
mnemonic
f (s, t)
4
6
7
33
35
36
37
38
39
42
43
sllv
srlv
srav
addu
subu
and
or
xor
nor
slt
sltu
t31−s:0 · Os
Os · t31−s:0
ts31 · t31−s:0
s add32 t
s sub32 t
s and t
s or t
s xor t
s or t
O31 · (s lt32 t)31
O31 · (s ltu32 t)31
B.1.2. Bitschifts mit konstantem Schift
funct $rd $rt sa
load _word (m, pc) = OOOOOO · OOOOO · rt · rd · sa · funct
r0 = r[rd 7→ f (sa, r(rt))][0 7→ 0] m0 = m pc 0 = pc add32 4
(r, m, pc)
(r0 , m0 , pc 0 )
854
855
funct
mnemonic
f (s, t)
0
2
3
sll
srl
sra
t31−s:0 · Os
Os · t31−s:0
ts31 · t31−s:0
B.1.3. Arithmetik mit konstantem Operand
opcode $rt $rs imm
load _word (m, pc) = opcode · rs · rt · imm
r0 = r[rt 7→ f (r(rs), imm)][0 7→ 0] m0 = m pc 0 = pc add32 4
(r, m, pc)
(r0 , m0 , pc 0 )
35
B. Syntax und Semantik der wichtigsten MIPS-Befehle
856
857
opcode
mnemonic
f (s, i)
9
10
11
12
13
14
15
addiu
slti
sltiu
andi
ori
xori
lui
s add32 sext32
16 (i)
31
O · (s lt32 sext32
16 (i))31
O31 · (s ltu32 sext32
16 (i))31
s and zext32
(i)
16
s or zext32
16 (i)
s xor zext32
16 (i)
i · O16
B.2. Lade- und Speicherbefehle
load _word (m, pc) = opcode · rs · rt · imm
0
r0 = r[rt 7→ l(m, r(rs) add32 sext32
16 (imm))][0 7→ 0] m = m
opcode $rt imm($rs)
(r, m, pc)
858
mnemonic
l(m, a)
32
36
33
37
35
lb
lbu
lh
lhu
lw
sext32
8 (m(a))
zext32
8 (m(a))
sext32
16 (m(a) · m(a + 1))
zext32
16 (m(a) · m(a + 1))
m(a) · m(a + 1) · m(a + 2) · m(a + 3)
opcode $rt imm($rs)
load _word (m, pc) = opcode · rs · rt · imm
0
m0 = s(m, r(rs) add32 sext32
16 (imm), r(rt)) pc = pc add32 4
(r, m, pc)
859
860
(r0 , m0 , pc 0 )
opcode
r0 = r
pc 0 = pc add32 4
(r0 , m0 , pc 0 )
opcode
mnemonic
s(m, a, v)
40
41
43
sb
sh
sw
m[a 7→ v0:7 ]
m[a 7→ v0:7 ][a + 1 7→ v8:15 ]
m[a 7→ v0:7 ][a + 1 7→ v8:15 ][a + 2 7→ v16:23 ][a + 3 7→ v24:31 ]
B.3. Sprungbefehle
opcode $rt $rs
r0 = r
load _word (m, pc) = opcode · rt · rs · imm
m0 = m pc 0 = pc add32 (if c(r(rs), r(rt)) then sext30
16 (imm) · OO else 4)
(r, m, pc)
(r0 , m0 , pc 0 )
opcode
mnemonic
c(s, t)
4
5
beq
bne
s=t
s 6= t
861
862
opcode $rt
r0 = r
m0 = m
load _word (m, pc) = opcode · rs · OOOOO · imm
pc 0 = pc add32 (if c(r(rs), r(rt)) then sext30
16 (imm) · OO else 4)
(r, m, pc)
(r0 , m0 , pc 0 )
opcode
mnemonic
c(s, t)
6
7
blez
bgtz
sn (s) ≤ 0
sn (s) > 0
863
864
j
865
jal
36
load _word (m, pc) = OOOOLO · addr
r0 = r m0 = m pc 0 = pc 31:28 · addr · OO
(r, m, pc)
(r0 , m0 , pc 0 )
load _word (m, pc) = OOOOLL · addr
r0 = r[31 7→ pc add32 8] m0 = m pc 0 = pc 31:28 · addr · OO
(r, m, pc)
(r0 , m0 , pc 0 )
Revision: b267af6
Tue May 15 00:34:47 2012 +0200
B.3. Sprungbefehle
866
load _word (m, pc) = OOOOOO · rs · O15 · OOLOOO
r0 = r m0 = m pc 0 = rs
jr
(r, m, pc)
(r0 , m0 , pc 0 )
867
load _word (m, pc) = OOOOOO · code · OOLLOO
r0 = r m0 = m pc 0 = pc add32 4
syscall
(r, m, pc)
(r0 , m0 , pc 0 )
Revision: b267af6
Tue May 15 00:34:47 2012 +0200
37
868
869
870
871
872
873
874
875
876
Literaturverzeichnis
[Nielson and Nielson(1992)] Hanne Riis Nielson and Flemming Nielson.
Semantics with applications: a formal introduction. John Wiley & Sons, Inc., New York, NY, USA, 1992. ISBN 0-471-92980-8. URL
http://www.daimi.au.dk/ bra8130/Wiley_book/wiley.html.
[Price(1995)] Charles Price. The MIPS IV Instruction Set. Silicon Graphics Computer Systems, Januar 1995. URL
http://www.cs.cmu.edu/afs/cs/academic/class/15740-f97/public/doc/mips-isa.pdf.
[von Neumann(1993)] John von Neumann.
First draft of a report on the EDVAC.
IEEE Ann. Hist.
Comput., 15(4):27–75, 1993.
ISSN 1058-6180.
doi: http://dx.doi.org/10.1109/85.238389.
URL
http://qss.stanford.edu/ godfrey/vonNeumann/vnedvac.pdf.
39