Wirtschaftsinformatik (Bachelor-Studiengang): Rechnerarchitketur & Betriebssysteme (1. Semester)

Sie sind hier: StartseiteWirtschaftsinformatikRechnerarchitektur/Betriebssysteme: Maschinen-Ebenen

BM / CM, Kurs vom 01.04.2002 - 30.09.2002

Rechnerarchitektur/Betriebssysteme: Maschinen-Ebenen: Konventionelle Maschinenebene (Grundsätzliche Arten von Speicherinhalten, Zeiger, Pointer und Adressen, Operationsmodi, CPU-Register für Programme), Stack (Stapel, Keller) (Stack-Aufbau, Subroutinen, Lokale Variablen und Parameter auf dem Stack), Assembler, Compiler, CISC - RISC.

  1. Konventionelle Maschinenebene
  2. Stack (Stapel, Keller)
  3. Assembler
  4. Compiler
  5. CISC - RISC

Konventionelle Maschinenebene

Die Konventionelle Maschinenebene ist die Ebene, auf der eine CPU die Instruktionen aus dem Arbeitsspeicher (RAM) holt, diese dekodiert und ausführt (interpretiert).

Ebenenaufbau

Bildbeschreibung "Ebenenaufbau": Von oberster zu unterster Ebene: Anwendungen (realisierte Anwendungsprogramme), Betriebssystem (realisierte Komponenten des Betriebssystems), Compiler (Programme höherer Programmiersprachen), Assembler (Assembler-Maschinenprogramme), Maschine (CPU-Maschinenprogramme), Hardware (Aufbau der Hardware: CPU, Bus, I/O-Geräte), Physik (Technologien, z.B. DTL, TTL). Die Hardware-Ebene ist weiter unterteilbar in die drei Ebenen (von oben nach unten) Komponenten, Bausteine, Gatter.

Ein direkt von der CPU ausführbares Programm heißt Binärprogramm oder Programm im Maschinencode; verkürzt: Maschinencode.

Binärprogramme sind interne Repräsentationen der Instruktionen und lassen sich nur schwer vom Menschen sichtbar machen (Repräsentation); noch schwieriger ist ihre Erstellung.

Die Präsentation von Binärprogrammen erfolgt meistens in hexadezimaler (Basis 16) oder oktaler (Basis 8) Form.

Um diese Folien leichter lesen zu können, wird eine symbolische Ersatzdarstellung gewählt (die einem fiktiven Assembler entspricht).

Maschinencode und seine Präsentation
Präsentation in Hexadezimal Präsentation in symbolischer Darstellung (Assembler)
Adresse
(jeweils die Adresse
des ersten Bytes)
Maschinencode
(jeweils vier Bytes)
Adresse Maschinencode
00000 34A3 56ee 00000 load R3,@56ee Register R3 = Inhalt von 56ee
00004 67A2 1000 00004 load R2,#1000 Register R2 = 0x1000
00008 7F23 1200 00008 add R2,R3 Register R2 = R2 + R3
0000E 0124 2300 0000E sub R2,R4 Register R2 = R2 - R4
00010 66A3 56ee 00010 store R3,@56ee Inhalt von 56ee = Register R3
. . . . . . . . . . . .

Grundsätzliche Arten von Speicherinhalten

An einer Speicherstelle wird ein Wert folgender Typen abgeklegt (genauer: die Repräsentation bzw. Codierung):

  1. Ganze Zahl (Integer, int)
  2. Rationale Zahl (Fest- und Fließpunkt)
  3. Zeichen (Character, char)
  4. Instruktion
  5. Adresse einer anderen Speicherstelle

1. bis 3. wurden schon behandelt; 4. betrifft die Speicherung von Befehlen, während 5. Pointer betrifft.

Pointer = Variable (Speicherzelle) mit den Werten von Adressen
Zeiger = Pointer (Deutsche Übersetzung)
Adresse = Nummer einer Speicherzelle im RAM

Aufbau von Instruktionen (Befehlen):

Prinzipieller Aufbau eines Befehls:

Aufbau eines Befehls

