Einführung in die Systemprogrammierung Prof. Dr. Christoph Reichenbach Fachbereich 12 / Institut für Informatik 9. Juli 2015 Systemprogrammierung und Hochsprachen Programm Bibliotheken Laufzeitsystem Systemprogramm Systembibliotheken Betriebssystem Rechnerarchitektur Vor- und Nachteile von Systemsprachen Hoher Grad an Kontrolle über Hardware: I I I Hohe Performanz erreichbar Speicheraufwand minimierbar Echtzeit-Programme möglich Detailkontrolle wird oft erzwungen: I I Speicherfehler (Relativ) geringe Entwicklungsgeschwindigkeit Gründe: I I I Speicherfehler Detailentscheidungen evtl. geringe Abstraktion Speicherfehler (Wiederholung) I I I I Doppel-free() Deallozierung vergessen Hängender Zeiger Zeigerarithmetik-Fehler: I I Falscher Typ Falscher Speicherbereich: I Array-Grenzen überschritten Speicherfehler (Wiederholung) I I I I Doppel-free() Deallozierung vergessen Hängender Zeiger Zeigerarithmetik-Fehler: I I Falscher Typ Falscher Speicherbereich: I Array-Grenzen überschritten Wie können wir Speicherfehler einzäunen? Lösungsansatz #1: Starke Typisierung I I Jeder Speicherinhalt hat einen Typ Der Typ diktiert “Vertrag”: I I I Welche Speicherinhalte sind erlaubt? Was darf mit Speicherinhalt geschehen? Starke Typisierung: I Erzwingt Typverträge Lösungsansatz #1: Starke Typisierung I I Jeder Speicherinhalt hat einen Typ Der Typ diktiert “Vertrag”: I I I Welche Speicherinhalte sind erlaubt? Was darf mit Speicherinhalt geschehen? Starke Typisierung: I I Erzwingt Typverträge Prüfungen können stattfinden: I I statisch (Bei Übersetzung) dynamisch (Bei Ausführung) Lösungsansatz #2: Automatische Speicherverwaltung I Viele Speicherfehler werden von free ausgelöst? ⇒ Sprachdesign ohne Free: I I Laufzeitsystem entsorgt ungenutzten Speicher automatisch Speicherallozierung kann explizit (Java) oder implizit (Haskell) stattfinden Lösungsansatz #2: Automatische Speicherverwaltung I Viele Speicherfehler werden von free ausgelöst? ⇒ Sprachdesign ohne Free: I I Laufzeitsystem entsorgt ungenutzten Speicher automatisch Speicherallozierung kann explizit (Java) oder implizit (Haskell) stattfinden Laufzeitsystem benötigt Automatische Speicherverwaltung Hochsprachen-Lösungen für Speicherfehler I I Starke Typisierung [ST]: Automatische Speicherverwaltung [ASV] Doppel-free() Deallozierung vergessen Hängender Zeiger Falscher Typ Falscher Speicherbereich Array-Grenzen überschritten ST ST ST ST ASV (ASV) ASV Hochsprachen-Lösungen für Speicherfehler I I Starke Typisierung [ST]: Automatische Speicherverwaltung [ASV] Doppel-free() Deallozierung vergessen Hängender Zeiger Falscher Typ Falscher Speicherbereich Array-Grenzen überschritten ST ST ST ST ASV (ASV) ASV Auch mit automatischer Speicherverwaltung sind vergessene Deallozierungen noch möglich Zusammenfassung: Speicherfehler und Hochsprachen I Hochsprachen vermeiden Speicherfehler durch I Starke Typisierung: I I I I Schließt ‘Typ-Verträge’ Erzwingt diese Verträge Verbietet Zeigertypen Automatische Speicherverwaltung: I I Kein explizites free Automatisches Speicherverwaltungssystem (garbage collection) Hochsprachen-Laufzeitsysteme I I C/C++ verwenden vergleichsweise einfaches Laufzeitsystem Andere Hochsprachen benötigen komplexere Laufzeitunterstützung: I I I I Umsetzung der Starken Typisierung Automatische Speicherverwaltung Code-Optimierung zur Laufzeit Unterstützung für Sprachfeatures: I I I I I I Objekte und Klassen Funktionen als Werte Nachladen von Code zur Laufzeit Nebenläufige Ausführung und Synchronisierung Ausnahmen Reflektion ... Hochsprachen-Laufzeitsysteme Wir betrachten heute die Laufzeitsysteme folgender Sprachen: I I I I Python Java Haskell AttoL All diese Sprachen leben in der gleichen Laufzeitumgebung wie C-Programme, nutzen sie aber unterschiedlich Python: Die Sprache I I I Multi-Paradigmensprache (Objektorientiert, Funktional) Beeinflußt von vielen Sprachen: Modula-3, LISP, SETL, ... Initiales Design: Guido van Rossum for i in range (1, 11): print i Python: Übersetzung und Ausführung Python-Bytecode (optimiert) .pyo Python-Quellcode .py Ausführung: Interpreter Python-Bytecode .pyc I I Python-Bytecode wird oft nur im Speicher gehalten (nicht in Datei gespeichert) Andere Ausführungsmodi möglich (pypy, jython etc.) Python: Bytecode # Python-Quellcode i = 0 while i <= 10: print i i += 1 0 3 LOAD_CONST STORE_FAST 6 9 12 15 18 SETUP_LOOP LOAD_FAST LOAD_CONST COMPARE_OP POP_JUMP_IF_FALSE 21 24 25 LOAD_FAST PRINT_ITEM PRINT_NEWLINE 26 29 32 33 36 LOAD_FAST LOAD_CONST INPLACE_ADD STORE_FAST JUMP_ABSOLUTE 39 POP_BLOCK Python: Ausführung in Stapelmaschine I I Ausführung findet auf Stapelmaschine statt Werte werden auf Stapel gelegt, ausgewertet Stapel 1 LOAD_CONST 2 Stapel 1 2 INPLACE_ADD Stapel 3 Python: Ausführung opcode = NEXTOP (); oparg = 0; if ( HAS_ARG ( opcode )) oparg = NEXTARG (); ... switch ( opcode ) { ... case LOAD_FAST : x = GETLOCAL ( oparg); if (x != NULL) { Py_INCREF (x); PUSH(x); ... break; case INPLACE_ADD : w = POP (); v = TOP (); ... a = PyInt_AS_LONG (v); b = PyInt_AS_LONG (w); i = a + b; ... // Ueberlauf ? x = PyInt_FromLong (i); Py_DECREF (v); ... SET_TOP (x); ... break; case LOAD_CONST : x = GETITEM (consts , oparg); Py_INCREF (x); PUSH(x); ... case STORE_FAST : v = POP (); SETLOCAL (oparg , v); ... Steckbrief: Python Python Version: Datum: Quelle: Laufzeitsystem Zwischensprache: Ausführungsmodus: Alternativen: Sprache Sprachtyp: Starke Typisierung: Sprachkonstrukte: 3.4.3 / 2.7.10 2015-03-03/05-23 http://python.org In Speicher oder Datei (Python-Bytecode) Interpretiert PyPy (JIT-Übersetzer), Jython (Übersetzer nach Java-Bytecode) Multi-Paradigmatisch ja (dynamisch) Klassen, Objekte, Funktionen-als-Werte, Dynamische Übersetzung, Reflektion Java: Die Sprache I I I I Objektorientierte Sprache Portabler Zwischencode (Java-Bytecode) Initiales Design: James Gosling, Bill Joy, Guy Steele Basiert ursprünglich auf C89 mit Ideen aus Self, Smalltalk public class Zehn { public static final void main( String [] args) { for (int i = 1; i <= 10; i++) { System .out. println (i); } } } Java: Übersetzung und Ausführung Quellcode .java Java-Laufzeitsystem javac Bytecode .class java Lader/Binder (Classloader) JIT-Übersetzer Interpreter Maschinencode Java: Bytecode public class Zehn { public static final void main(String[] args) { for (int i = 1; i <= 10; i++) { System.out.println(i); } } 0: 1: 2: 3: 5: 8: 11: 12: 15: 18: 21: iconst_1 istore_1 iload_1 bipush if_icmpgt getstatic iload_1 invokevirtual iinc goto return 10 21 #2 #3 1, 1 2 } Konstantenliste java/lang/System.out:Ljava/io/PrintStream; java/io/PrintStream.println:(I)V Java: Ausführung I I Interpretierung ähnlich Python Unterschied: I I Stapelmaschine in Python speichert nur Objekte (Zahlen werden zu Objekten konvertiert) Stapelmaschine in Java speichert: I I I I Objekte Ganzzahlen Fließkommazahlen Übersetzung zur Laufzeit (JIT): anderes Modul Steckbrief: Java Java Version: Datum: Quelle: 1.8.0u45 (Oracle) 2015-04-14 http://java.com (Oracle Inc.) Laufzeitsystem Zwischensprache: In Datei (Java-Bytecode) Ausführungsmodus: JIT (HotSpot) Sprache Sprachtyp: Objektorientiert Starke Typisierung: ja (statisch + dynamisch) Sprachkonstrukte: Klassen, Objekte, Dynamische Übersetzung, Reflektion AttoL: Die Sprache I I I I I Kleine while-Sprache Unterstützt Arrays, Funktionen, Objekte Zwei zur Übersetzungszeit angegebene Typen: int (Zahlen) und obj (Objekte) Laufzeitsystem: AttoVM Zur Lehre entwickelt int x = 1; while (x < 11) { print(x); x := x + 1; } AttoL: Analyse und Übersetzung AttoL .atl Abstrakter Syntaxbaum (AST) Namensanalyse Typanalyse Annotierter AST Maschinencode AttoL: Analyse und Übersetzung AttoL .atl Abstrakter Syntaxbaum (AST) Namensanalyse Typanalyse Annotierter AST Maschinencode Andere Sprachen verwenden auch AST und Programmanalysen; dort zur Vereinfachung ausgelassen AttoL: AST BLOCK VARDECL WHILE FUNAPP x:NAME 1:INT ACTUALS id:TEST_LT ASSIGN FUNAPP x:NAME 11:INT FUNAPP ACTUALS print:NAME ACTUALS id:ADD x:NAME x:NAME 1:INT x:NAME int x = 1; while (x < 11) { print(x); x := x + 1; } BLOCK AttoL: AST BLOCK VARDECL WHILE FUNAPP x:NAME 1:INT ACTUALS id:TEST_LT ASSIGN FUNAPP x:NAME 11:INT FUNAPP ACTUALS print:NAME ACTUALS id:ADD x:NAME x:NAME 1:INT x:NAME int x = 1; while (x < 11) { print(x); x := x + 1; } BLOCK AttoL: AST BLOCK VARDECL WHILE FUNAPP x:NAME 1:INT ACTUALS id:TEST_LT ASSIGN FUNAPP x:NAME 11:INT FUNAPP ACTUALS print:NAME ACTUALS id:ADD x:NAME x:NAME 1:INT x:NAME int x = 1; while (x < 11) { print(x); x := x + 1; } BLOCK AttoL: AST BLOCK VARDECL WHILE FUNAPP x:NAME 1:INT ACTUALS id:TEST_LT ASSIGN FUNAPP x:NAME 11:INT FUNAPP ACTUALS print:NAME ACTUALS id:ADD x:NAME x:NAME 1:INT x:NAME int x = 1; while (x < 11) { print(x); x := x + 1; } BLOCK AttoL: AST BLOCK VARDECL WHILE FUNAPP x:NAME 1:INT ACTUALS id:TEST_LT ASSIGN FUNAPP x:NAME 11:INT FUNAPP ACTUALS print:NAME ACTUALS id:ADD x:NAME x:NAME 1:INT x:NAME int x = 1; while (x < 11) { print(x); x := x + 1; } BLOCK AttoL: Semantische Analyse I I Namen werden aufgelöst und durch Symbole ersetzt Symbole werden in Symboltabelle gespeichert: I I I Merkt sich für jedes Symbol relevante Informationen (Typ, Speicheradresse, . . . ) Eingebaute Bezeichner (print) haben negativen Symboltabelleneintrag (z.B. −12) Benutzerdefinierte Bezeichner (x) haben positiven Symboltabelleneintrag (z.B. 1) AttoL: Semantische Analyse I I Namen werden aufgelöst und durch Symbole ersetzt Symbole werden in Symboltabelle gespeichert: I I I I Macht implizite Typkonvertierungen explizit (print(x)) I I I Merkt sich für jedes Symbol relevante Informationen (Typ, Speicheradresse, . . . ) Eingebaute Bezeichner (print) haben negativen Symboltabelleneintrag (z.B. −12) Benutzerdefinierte Bezeichner (x) haben positiven Symboltabelleneintrag (z.B. 1) print benötigt obj-Parameter x hat Typ int ⇒ Konvertierung nötig Weitere Annotationen: LValue, Deklarationsort AttoL: Annotierter AST BLOCK VARDECL INT, LV, DECL SYM[1]:x WHILE FUNAPP BLOCK INT 1:INT ACTUALS ASSIGN INT, LV INT OBJ id:TEST_LT sym[1]:x FUNAPP INT INT INT sym[1]:x 11:INT FUNAPP ACTUALS I Typkonvertierung sym[-12]:print ACTUALS id:ADD OBJ I INT: int-Typ FUNAPP INT INT sym[1]:x 1:INT I OBJ: obj-Typ sym[-3]:*convert ACTUALS I LV: LValue I DECL: Deklaration INT sym[1]:x AttoL: Annotierter AST BLOCK VARDECL INT, LV, DECL SYM[1]:x WHILE FUNAPP BLOCK INT 1:INT ACTUALS ASSIGN INT, LV INT OBJ id:TEST_LT sym[1]:x FUNAPP INT INT INT sym[1]:x 11:INT FUNAPP ACTUALS I Typkonvertierung sym[-12]:print ACTUALS id:ADD OBJ I INT: int-Typ FUNAPP INT INT sym[1]:x 1:INT I OBJ: obj-Typ sym[-3]:*convert ACTUALS I LV: LValue I DECL: Deklaration INT sym[1]:x AttoL: Maschinencode-Übersetzung (2OPM) int x = 1; li $v0, 1 sd $v0, 0($gp) while (x < 11) ld $a0, 0($gp) li $a1, 0xb slt $v0, $a0, $a1 beqz $v0, 0xb000000143 print(x); ld $a0, li $v0, jalr $v0 move $a0, li $v0, jalr $v0 0($gp) 41e5eb ; new_int $v0 42027f ; print x := x + 1; ld $a0, 0($gp) li $a1, 1 add $a1, $a0 move $v0, $a1 sd $v0, 0($t0) Schleifenende j 0xb0000000d6 Steckbrief: AttoL AttoL Version: Datum: Quelle: 0.4.0 2015-04-22 http://sepl.cs.uni-frankfurt.de/ teaching/attovm.de.html Laufzeitsystem Zwischensprache: nein Ausführungsmodus: Kompiliert oder JIT (AttoVM) Sprache Sprachtyp: Objektorientiert Starke Typisierung: ja (dynamisch) Sprachkonstrukte: Klassen, Objekte Haskell: Die Sprache I I I I Funktionale Programmiersprache: Funktionen sind Werte Initiales Design: Simon Peyton Jones, Lennart Augustsson et al. Basiert ursprünglich auf Miranda Verzögerte Auswertung: I I I I Definition unendlich großer Datenstrukturen möglich Auswertung nur, wenn nötig Vermeidung mehrfacher Auswertung Statisch typisiert: Fast alle Typfehler werden zur Übersetzungszeit gefunden main = print [1..10] Haskell: Übersetzung und Ausführung Haskell .hs Core STG Maschinencode Haskell: Zwischenrepräsentierung ‘Core’ I I Erster Übersetzungsschritt entfernt ‘Syntaktischen Zucker’ Übersetzung in Zwischenrepräsentierung: I Einfache Variante des Lambda-Kalküls main = runMainIO (print ($fShow[] $fShowInteger) (enumFromTo $fEnumInteger (__integer 1) (__integer 10)) Haskell: Die STG-Maschine I I Laufzeitsystem von Haskell: ‘Spineless, Tagless G-machine’ (STG) Verwendet C-artigen Ausführungsstapel (anders als andere funktionale Systeme!) Haskell: Die STG-Maschine I I I Laufzeitsystem von Haskell: ‘Spineless, Tagless G-machine’ (STG) Verwendet C-artigen Ausführungsstapel (anders als andere funktionale Systeme!) Besondere Herausforderungen: I I Funktionen als Werte: addiere x = λy → x + y (addiere 3) -- gibt Funktion λy → 3 + y zurück Verzögerte Auswertung (‘lazy evaluation’): geradezahl x = x*2 : geradezahl (x+1) Haskell-Ausführung: Funktionen als Werte addiere x = λy → x + y ... addiere 7 ... I I Wie repräsentieren wir den Rückgabewert von addiere 7? Wert ist eine Funktion: I I I Idee: speichern eine Struktur mit zwei Einträgen I I I λy → x + y Wert von x ist bekannt Zeiger auf Code Variableninhalte Struktur wird Closure genannt Closures auch in Python Haskell-Ausführung: Closures 0+$a0 .text Ablagespeicher lw add jr 4+$a0 7 (x) $s0 li jal move li move lw jalr $v0, 4($a0) $v0, $v0, $a1 $ra # Closure erstellen $a0, 7 addiere $s0, $v0 ... # Aufruf der Closure $a1, 35 $a0, $s0 $t0, 0($a0) $t0 Haskell-Ausführung: Verzögerte Auswertung geradezahl x = x*2 : geradezahl (x+1) geradezahlen = geradezahl 0 I I Verzögerte Auswertung erlaubt Definition ‘unendlich’ großer Konstrukte Tatsächliche Verwendung eines Wertes kann zu Nichtterminierung führen I I take 5 geradezahlen: terminiert, liefert [0,2,4,6,8] take 5 (rev geradezahlen): terminiert nicht Haskell-Ausführung: Verzögerte Auswertung geradezahl x = x*2 : geradezahl (x+1) geradezahlen = geradezahl 0 I I Verzögerte Auswertung erlaubt Definition ‘unendlich’ großer Konstrukte Tatsächliche Verwendung eines Wertes kann zu Nichtterminierung führen I I I Implementierung: I I I Wert soll erst berechnet werden, wenn wir ihn brauchen Wert soll höchstens ein Mal berechnet werden Idee: Closure ohne Parameter I I take 5 geradezahlen: terminiert, liefert [0,2,4,6,8] take 5 (rev geradezahlen): terminiert nicht Aktualisiert sich selbst, wenn Auswertung abgeschlossen Solche Closures werden Thunks genannt Haskell-Ausführung: Thunks .text Ablagespeicher 2 sw la sw jr ... # Berechnung $v0, 4($a0) $t0, quickret $v0, 0($a0) $ra Haskell-Ausführung: Thunks .text Ablagespeicher 0+$a0 4+$a0 Wert sw la sw jr ... # Berechnung $v0, 4($a0) $t0, quickret $v0, 0($a0) $ra Haskell-Ausführung: Thunks .text Ablagespeicher 0+$a0 4+$a0 Wert sw la sw jr ... # Berechnung $v0, 4($a0) $t0, quickret $v0, 0($a0) $ra quickret: lw $v0, 4($a0) jr $ra Nach erster Auswertung: Ergebnis gesichert, Code-Zeiger auf schnelle Ausleseroutine geändert Zusammenfassung: Closures und Thunks I Eine Closure ist ein Paar von: I I I Verwendung: I I Repräsentierung ‘anonymer’ Funktionen Ein Thunk ist: I I I Zeiger auf Maschinencode Tabelle von Variableninhalten (‘Umgebung’) für den Maschinencode Eine Closure ohne Parameter Kann sich selbst nach Auswertung aktualisieren (Vorsicht: Haskell-spezifisch!) Verwendung: I Verzögerte Auswertung Steckbrief: Haskell Haskell Version: Datum: Quelle: 7.10.1 (GHC) 2015-03-27 http://www.haskell.org/ghc/ Laufzeitsystem Zwischensprache: Im Speicher (Core: Lambda-Kalkül, STG) Ausführungsmodus: Vorübersetzt, Graphreduktionsmodell Sprache Sprachtyp: Funktional Starke Typisierung: ja (statisch) Sprachkonstrukte: Funktionen-als-Werte, Datenkonstruktoren, Typen höherer Ordnung, Typklassen, Monaden Objekte Sprachen allozieren verschiedene ‘Dinge’: I I I Closures Listen Zeichenketten ... Allgemeiner Oberbegriff: Objekte Objekte Sprachen allozieren verschiedene ‘Dinge’: I I I Closures Listen Zeichenketten ... Allgemeiner Oberbegriff: Objekte Auch in Haskell spricht man hier von ‘Objekten’ Laufzeit-Typinformationen Typbestimmung zur Laufzeit: Python Java AttoVM type(v ) is t v instanceof t v is t I I Anfragen können nicht immer zur Übersetzungszeit beantwortet werden Objekte beinhalten Typdeskriptoren, die Laufzeit-Typinformationen kodieren Laufzeit-Typinformationen Typbestimmung zur Laufzeit: Python Java AttoVM type(v ) is t v instanceof t v is t I I Anfragen können nicht immer zur Übersetzungszeit beantwortet werden Objekte beinhalten Typdeskriptoren, die Laufzeit-Typinformationen kodieren Typinformationen sind auch für andere Anwendungen nützlich Homogene Objektrepräsentierung Python typedef struct _object { Py_ssize_t ob_refcnt; struct _typeobject *ob_type; ... (Python 2.7.4) AttoVM typedef struct object { class_t *classref; ... Java class oopDesc { volatile markOop _mark; union _metadata { wideKlassOop _klass; narrowOop _compressed_klass; } _metadata; ... (JDK 1.7/HotSpot) Vorteile der homogenen Objektrepräsentierung Homogene Repräsentierung erlaubt: I I I Laufzeit-Typprüfung Automatische Speicherverwaltung Vereinfachung von Schnittstellen (Polymorphismus) Objekte und Klassen im Ablagespeicher Beispiel Python: Klasse list yp _t ob e yp ob_type e yp _t ob e _t yp ob _t ob e Klasse str [1, 2, 3] "foo" "bar" "quux" (Analog Java, AttoVM, Haskell) ["a", "b"] Objekte und Klassen im Ablagespeicher Beispiel Python: Klasse list yp _t ob e yp ob_type e yp _t ob e _t yp ob _t ob e Klasse str [1, 2, 3] "foo" ["a", "b"] "bar" "quux" (Analog Java, AttoVM, Haskell) Objekte gleichen Typs zeigen auf gleiches Klassenobjekt Zusammenfassung: Laufzeit-Typinformationen I I I Objekte bieten homogenes Interface auf Daten im Speicher Jedes Objekt speichert Typdeskriptor: Laufzeit-Typinformationen Nicht alle Dinge sind Objekte: int, double Verpacken (boxing) I I Zeichenketten, Listen etc. als Objekte im Speicher int? double? Trennung Objekte/Primitive Typen (AttoVM, Java) int i = 0; Object o = new Object (); Objekt-Universalität (Python, Haskell) I I Vorteile: I Höhere Performanz bei Arithmetik Alles ist ein Objekt Eingebaute Operationen zur Addition Vorteile: I I einfachere Semantik einfachere Implementierung Automatisches Ein- und Auspacken Beispiel Java (LinkedList) LinkedList<Integer> ll = new LinkedList<>(); 1 ); ll.add( int i = ll.get(0) + 3; Automatisches Ein- und Auspacken Beispiel Java (LinkedList) LinkedList<Integer> ll = new LinkedList<>(); ll.add(new Integer(1)); int i = ll.get(0).intValue() + 3; Zusammenfassung I I I I Manche Sprachen unterscheiden zwischen verpackten und unverpackten Werten Verpackt: Primitiver Wert indirekt in Speicherobjekt abgelegt Unverpackt: Primitiver Wert direkt (in Register oder Speicher) abgelegt Verpacken/Entpacken: Konvertierung Verpacken Objekt Wert Entpacken