Einführung in die Systemprogrammierung 01 Prof. Dr. Christoph Reichenbach Fachbereich 12 / Institut für Informatik 24. April 2013
Administrativa Ab nächster Woche bitte Laptops in Übungen mitbringen OLAT-Paßwort ist mips Übungen werden montags morgens veröffentlicht Bearbeitungszeitlimit für Bonuspunkte: 12 Tage (Freitag Nachmittag, 17:00) Abgabe: Übungen Vorlesungen Sprechstunden Mein Fach im Direktorat, Robert-Mayer-Str. 11-15.
Daten-Repräsentation: Ganze Zahlen Wie repräsentieren wir ganze Zahlen (inklusive negativer Zahlen) im Speicher? Wie immer: Bitmuster!
Daten-Repräsentation: Ganze Zahlen Positive Zahlen: wie bisher Negative Zahlen: verschiedene Verfahren: Vorzeichen-Betrag Exzesscode Einerkomplement Zweierkomplement Negative Repräsentierungen verwenden logische Negation (neg bzw. ): b neg(b) 0 1 1 0 Im Folgenden betrachten wir 8-Bit Zahlen. Die Konzepte funktionieren analog mit größeren n-bit Zahlen.
Vorzeichen-Betrag-Darstellung Beispiel Bitfolge 5 00000101 2-5 10000101 2 0 00000000 2-0 10000000 2 repr( 5) = 10000101 Betrag Vorzeichen Negation x: Oberstes Bit negieren Arithmetik: Benötigt Fallunterscheidung Zwei Nullen: 00000000 2, 10000000 2 Zahlenraum: 2 n 1 1... 2 n 1 1 (symmetrisch)
Exzess-N-code-Darstellung Beispiel Bitfolge 5 10000101 2-5 01111011 2 0 10000000 2-0 10000000 2 repr( 5) = 128 + 5 = 123 = 01111011 2 Hier: Exzess-128-Code (N = 128 = 10000000 2 ) Negation x: 2N x Arithmetik: Benötigt Fallunterscheidung Eine Null: 10000000 2 Zahlenraum: 2 n 1 1... 2 n 1 (asymmetrisch)
Einerkomplement-Darstellung Beispiel Bitfolge 5 00000101 2-5 11111010 2 0 00000000 2-0 11111111 2 repr( 5) = neg(5) = neg(00000101 2 ) = 11111010 2 Negation x: Alle Bits negieren Arithmetik: Ähnlich bei negativ/positiv Zwei Nullen: 00000000 2, 11111111 2 Zahlenraum: 2 n 1... 2 n 1 (symmetrisch)
Zweierkomplement-Darstellung Beispiel Bitfolge 5 00000101 2-5 11111011 2 0 00000000 2-0 00000000 2 repr( 5) = neg(5 1) = neg(00000100 2 ) = 11111011 2 Negation x: Benötigt Fallunterscheidung Arithmetik: Positive/negative Zahlen sehr ähnlich, nur Vorzeichenerweiterung nötig Eine Null: 00000000 2 Zahlenraum: 2 n 1 1... 2 n 1 (asymmetrisch) Einfache Arithmetik kleiner Prozessor
Zusammenfassung: Zahlen mit Vorzeichen Vorzeichen-Betrag: Vorzeichen-Bit repr( 1) = 10000001 2 Exzess-N-code: +N (hier N = 128) repr( 1) = 01111111 2 Einerkomplement: Negation repr( 1) = 11111110 2 Zweierkomplement: Negation, +1 repr( 1) = 11111111 2
Kurze Geschichte der MIPS-Architektur 1984: MIPS Technologies gegründet 1985: MIPS R2000 (bis 16.67 MHz) 1988: MIPS R3000 (bis 40 MHz), kommerziell erfolgreich 1992: Firma von SGI aufgekauft 1999: MIPS64 2007: Basis für Loongson-Prozessor 2012: Aktuellste MIPS-Prozessoren: MIPS32 microaptiv, interaptiv, proaptiv Eingebettete Systeme, Router, Android-Systeme, Allzweckrechner (Loongson)
Bedeutung der MIPS-Architektur RISC: Reduced Instruction Set Computing (Rechnen mit reduziertem Befehlssatz) Prozessoren vor MIPS: Komplexe Befehlssätze, oft für menschliche Programmierer gedacht Komplexer Prozessoraufbau (Mikroarchitektur) RISC ermöglicht mehrere Hardware-Optimierungen ( nächste Vorlesung) RISC verkleinerte Prozessoren Beschleunigung RISC-Idee aufgenommen von SPARC, DEC Alpha, PA-RISC, ARM, PowerPC Moderne x86-prozessoren verwenden intern RISC-Architektur
Einfacher Befehlssatz dank Übersetzern Programmrepräsentierungen und Werkzeuge Höhere Sprache Übersetzer/ Compiler Assembler Assemblersprache Maschinensprache
MIPS-Register Alle Register fassen 32 Bit (64 Bit bei Mips64) Registername(n) Bedeutung PC Programmzähler $0 Konstante 0 $1... $31 Allzweckregister HI, LO Divisions-/Multiplikationsergebnis Weitere Register in den Coprozessoren: Coprozessor 0: Verwaltung von Ausnahmen, Unterbrechungen, und Speicher Coprozessor 1 (optional): Fließkommaberechnung
MIPS-Hardware-Operationen Arithmetische und logische Operationen (add, ori,... ) Ladeoperationen Speicherzugriffsoperationen (lbu, sw,... ) Vergleichsoperationen Sprung- und Verzweigungsoperationen Systemoperationen Operationen auf Coprozessor 0 (Ausnahmen, Speicher) Operationen auf Coprozessor 1 (Fließkomma-Operationen)
Logische Operationen 0 0 1 1 0 1 1 0 and 0 1 0 1 1 1 0 0 0 0 0 1 0 1 0 0 0 0 1 1 0 1 1 0 or 0 1 0 1 1 1 0 0 0 1 1 1 1 1 1 0 0 0 1 1 0 1 1 0 nor 0 1 0 1 1 1 0 0 1 0 0 0 0 0 0 1 0 0 1 1 0 1 1 0 xor 0 1 0 1 1 1 0 0 0 1 1 0 1 0 1 0 0 1 1 0 0 0 1 0 sll 2 1 0 0 0 1 0 0 0 0 1 1 0 0 0 1 0 srl 3 0 0 0 0 1 1 0 0
Arithmetische Operationen Addition (add etc.) Subtraktion (sub etc.) Multiplikation (mul etc.) Division und Divisionsrest (div etc.) Verschiedene Implementierungsvarianten: Vorzeichenbehandlung Operanden Überlaufbehandlung Zielregister
Arithmetische Operationen: Operanden Register-Operanden add $x, $y, $z Beispiel: add $2, $2, $2 0x00 x y z 0x0 0x20 6 5 5 5 5 6 Maschinensprache: R-Format Für alle arithmetischen und logischen Operationen Direktoperand addi $x, $y, v Beispiel: addi $2, $3, 777 0x08 x z v 6 5 5 16 Maschinensprache: I-Format Nur für addi, andi, ori, xori (und einige nicht-arithmethisch/logische Operationen)
Überlauf 32 Bit: 01000002 00012345 64 Bit: 00000123 4502468A HI LO Option #1: 64 Bit-Wert aufteilen in Register HI, LO (z.b. mult, multu)
Überlauf 01000002 00012345 00000123 4502468A Ausgaberegister Option #2: Obere 32 Bit verwerfen (z.b. mul, addu)
Überlauf 01000002 00012345 00000123 4502468A Ausnahme nein = 0? ja Ausgaberegister ok Option #3: Ausnahme, falls obere 32 Bit 0 (z.b. add)
Arithmetische Operationen, Zusammenfassung Ganzzahl-Arithmetik: +,,, / Vorzeichen: Vorzeichenbehaftet: (div) Ohne Vorzeichen: (divu) Operanden: Registeroperanden: add $t1, $t2, $t3 Direktoperanden: addi $t1, $t2, 23 Überlaufverhalten: Ignoriert: (addu): FFFFFFFF + 2 1 Ausnahme: (add): FFFFFFFF + 2 Ausnahme Zielregister: Allzweckregister: mul $x, $y, $z HI und LO: 64-Bit Multiplikation/Division: mult $x, $y ; Obere 32 Bit in HI, untere in LO div $x, $y ; Rest in HI, Ergebnis in LO
Registerladeoperationen Obere 16 Bits direkt laden (untere 16 auf 0): lui $z, v HI- und LO-Register auslesen: mflo $z mfhi $z HI- und LO-Register beschreiben: mtlo $x mthi $x
Speicherzugriff [v + $x] Speicher:... E8 03 CD b 00 01 64 64 C8... Mit Vorzeichen lb $z, v($x) Ohne Vorzeichen lbu $z, v($x) nein 00 < 0 FF ja $z = V V V b $z = 00 00 00 b
Speicherbefehle Lesen: Befehl Name Ausr. B Vorzeichenerweiterung lb $z, v($x) load byte 8 1 ja lbu $z, v($x) load byte unsigned 8 1 nein lh $z, v($x) load halfword 16 2 ja lhu $z, v($x) load halfword unsigned 16 2 nein lw $z, v($x) load word 32 4 Schreiben: Befehl Name Ausrichtung Bytes sb $z, v($x) store byte 8 1 sh $z, v($x) store halfword 16 2 sw $z, v($x) store word 32 4
Sprung- und Verzweigungsbefehle Sprungbefehle ändern den Programmzähler steuern den Programmfluß Bezug zur theoretischen Informatik: Um Turing-vollständig zu sein ( Allzweckrechner ) benötigt ein Rechner: Sprungbefehle Bedingte Ausführung Indirekten Speicherzugriff (oder unendlich große Register)
Sprungbefehle Jeder dieser Befehle setzt den Programmzähler auf a, wenn die Bedingung wahr ist. Befehl Name Bedingung j a jump (keine) beq $x, $y, a branch if equal $x = $y bne $x, $y, a branch if not equal $x $y bltz $x, a branch if less than zero $x < 0 blez $x, a branch if less or equal to zero $x 0 bgtz $x, a branch if greater than zero $x > 0 bgez $x, a branch if greater or equal to zero $x 0
Sprungbefehle in Maschinensprache j ziel: 2 ziel 6 26 ziel: Absolute Adresse im Speicher Um zwei 0-Bits erweitert (Befehle 32-Bit ausgerichtet): PC := ziel 4 Kann noch nicht alle Adressen im Adreßraum anspringen (stattdessen ist u.u. der jr-befehl nötig) beq $x, $y, rziel: 4 $x $y rziel 6 5 5 16 rziel: Relative Adresse im Speicher (Zweierkomplement) Um zwei 0-Bits erweitert: PC := PC + rziel 4
Sprungbefehle: Beispiel 0000: li $1, 1 34 20 00 01 0004: li $2, 1 34 40 Sprungmarken 00 01 0008: eingabe $3 FC 60 00 00 000C: schleife: addi $1, $1, 1 20 21 00 01 0010: beq $1, $3, fertig 10 43 00 03 1000 0014: mul $2, $2, $1 70 41 10 02 0018: j schleife 08 00 00 03 001C: fertig: ausgabe $2 FC 40 00 01 Relative Sprungmarke 00 03 4 + 0010 = 001C Absolute Sprungmarke 0 00 00 03 4 = 000C Verweise auf Sprungmarken werden vom Assembler in relative oder absolute Adressen übersetzt.
Sprungbefehle: Beispiel li $1, 1 li $2, 1 eingabe $3 schleife: addi $1, $1, 1 beq $1, $3, fertig mul $2, $2, $1 j schleife fertig: ausgabe $2 Prozessor $0 = 0 $1 = 0 $2 = 0 $3 = 0
Sprungbefehle: Beispiel li $1, 1 li $2, 1 eingabe $3 schleife: addi $1, $1, 1 beq $1, $3, fertig mul $2, $2, $1 j schleife fertig: ausgabe $2 Prozessor $0 = 0 $1 = 1 $2 = 0 $3 = 0
Sprungbefehle: Beispiel li $1, 1 li $2, 1 eingabe $3 schleife: addi $1, $1, 1 beq $1, $3, fertig mul $2, $2, $1 j schleife fertig: ausgabe $2 Prozessor $0 = 0 $1 = 1 $2 = 1 $3 = 0
Sprungbefehle: Beispiel li $1, 1 li $2, 1 eingabe $3 schleife: addi $1, $1, 1 beq $1, $3, fertig mul $2, $2, $1 j schleife fertig: ausgabe $2 Eingabe: 4 Prozessor $0 = 0 $1 = 1 $2 = 1 $3 = 4
Sprungbefehle: Beispiel li $1, 1 li $2, 1 eingabe $3 schleife: addi $1, $1, 1 beq $1, $3, fertig mul $2, $2, $1 j schleife fertig: ausgabe $2 Prozessor $0 = 0 $1 = 2 $2 = 1 $3 = 4
Sprungbefehle: Beispiel li $1, 1 li $2, 1 eingabe $3 schleife: addi $1, $1, 1 beq $1, $3, fertig mul $2, $2, $1 j schleife fertig: ausgabe $2 Prozessor $0 = 0 $1 = 2 $2 = 1 $3 = 4
Sprungbefehle: Beispiel li $1, 1 li $2, 1 eingabe $3 schleife: addi $1, $1, 1 beq $1, $3, fertig mul $2, $2, $1 j schleife fertig: ausgabe $2 Prozessor $0 = 0 $1 = 2 $2 = 2 $3 = 4
Sprungbefehle: Beispiel li $1, 1 li $2, 1 eingabe $3 schleife: addi $1, $1, 1 beq $1, $3, fertig mul $2, $2, $1 j schleife fertig: ausgabe $2 Prozessor $0 = 0 $1 = 2 $2 = 2 $3 = 4
Sprungbefehle: Beispiel li $1, 1 li $2, 1 eingabe $3 schleife: addi $1, $1, 1 beq $1, $3, fertig mul $2, $2, $1 j schleife fertig: ausgabe $2 Prozessor $0 = 0 $1 = 3 $2 = 2 $3 = 4
Sprungbefehle: Beispiel li $1, 1 li $2, 1 eingabe $3 schleife: addi $1, $1, 1 beq $1, $3, fertig mul $2, $2, $1 j schleife fertig: ausgabe $2 Prozessor $0 = 0 $1 = 3 $2 = 2 $3 = 4
Sprungbefehle: Beispiel li $1, 1 li $2, 1 eingabe $3 schleife: addi $1, $1, 1 beq $1, $3, fertig mul $2, $2, $1 j schleife fertig: ausgabe $2 Prozessor $0 = 0 $1 = 3 $2 = 6 $3 = 4
Sprungbefehle: Beispiel li $1, 1 li $2, 1 eingabe $3 schleife: addi $1, $1, 1 beq $1, $3, fertig mul $2, $2, $1 j schleife fertig: ausgabe $2 Prozessor $0 = 0 $1 = 3 $2 = 6 $3 = 4
Sprungbefehle: Beispiel li $1, 1 li $2, 1 eingabe $3 schleife: addi $1, $1, 1 beq $1, $3, fertig mul $2, $2, $1 j schleife fertig: ausgabe $2 Prozessor $0 = 0 $1 = 4 $2 = 6 $3 = 4
Sprungbefehle: Beispiel li $1, 1 li $2, 1 eingabe $3 schleife: addi $1, $1, 1 beq $1, $3, fertig mul $2, $2, $1 j schleife fertig: ausgabe $2 Prozessor $0 = 0 $1 = 4 $2 = 6 $3 = 4
Sprungbefehle: Beispiel li $1, 1 li $2, 1 eingabe $3 schleife: addi $1, $1, 1 beq $1, $3, fertig mul $2, $2, $1 j schleife fertig: ausgabe $2 Prozessor $0 = 0 $1 = 4 $2 = 6 $3 = 4 Ausgabe: 6
Sprünge mit Registern Befehl Name Effekt jr $x jump to register PC := $x jal ziel jump and link $31 := PC + 4; PC := ziel jal springt und merkt sich Rücksprungadresse jr springt zu berechneter (z.b. von jal gespeicherter) Adresse $31 heißt auch $ra ( Rücksprungadresse bzw. return address)
Subroutinen mit jr und jal Hauptprogramm: jal eingabe... jal eingabe ausgabe $2 eingabe: lw $2, 1000($0) addi $2, 1 sw $2, 1000($0) eingabe $3 jr $ra eingabe markiert eine Subroutine jal eingabe ruft die Subroutine auf Die Subroutine hat folgende Auswirkungen: $3 wird eingelesen Das 32-Bit-Wort auf Adresse 1000 wird um 1 erhöht $2 wird auf den aktuellen Stand dieses Zählers gesetzt
Schwer zu merken: Welche Routinen verändert welche Registerinhalte wie? Aufrufkonventionen fakultaet: li $1, 1 li $2, 1 addi $3, $3, 1 schleife: addi $1, $1, 1 beq $1, $3, fertig mul $2, $2, $1 j schleife fertig: jr $ra Subroutine berechnet Fakultät von Wert in $3 Ergebnis in $2 Seiteneffekte: Inhalte von Registern $3 und $1 werden verändert
Subroutinen auf MIPS Größere Programme werden von mehreren Entwicklern gebaut, u.u. auch in verschiedenen Programmiersprachen Konventionen nötig, um Kommunikation zu erlauben Diese Konventionen geben an: Wo wird welcher Aufrufparameter abgelegt? In welche Register dürfen Subroutinen schreiben? Wo wird welcher Rückgabewert abgelegt? Auswirkungen: Registerkonventionen Speicherkonventionen Wer die Konventionen verletzt, riskiert schwer zu findende Programmfehler
Registerkonventionen Register Name Bedeutung Erhalten? $0 $zero Konstante 0 $1 $at Assembler-Hilfsregister nein $2 $3 $v0 $v1 Rückgabewerte nein $4 $7 $a0 $a3 Argumente (Parameter) nein $8 $15 $t0 $t7 Temporäre Register nein $16 $23 $s0 $s7 Gesicherte Register ja $24 $25 $t8 $t9 Temporäre Register nein $26 $27 $k0 $k1 Kernelregister nein $28 $gp Globaler Zeiger ja $29 $sp Stapelzeiger ja $30 $fp Rahmenzeiger ja $31 $ra Rücksprungadresse Erhaltene Register müssen beim Rücksprung von einer Subroutine den gleichen Zustand wie beim Einsprung haben.
Speicherkonventionen auf 32-Bit MIPS Betriebssystem verwendet Speicher von 0x80000000 0xFFFFFFFF. Stapelspeicher (stack) per Konvention durch $sp begrenzt Ablagespeicher, von Systembibliotheken verwaltet (dynamische Allozierung) Statischer Speicher: Programmvariablen, mit gleicher Lebenszeit wie Gesamtprogramm Programmcode (text) ab 0x00400000 $fp $sp $gp Betriebssystem- Kern (Kernel), Gerätespeicher Stapelspeicher Ablagespeicher Statische Daten Programmcode
Der Stapelspeicher Per Konvention: Stapelspeicher speichert: Variablen von Subroutinen Parameter an Subroutinen $sp zeigt auf nach unten wachsenden Speicher $sp ist 64-Bit-ausgerichtet
Der Stapelspeicher: Beispiel Lokale Variablen: sub: addi $sp, $sp, -8 sw $s0, $sp(0) li $s0, 17... lw $s0, $sp(0) addi $sp, 8 jr $ra $sp $sp Stapelspeicher Prozessor...? 00000064? $s0 = 00000064 00000011 72CB33F5 $sp = 7FFF0A00 7FFF09F8
Der Stapelspeicher: Parameterübergabe Konvention: Für alle Parameter wird Platz auf dem Stapelspeicher alloziert Parameter 0 3 werden in Registern $a0-$a3 gespeichert Parameter ab 4 werden im Stapelspeicher gespeichert $sp Stapelspeicher... Param. 7 Param. 6 Param. 5 Param. 4 (Param. 3) (Param. 2) (Param. 1) (Param. 0)
MIPS-Speicher, Zusammenfassung Bereich unterhalb 0x80000000 für Anwendungen Speicheraufbau von oben nach unten: Stapelspeicher (wächst nach unten) ungenutzter Speicher Ablagespeicher (wächst nach oben) Statische Daten (konstante Größe) Programmcode (konstante Größe) Stapelspeicher nimmt lokale Variablen und Parameter auf $sp ist unteres Ende des Stapelspeichers $fp zeigt auf Aktivierungseintrag (Beginn der lokalen Variablen)
Subroutinen, Zusammenfassung Subroutinen werden mit jal (oder ähnlichen bedingten Sprüngen) aufgerufen Rücksprungadresse wird dabei in $ra abgelegt Parameter in $a0 $a3 und Stapelspeicher Rückgabewerte in $v0, $v1 Subroutinen müssen $s0 $s7, $fp, $sp, $gp erhalten Ablage von Variablen und Parametern im Stapelspeicher
Weitere MIPS-Operationen Pseudo-Operationen Coprozessor 0 Coprozessor 1 (Wir überspringen einige weniger wichtige Operationen.)
Pseudo-Operationen li $2, 0xABCD0123 ; Pseudoinstruktion wird vom Assembler überzetzt zu: lui $2, 0xABCD ori $2, $2, 0x0123 ; load upper immediate: Obere 16 Bits laden ; Bitweise-Oder fügt untere 16 Bits hinzu Einige Pseudo-Operationen benötigen Zwischenablagen, z.b.: blt $t0, $t1, ziel (springt gdw $t0 < $t1) muß erst Differenz zwischen $t0 und $t1 berechnen Register $at ist daher als Zwischenablage reserviert
Coprozessor 0 Speicherverwaltung Behandlung von Unterbrechungen und Ausnahmen: Unterbrechung (interrupt): Ein Eingabe- oder Ausgabegerät fordert Aufmerksamkeit Ausnahme (exception): Der Prozessor ist bei der Befehlsausführung auf eine ungewöhnliche Situation gestoßen: Unbekannter Befehl Arithmetischer Überlauf... Anfrage auf Systemdienst (syscall) 32 Spezialregister (nicht alle verwendet) Zugriff (nur durch Betriebssystemkern!) per: mtc0 $x, CP0Reg ; Lesen mfc0 $x, CP0Reg ; Schreiben
Systemdienste Betriebssystem hat Monopol auf Ein- und Ausgabegeräte Betriebssystemcode kann nur in privilegiertem Modus ( Kernel-Modus ) ausgeführt werden Privilegierter Modus (steuerbar durch Statusregister in CP0) erlaubt: Zugriff auf Register in Coprozessor 0 Zugriff auf Betriebssystemkernspeicher Zugriff auf Gerätespeicher Anwendungsprogramme können Betriebssystemkern um Dienstleistungen bitten: li $v0, Dienstleistungsnummer syscall
Systemdienste: Beispiele Diese Systemdienste werden von dem in den Übungen verwendeten SPIM-Simulator angeboten: $v0 Systemdienst 1 Zahl ausgeben: $a0 4 ASCII-Zeichenkette ausgeben: $a0 zeigt auf 0- terminierte Zeichenkette 5 Zahl einlesen: wird in $v0 gespeichert 8 ASCII-Zeichenkette einlesen: auf Adresse $a0, maximal $a1 Zeichen 10 SPIM-Simulator beenden
Behandlung von Unterbrechungen und Ausnahmen Bei einer Unterbrechung/Ausnahme reagiert der Prozessor wie folgt (in einem Schritt): Programmzähler wird in CP0-Register abgelegt (CP0 14) Prozessor sichert Statusregister und deaktiviert Hardwareunterbrechungen Grund für die Unterbrechung/Ausnahme wird in Register CP0 13 geschrieben Bei Speicherzugriffsfehlern: Fehleradresse wird gesichert System geht in Kernelmodus über System springt nach 0x8000 0080 Das Betriebssystem schreitet ein: Betriebssystemkern analysiert, behandelt Unterbrechung Betriebssystem führt eret-befehl aus: Programmzähler wird aus CP0 14 geladen Alter Status (Unterbrechungen, priv. Modus) wiederhergestellt
Coprozessor 1 Optionaler Zusatzprozessor (bei R2000, R3000) Hardware-Unterstützung für Fließkommazahlen: Basis-Arithmetik Quadratwurzel Zahlenkonvertierung / Rundung Bedingte Sprünge auf dem Zentralprozessor
MIPS-Hardware-Instruktionen, Zusammenfassung Alle Befehle als 32 Bit kodiert Befehle manipulieren die Register: 31 Allzweckregister, PC, HI, LO 32 CP0-Register für Systemverwaltung (nicht alle verwendbar!) CP1-Register für Fließkommazahlen Manche Befehle und Operationen nur in privilegiertem Modus ( Kernel-Modus ) erlaubt Drei Kodierungen für Befehle: R-Instruktionsformat: I-Instruktionsformat: J-Instruktionsformat: op $x $y $z fc op $x $y v op 6 5 5 5 5 6 6 5 5 16 6 26
Nächste Woche: Grundlagen der Performanz