Bildbeschreibung "Aufbau eines Befehls": Eine Instruktion (auch "Befehl" genannt) setzt sich zusammen aus Operation und Operand. In einem Befehl können mehrere Operanden aufeinander folgen. Somit gibt es verschiedene Arten von Befehlen: 0-Adress-Befehl = 0 Operanden, 1-Adress-Befehl = 1 Operand, 2-Adress-Befehl = 2 Operanden, 3-Adress-Befehl = 3 Operanden.

Arten von Operanden:

Die Fälle werden durch die Adressierungsarten unterschieden:

Adressierungsarten

Bildbeschreibung "Adressierungsarten": Repräsentation (Codierung) der Operation durch Opcode (Operationscode). Operand setzt sich zusammen aus Codierung der Adressierungsart und Registernummer. In der Praxis hängen mögliche Adressierungsarten von der Operation ab. Die Adressierungsart bestimmt den Aufbau des gesamten Befehls.

Aufbau von Befehlen (Beispiel):

LOAD R3,#00FF (Lade die Konstante 0x00FF ins Register R3)

Aufbau von Befehlen

Bildbeschreibung "Aufbau von Befehlen": Der 8 bit Opcode enthält die Codierung [0x15]. Der 8 bit Operand 1 setzt sich aus Modus 0 (Register) [0x0] und Registernummer 3 [0x3] zusammen. Der 16 bit Operand 2 enthält [0x00FF]. Der endgültige Befehl setzt sich somit aus Opcode, Operand 1 und Operand 2 zusammen (0x15 | 0x03 | 0x00FF).

Zeiger, Pointer und Adressen

Zeiger, Pointer und Adressen

Bildbeschreibung "Zeiger, Pointer und Adressen": Variable P (Typ Adresse) verweist / referenziert auf Variable V (Typ Integer).

Adressen sind die "Namen" von Speicherstellen, an denen sich Variablen befinden.

Variablen mit Adressen als Werte werden Pointer (Zeiger) genannt. Diese Variablen werden analog zu den anderen (int ...) benutzt, wobei die Operationen anders sind:

Dereferenzieren = Den Inhalt eines Zeigers holen und den Wert der Variablen an dieser Adresse holen.

Eine Adresse ist gültig, wenn ein Dereferenzieren sinnvoll ist - das ist es nur dann, wenn an der Adresse (Wert des Pointer) eine Variable sich befindet. Ansonsten sind alle Werte eines Pointer ungültig.

Zur absichtlichen Kennzeichnung ungültiger Pointer-Werte wird eine bestimmte Adresse (Wert) reserviert. Dieser Wert wird nil (nichts) oder null genannt. Meistens wird dafür eine 0 benutzt (daher der Name null). Es kann aber auch eine hohe Adresse verwendet werden, z.B. 0xFFFF FFFF.

Die Operation Dereferenzieren ist ungültig, wenn der Pointer-Wert ungültig ist.

Bei Addieren bzw. Indizieren werden Integer-Konstanten oder Registerinhalte addiert. Das Addieren zweier Adressen ist ungültig. Addieren und Subtrahieren von Konstanten ist zum Durchlaufen von Feldern sinnvoll.

Um die Größe eines Objektes bzw. den Adressabstand zwischen zwei Objekten zu bestimmen, wird die Differenz zwischen zwei Adressen (Pointer-Werte) berechnet.

Folgende Register sind Pointer (enthalten Adressen):

Indizieren in Feldern (Arrays):

Indizieren in Feldern (Arrays)

Bildbeschreibung "Indizieren in Feldern (Array)": Die Startadresse wird abgebildet durch "Element [0]". Dem folgen Element [1], [2], [3] bis zum letzten Element, bezeichnet als "Element [max]". Die Adressdifferenz D (auch bezeichnet als Offset oder Displacement) beschreibt die Differenz zwischen einem Element und dem Startelement. Index = Nummer der Elemente beginnend mit 0. Adresse des Elements = Startadresse + (Elementgröße × Index). Adressdifferenz D = Elementgröße × Index. Beispiel: Gegeben sind "int vector[10]; vector[3]=10". Elementgröße: 4 byte (32 bit integer); Index: 3; Adressdifferenz: 12; Element: Dereferenzieren(Startadresse + 12); Indizieren: Dereferenzieren(Startadresse + 12).

