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