Beim Indizieren muss in der Regel multipliziert werden, was zu langsamen Programmen führt (Multiplikation ist nach der Division die in der Regel langsamste Operation).

Operationsmodi

Operationsmodi

Bildbeschreibung "Operationsmodi": Register, Register Indirekt, Adresse Indirekt, Adresse Indiziert, Offset Indiziert, Immediate.

Arten von Operationen:

Transfer-Operationen

Arithmetische Operationen

Logische Operationen

Kontrollflussoperationen

Sonderoperationen

Diese Gruppen von Operationen können 1-3 Operanden mit jeweils verschiedenen Adressierungsarten haben.

CPU-Register für Programme

Grundelemente der meisten (CISC-)Architekturen:

Grundelemente der meisten (CISC-)Architekturen

Bildbeschreibung "Grundelemente der meisten (CISC-)Architekturen": N-bit-Adressregister + M-bit-Datenregister + N-bit-PC (Programm Counter) + N-bit-SR (Status Register) + N-bit-SP (Stack Pointer).

Zum Menü Wirtschaftsinformatik | Zum Seitenanfang

Stack (Stapel, Keller)

Ein Stack ist eine Organisation eines Stück Speichers, um mit minimalen Aufwand beliebig komplexe arithmetische Ausdrücke sowie das Aufrufen von Unterprogrammen zu realisieren.

In den folgenden Beispiele möge der Stack Pointer (SP) immer auf das oberste Element des Stack zeigen. Der Stack möge von den hohen Adressen zu den niedrigen wachsen, d.h. mit voller werdenden Stack hat der SP einen immer kleineren Wert.
Es gibt Stacks, die anders organisiert sind!

Stack-Aufbau

Stack-Aufbau

Bildbeschreibung "Stack-Aufbau": Speicherbereich von hohen Adressen bis hin zu niedrigen Adressen. Diese ist auch die Richtung, in der der Stack wächst. Hohe Adressen sind durch den Stack belegt. Dann folgt ein freier Bereich und der Bereich der niedrigen Speicheradressen ist dann wieder belegt.

Der Stack-Pointer ist eine Variable, deren Werte Adressen auf eine bestimmten Speicherbereich sind.

Stack-Operation push(integer):

push(integer)

Bildbeschreibung "push(integer)": Stack-Operation. Vorher: Der Stack-Pointer zeigt auf den Stack. Nachher: Ein Integer-Bereich wurde an den Stack-Bereich angefügt. Der Stack-Pointer zeigt auf den Integer-Bereich.

Stack-Operation pop(integer):

pop(integer)

Bildbeschreibung "pop(integer)": Stack-Operation. Vorher: Der Stack-Pointer zeigt auf einen dem Stack angefügten Integer-Bereich. Nachher: Der Stack-Pointer zeigt auf den Stack. Der Integer-Bereich wurde entfernt.

Der Zugriff auf den Stack erfolgt über den Stack-Pointer bzw. über andere Zeiger (Pointer), die in den Stack zeigen.

Diese neue Architektur - Stack-Maschine genannt - ist ein gedankliches Gebilde, das auf realen Architekturen per Software simuliert werden kann. Diese simulierende Software realisiert dann eine Virtuelle Maschine mit einem Interpreter. Dies wird zur Realisierung von Java häufig benutzt (diese Maschine arbeitet stark Stack-orientiert, also sehr ähnlich der hier vorgestellten Idee).

Stack werden in zwei Bereichen angewendet:

Ausrechnen von Formeln: (a+b) × (c−d)

(a+b) × (c-d)

Bildbeschreibung "(a+b) × (c−d)": Erstens: a hinzufügen. Zweitens: b hinzufügen. Drittens: a und b addieren. Viertens: c hinzufügen. Fünftens: d hinzufügen. Sechstens: c und d subtrahieren. Siebtens: Ergebnis aus Schritt drei und Schritt sechs multiplizieren.

Werte, die erst später benutzt werden, werden auf den Stack gebracht und bleiben dort, bis sie gebraucht werden.

Werte, die sofort gebraucht werden, werden von der Operation während der Ausführung vom Stack geholt; dafür wird das Ergebnis wieder auf den Stack gebracht.

Mit einem Stack können beliebig komplexe, geschachtelte arithmetische Ausdrücke sowie Ausdrücke zur Berechnung von Adressen ausgewertet werden.

Subroutinen

Es gibt immer wieder zu verwendende Code-Stücke. Um diese nicht immer wieder neu programmieren zu müssen, werden diese einmal im Speicher abgelegt und bei Bedarf dynamisch angesprungen.

Prozedur = Procedure = Routine = Methode = Funktion = Benanntes Stück Code, das angesprungen und ausgeführt werden kann, um am Ende wieder zurückzuspringen [die Begriffe werden später noch etwas differenziert].

Unter einer Subroutine wird hier der Oberbegriff für alle eben genannten Begriffe benutzt.

Schritte beim Aufruf und bei der Rückkehr einer Subroutine:

Schritte beim Aufruf und bei der Rückkehr einer Subroutine

Bildbeschreibung "Schritte beim Aufruf und bei der Rückkehr einer Subroutine": Erstens: Das Hauptprogramm wird gestartet und teilweise ausgeführt. Zweitens: Die Subroutine S wird aufgerufen. Drittens: Die Subroutine S wird ausgeführt. Viertens: Rückkehr zum Hauptprogramm. Fünftens: Ausführen des restlichen Teils des Hauptprogramms.

Die Pfeile geben den Weg an, den der PC nimmt.
Der Code beider Routinen befindet sich im selben Adressraum.

Subroutinen können sich auch geschachtelt aufrufen. Die Subroutine, die am Anfang als erstes gestartet wird, wird Hauptprogramm genannt.

Wenn sich Subroutinen selbst aufrufen, wird dies Rekursion genannt:

Das Phänomen der Rekursion ist hier nur in dem Sinne wichtig, als dass es möglich sein soll - auch wenn die sinnvollen Anwendungsfälle (noch) unbekannt sind.

Rekursion

Bildbeschreibung "Rekursion": Erstens: Das Hauptprogramm wird gestartet und teilweise ausgeführt. Zweitens: Die Subroutine S wird aufgerufen. Drittens: Die Subroutine S wird teilweise ausgeführt. Viertens: Die Subroutine T wird aufgerufen. Fünftens: Die Subroutine T wird ausgeführt. Sechstens: Rückkehr zu Subroutine S. Siebtens: Ausführen des restlichen Teils der Subroutine S. Achtens: Rückkehr zum Hauptprogramm. Neuntens: Ausführen des restlichen Teils des Hauptprogramms.

Realisierung:

"call xyz" wird durch die Instruktion jsr realisiert.
(jsr = jump to subroutine). Es wird die Rückkehradresse auf den Stack geschrieben und dann zur Routine gesprungen.

jsr-Instruktion

Bildbeschreibung "jsr-Instruktion": Vorher: Der Stack-Pointer zeigt auf den Stack. Nachher: Ein Adress-Bereich wurde an den Stack-Bereich angefügt. Der Stack-Pointer zeigt auf den Adress-Bereich.

"return" wird durch die Instruktion rts realisiert.
(rts = return from subroutine). Es wird die Adresse vom Stack geholt und dorthin gesprungen.

rts-Instruktion

Bildbeschreibung "rts-Instruktion": Vorher: Der Stack-Pointer zeigt auf einen dem Stack angefügten Adress-Bereich. Nachher: Der Stack-Pointer zeigt auf den Stack. Der Adress-Bereich wurde entfernt.

Durch die Verwendung des Stack ist eine einfache Realisierung bei geschachtelten sowie auch bei rekursiven Aufrufen möglich.

Die Instruktionen jsr und rts sind hier fiktiv. Bei jedem Prozessor heißen sie anders, haben aber im Prinzip dieselbe Bedeutung (Funktion) wie jsr und rts.

Lokale Variablen:

Subroutinen benötigen für das Speichern von Zwischenergebnissen auch Variablen, die aber während der Subroutinen-Aktivierung vorhanden sein sollen.

Diese Variablen müssen beim Aufruf erzeugt sowie bei der Rückkehr wieder entfernt werden.

Da dies auch bei Schachtelungen bzw. Rekursion erfolgen soll, müssen diese Variablen auf den Stack gebracht werden: So geschachtelt wie die Aufrufe, so geschachtelt sollen diese Variablen vorhanden sein.

Variablen, die während der gesamten Laufzeit einer Subroutine existieren und mit deren Beendigung verworfen werden, werden lokale Variablen genannt.

Parameter:

Wenn eine Subroutine zum Ausgeben einer Integer-Variablen geschrieben werden soll, dann muss es möglich sein, zu verschiedenen Zeitpunkten auch verschiedene Integer-Variablen auszugeben.

Um derartige Subroutinen schreiben zu können, werden Parameter benötigt, über die der Subroutine mitgeteilt wird, was und womit sie ihre Aufgabe lösen soll.

Parameter = Wert einer Variablen aus dem Bereich der aufrufenden Subroutine, mit der die aufgerufene Subroutine arbeiten soll.

Auch hier muss das Problem der Schachtelung gelöst werden.

Lokale Variablen und Parameter auf dem Stack

Stack-Aufbau während der Ausführung der Subroutine:

Stack-Aufbau während der Ausführung der Subroutine

Bildbeschreibung "Stack-Aufbau während der Ausführung der Subroutine": Vier Bereiche (in Richtung, in der der Stack wächst): Parameter, Rückkehr-Adresse, Lokale Variablen, freier Bereich.

Schrittweiser Aufbau des Stack:

Aufruf: Subroutine(Parameter 1, ..., Parameter N):

Aufruf: Subroutine(Parameter 1, ..., Parameter N), Bild 1

Bildbeschreibung "Aufruf: Subroutine(Parameter 1, ..., Parameter N), Bild 1": Erstens (Aufrufer): Aufruf des Stack (Inhalt des Stack = Lokale Variablen + freier Bereich). Zweitens (Aufrufer): Hinzufügen der Bereiche Parameter 1, 2 und N (Inhalt des Stack = Lokale Variablen + Parameter 1 + Parameter 2 + Parameter N + freier Bereich). Drittens (Aufrufer): Aufruf der Subroutine (Inhalt des Stack = Lokale Variablen + Zusammengefasster Parameter-Bereich + Return-Adresse + freier Bereich). Viertens (Subroutine): Hinzufügen Lokale Variablen (Inhalt des Stack = Lokale Variablen + Parameter + Return-Adresse + Lokale Variablen + freier Bereich). Fortsetzung in nachfolgender Grafik!

Aufruf: Subroutine(Parameter 1, ..., Parameter N), Bild 2

Bildbeschreibung "Aufruf: Subroutine(Parameter 1, ..., Parameter N), Bild 2": Fortsetzung der vorangegangenen Grafik! Fünftens (Subroutine): Hinzufügen von Berechnungen (Inhalt des Stack = Lokale Variablen + Parameter + Return-Adresse + Lokale Variablen + Ausdrücke + freier Bereich). Sechstens (Subroutine): Schließen der lokalen Variable (Inhalt des Stack = Lokale Variablen + Parameter + Return-Adresse + freier Bereich). Siebtens (Subroutine): rts (Inhalt des Stack = Lokale Variablen + Parameter + freier Bereich). Achtens (Aufrufer): Schließen der Parameter (Inhalt des Stack = Lokale Variablen + freier Bereich).

Nach dem nachfolgenden Schema arbeiten die (meisten) Aufrufe von Subroutinen. Bei einer Subroutine ohne Parameter entfällt das "push Parameter" bzw. "pop Parameter". Bei einer Subroutine ohne lokale Variablen entfällt das "push Lokale Variablen" bzw. "pop Lokale Variablen".

Schema Subroutinen

Bildbeschreibung "Schema Subroutinen": Aufrufer = push Parameter + jsr Subroutine + pop Parameter. Subroutine = push Lokale Variablen + Berechnungen + pop Lokale Variablen + rts.

Wenn die Subroutine selbst einen Wert als Ergebnis zurückliefern soll, z.B. wie sin(x), dann wird der Platz für das Resultat vom Aufrufer als besonderen Parameter gleich zum Anfang auf den Stack gebracht, so dass nach dem Aufruf das Resultat als oberstes Element auf dem Stack steht (und damit leicht in die Berechnungen beim Aufrufe benutzt werden kann, siehe 1. Stack-Anwendung).

Eine Subroutine, die einen Wert liefert, wird Funktion genannt, eine, die keinen Wert liefert Prozedur bzw. Procedure.

Routine und Subroutine verbleiben als Oberbegriffe. Statt Routine bzw. Subroutine wird beim objektorientierten Ansatz von Methoden gesprochen.

Activation Record = Teil des Stack, der alle temporären Daten eines Subroutinenaufrufs beinhaltet.

Kopf und Körper einer Subroutine:

Kopf = Schnittstelle der Subroutine nach außen.

Körper = Body = Code einer Subroutine ohne Parameter, d.h. der Teil nach dem Anlegen der lokalen Variablen bis zum Entfernen der lokalen Variablen.

Kopf und Körper einer Subroutine

Bildbeschreibung "Kopf und Körper einer Subroutine": Der Kopf setzt sich zusammen aus dem zu liefernden Resultat, Namen und Parametern. Der Körper beinhaltet Lokale Variablen und Resultate.

Makro:

Makro = Stück Code, der bei jedem Aufruf an die Stelle des Aufrufs hineinkopiert wird.

Wie eine Subroutine hat auch ein Makro einen Kopf und einen Körper.

Der Kopf definiert den Namen und die Parameter, der Körper das, was beim Aufruf hinein-kopiert werden soll.
Der Aufruf selbst wird entfernt und stattdessen der Körper eingesetzt.

Beispiel eines Makro in der Sprache C:

Beispiel eines Makro in der Sprache C

Bildbeschreibung "Beispiel eines Makro in der Sprache C": # define TEST (a==b), wobei # define für die Definition des Makros steht, TEST den Namen des Makros angibt und (a==b) den Körper darstellt. if TEST {a = 0; b = 1;} wird durch Expansion zu if (a==b) {a = 0; b = 1;}.

Zum Menü Wirtschaftsinformatik | Zum Seitenanfang

Assembler

Assembler = Übersetzer für Programme in einer symbolischen Maschinensprache. Die Sprache Assembler ist für jeden CPU-Typ anders und spiegelt die Eigenarten der CPU-Architektur (Register, Befehlssatz usw.) wieder. Zur Assembler-Sprache gehören

Der Assembler übersetzt das Assembler-Programm in ein maschinen-codiertes Format, dem Objekt-Format. Diese Dateien heißen daher Objekt-Dateien.

Assembler:

  1. Label: LOAD R3,@56EE
    Register R3 = Inhalt von 56EE
  2. Label: LOAD R2,#1000
    Register R2 = 0x1000
  3. Label: ADD R2,R3
    Register R2 = R2 + R3
  4. Label: SUB R2,R4
    Register R2 = R2 - R4
  5. Label: STORE R3,@56EE
    Inhalt von 56EE = Register R3
... Übersetzung...

Objekt-Datei:

  1. 34A3 56EE
  2. 67A2 1000
  3. 7F23 1200
  4. 0124 2300
  5. 66A3 56EE

Die Assemblersprachen sind in der Regel spaltenorientiert, d.h. die Zeilen haben ein festes Format, das einzuhalten ist.

Im vorangegangenen Beispiel sieht das wie folgt aus:

  1. Spalte zur Definition von Sprungmarken, z.B. Label
  2. Befehle mit den Parametern, z.B. LOAD R3,@56EE
  3. Kommentar, z.B. Register R3 = Inhalt von 56EE

Ein wichtiges Charakteristikum eines Assemblers ist, dass die Assembler-Befehle fast immer 1:1 zu Maschinenbefehlen umgesetzt werden.

Sprungmarken = Label = Namen für Speicherstellen (symbolische Adressen) von bestimmten Instruktionen, z.B. zum Beginn einer Subroutine.

Das Programmieren in Assembler ist sehr mühselig, da:

Aber: In Assembler sind die effizientesten Programme schreibbar.

Zum Menü Wirtschaftsinformatik | Zum Seitenanfang

Compiler

Höhere Programmiersprachen, wie z.B. C oder Java, werden durch Compiler in Maschinensprache übersetzt.

Compiler = Übersetzer für Programme in einer höheren Programmiersprache, die sich dadurch auszeichnet, dass ein Statement dieser Sprache in mehrere Befehle in der Maschinensprache übersetzt werden muss.

Beispiel Compiler

Bildbeschreibung "Beispiel Compiler": i = j + 1. Ablauf: LOAD R3,@56EE (R3 = Inhalt von j); ADD R3,#0001 (R3 = R3 + 1); STORE R3,@56FF (i = R3).

Beispiel: Compiler für C

Es wird hier C als Beispiel benutzt; dies kann analog auf C++ oder Java übertragen werden.

Das Übersetzen erfolgt in mehreren Durchläufen (Pass), in denen das gesamte Programm vollständig gelesen und interpretiert wird.

Nach jedem Durchlauf wird das Programm in überarbeiteter Form neu in einer speziellen Datei angelegt; diese wird bei dem nächsten Durchlauf benutzt, so dass beginnend vom ursprünglichen Programm über mehrere Dateien am Ende das Maschinenprogramm entsteht.

Die Steuerung der Durchläufe übernimmt ein spezielles Hauptprogramm.

Bei Compiler sind 4 bis 5 Durchläufe üblich, es können auch erheblich mehr sein, z.B. PL/1 hatte bis zu 60 Durchläufe.

Ablauf einer Übersetzung:

Ablauf einer Übersetzung

Bildbeschreibung "Ablauf einer Übersetzung": Erstens: "Beispiel.c" (C-Programm). Durch Preprozessor (Dateien mit Makrodefinitionen) folgt zweitens: "Beispiel.i" (C-Programm mit expandierten Makros). Dann folgt der eigentliche Übersetzer und drittens: "Beispiel.o" (übersetztes Programm ohne Bibliotheksroutinen, Objekt-Datei). Durch den Linker / Binder (Hauptprogramm, Bibliotheken) folgt viertens: "Beispiel" (fertiges ausführbares Programm, Maschinencode). Am Ende der Kette steht das Programm in Ausführung.

Jetzt kann die Datei mit Maschinencode von der CPU in den RAM geladen und ausgeführt werden.

Bibliothek = Archiv = Library = Datei mit mehreren benannten Informationsblöcken einschließlich eines Verzeichnisses.

Objektbibliothek = Bibliothek mit Objekt-Dateien.

Binärcode = Maschinencode.

Zum Menü Wirtschaftsinformatik | Zum Seitenanfang

CISC - RISC

Anfang der 80er Modewelle: Statt den komplizierten CPU-Architekturen sollten nur sehr schnelle, sehr einfache Instruktionen benutzt werden.

  1. Vorteile:
    • Einsparung von Chipplatz aufgrund Reduktion der Komplexität der Instruktionen
    • Sehr einfache Dekodierung der Instruktionen und einfache Pipelines (Super-Skalar-Pipelining)
    • Eingesparte Chipfläche wird in sehr vielen Registern (Register Files) und umfangreichem Cache investiert
    • Reduktion der Adressierungsmodi: "Normale" Operationen arbeiten nur auf Registern, während andere lediglich Register aus dem RAM laden bzw. deren Inhalte in den RAM schreiben (Transfer-Operationen)
  2. Nachteile:
    • Es werden für dieselben Aufgaben mehr Instruktionen benötigt, was zu einer höheren Belastung des Bus führt (Nadelöhr Bus in der Von-Neumann-Architektur)
    • Compiler sollten einen großen Teil der Optimierungen übernehmen

Die alten (komplexen) Architekturen wurden CISC, die neuen RISC genannt:

In den 90er Jahren haben sich die Unterschiede aufgelöst:

Die nunmehr entstandene Vereinigung beider Ideen führt zu "modernen" CPU-Architekturen, die historisch bedingt immer noch in die beiden Lager RISC/CISC gepackt werden:

Beispiele sind: Alpha, PowerPC, Itanium.