Benutzerprogramme und Benutzer-Adressraum Prof. Dr. Margarita Esponda Freie Universität Berlin WS 2011/2012 1
Benutzerprogramme Prozess src1.c Compiler cc src1.s Assembler as src1.o ELF Executable and Linkable Format Unix-Standard-Binärformat Prozessabbild Stapel src2.c cc src2.s as src2.o Linker ld a.out Loader OS... BSS Daten srcx.c cc srcx.s as srcx.o Programm 2
Ausführbare Dateien Betriebssysteme brauchen ein spezielles Format: mit mindestens: - Ein Kopf mit Information über Daten- und Programm-Startadressen - die eigentlichen Daten + Programmteile - Eine Symboltabelle mit zwei Listen - selbst definierte Namen - Namen, die in anderen Modulen definiert sind 3
0 ELF-Datei ELF header Program header table (required for executables).text section.data section.bss section.symtab.rel.txt.rel.data.debug Section header table (required for relocatables) ELF-Kopfinformation magische Zahl, Typ (.o, exec,.so), usw. Programmkopf-Tabelle Seitengröße, virtuelle Adresse der Speichersegmente (Sektionen), Segment-Größe..text section Code.data section initialisierte statische Daten.bss section nicht-initialisierte statische Daten.symtab section Symbol-Tabelle statische Variablen- und Funktionsnamen, usw..rel.text section.rel.data section.debug section 4
ELF Header: E L F Magic: 7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00 Class: ELF32 Data: 2's complement, little endian Version: 1 (current) OS/ABI: UNIX - System V ABI Version: 0 Type: EXEC (Executable file) Machine: Intel 80386 Version: 0x1 Entry point address: 0x80482d0 Start of program headers: 52 (bytes into file) Start of section headers: 10148 (bytes into file) Flags: 0x0 Size of this header: 52 (bytes) Size of program headers: 32 (bytes) Number of program headers: 6 Size of section headers: 40 (bytes) Number of section headers: 29 Section header string table index: 26 5
Adressbindung Die Adressbindung des Programms und der Daten kann zu drei verschiedenen Zeitpunkten stattfinden. Übersetzungszeit - absolute Adressen werden generiert - bei veränderter Startadresse muss das Programm neu übersetzt werden Ladezeit - der Übersetzer muss relocatable code erzeugen - die Adressbindung wird verzögert, bis das Programm im Speicher geladen wird Ausführungszeit - ein Programm kann während seiner Ausführung im Speicher verschoben werden. Eine neue Adressbindung kann zur Laufzeit stattfinden 6
Quellprogramm Adressbindung Übersetzer + Assembler Übersetzungszeit Objekt-Code Andere Objektmodule des Programms Linker Systembibliothek Lademodule Ladezeit Dynamisch ladbare Systembibliothek Lader Im Speicher ausführbares Programm Ausführungszeit
Dynamisches Laden - Ein Programmmodul wird erst geladen, wenn es gebraucht wird. - Nicht benutzte Programmteile werden nie geladen. - Dadurch wird eine bessere Speicherbenutzung erreicht. - Sehr nützlich, wenn viele Programmmodule nur selten benutzt werden. - Spezielle Unterstützung des Betriebssystems ist dafür nicht notwendig. - Gutes dynamisches Laden wird durch gutes Programmdesign erreicht. 8
Dynamisches Linking Einige Betriebssysteme benutzen statisches Linking. Mit dynamischem Linking wird ein stub verwendet, um eine Systemfunktion zu finden. Ohne dynamisches Linking muss jeder Prozess in dem ausführbaren Programm eine Kopie der Systemfunktionen, die er verwendet, einfügen. Dynamisches Linking erlaubt die gemeinsame Verwendung von Systembibliotheken. Neue Versionen der Systembibliotheken können benutzt werden, ohne dass die Programme neu übersetzt werden müssen. 9
Benutzerprogramme Prozess src1.c Compiler cc src1.s Assembler as src1.o ELF Executable and Linkable Format Unix-Standard-Binärformat Prozess-Layout Stapel src2.c cc... src2.s as src2.o Linker ld a.out Loader OS BSS srcx.c cc srcx.s as srcx.o Dynamic libraries *.dll Daten Programm Static libraries *.a 10
Absolute und relozierbare Lademodule Objektmodul Programm jmp a load z: 1024: Programm 0: Programm jmp 1500 jmp 476 load 2000 load 976 a: 1500: 476: Daten Daten Daten z: 2000: 976: Symbolische Adressen Lademodul mit absoluten Adressen Lademodul mit relativen Adressen 11
Namenbindung + Adressbindung Beispiel: exam.c int z = 2; int main() { int ret = func(); exit (0); } func.c extern int z; int *z_p = &z; int x = 5; int y; int func() { return *e_p + x + y; } Verschiebbare Objektdateien System-Code System-Daten.text.data &.bss exam.o main().text int z = 2.data func.o func() int *z_p = &z int x = 5 int y.text.data.bss Ausführbare Objektdatei 0 Headers System-Code main() func() System-Code System-Daten int z = 2 int *z_p = &z int x = 5 int y.symtab.debug.text.data.bss 12
Beispiel: main.c extern float sin(); extern printf(); extern scanf(); main() { double x, result; printf( "number: " ); scanf( "%f", &x ); result = sin(x); printf( "sin is %f\n", result ); } stdio.c int printf( char *fmt,...) {... } int scanf( char *fmt,...) {... } math.c double sin(double x) { static double res, lastx; if (x!= lastx) { lastx = x; berechnet sin(x) } return res; } 13
Objektdatei main.c main.o extern float sin(); extern printf(); 20... call printf....text extern scanf(); 32 call scanf... main() { double x, result; 40 56 call sin... call printf printf( "number: " ); scanf( "%f", &x ); def: main@t:0.symtab result = sin(x);.rel printf( "sin is %f\n", result ); ref: printf@t:20, T:56 } ref: scanf@t:32 ref: sin@t:40 14
math.c double sin(double x) { static double res, lastx; if (x!= lastx) { lastx = x; berechnet sin(x) } return res; } 8 20 204 0 8 Objektdatei math.o... load lastx... store lastx... load res res: lastx: def: sin@t:0 def: lastx@d:0 def: res@d:8 ref: lastx@t:8, T:20 ref: res@t:204.text.data.symtab.rel 15
Memory map: Nach dem ersten Durchgang: 836 708 stdio.o math.o.data.data Symbol table: main: 0 sin: 64 700 stdio.o.text lastx: 700 result: 708 printf: 314 276.text scanf: 508 math.o Verschiebung 40 main.o... call 0....text 64 0 main.o main.o ref: sin@t:40.rel.text math.o sin: 64.symtab 40 a.out... call 64....text 16
Dynamisches Linking/Laden printf.c printf.o gcc ar shared Library Hi.c gcc Hi.o Linker Hi.out Prozesse können schneller gestartet werden und brauchen weniger Speicher Run-time Loader Loader Speicher 17
Logische vs. Physikalische Adressen Das Konzept einer logischen Speicheradresse, die eine Bindung zu einer getrennten physikalischen Adresse hat, spielt eine zentrale Rolle in der Speicherverwaltung. Die CPU arbeitet mit logischen oder virtuellen Adressen. Die MMU arbeitet direkt mit den physikalischen Adressen. 18
Memory Management Unit (MMU) Die MMU ist der Teil der Hardware, der logische Adressen in physikalische Adressen umrechnet. Der Wert des Relokation-Registers wird zu jeder logischen Adresse addiert. Das Benutzerprogramm arbeitet nur mit logischen Adressen. Er sieht nie die physikalische Adresse, welche die MMU sieht. Die Relocation- und Limit-Register sind Teil des Prozesskontextes und werden von dem Dispatcher als Teil des Kontextwechsels des Prozesses geladen. 19
Logische vs. Physikalische Adressen CPU logische Relokation- Register 12000 + physikalische Adresse Adresse 770 770 12770 Hauptspeicher MMU 20
Logische vs. Physikalische Adressen 1024: Virtuelle Adressen Physikalische Adressen 4096: 0: free 512: 0: MMU 1024: 0: 0: Betriebssystem Kernel
Virtueller Speicher Die Programme generieren nur virtuelle Adressen. Prozess virtuelle Adresse Speicher- Verwaltung MMU+BS physische Adresse Hauptspeicher Bei Rechnern mit virtuellem Speicher geht die Adresse nicht direkt an den Hauptspeicher, sondern an die MMU Memory Festplatten- Adresse Der Adressraum wird in Hauptspeicher und Hintergrundspeicher aufgeteilt. Managment Unit, die die virtuelle Adresse auf die Hintergrundspeicher physikalische Adresse abbildet.
Copy-on-Write Vaterprozess Hauptspeicher Kindprozess Der fork()-systemaufruf ist Seite A sehr effizient, weil der Seite B Kindprozess am Anfang die gleichen Seiten wie der Seite C Vaterprozess benutzt. Die gemeinsamen Seiten Seite A werden als Copy-on-Write Seite B markiert. Nur wenn eine Seite C Seite verändert wird, wird kopiert. Seite C (Kopie) Zustand, nachdem der Vaterprozess die Seite C verändert hat
vfork() Das vfork(). Virtuelle fork() Beim vfork wird der Elternprozess suspendiert, und der Kindprozess benutzt den Adressraum des Elternprozesses ohne Verwendung von Copy-on-Write. Wenn der Kindprozess eine Seite verändert, verändert sich der Adressraum des Elternprozesses. Es wird verwendet, wenn wir sicher sind, dass unmittelbar nach dem vfork-systemaufruf ein exec-systemaufruf angewendet wird, der einen neuen Adressraum für den Kindprozess erzeugt. vfork wird sehr oft für die effiziente Implementierung von Shells verwendet.
Memory-Mapped-Dateien Ein Prozess kann eine Datei in einem Teilbereich seines virtuellen Adressraumes einblenden. Ein Teil der Datei wird auf die eingeblendeten Seitenspeicher eingelesen. Anstatt durch eine Reihe von zeitaufwändigen Lese- und Schreibe-Systemaufrufen kann auf die Datei wie auf ein großes Zeichenfeld im Speicher direkt zugegriffen werden. Dateien können dann gemeinsam von mehreren Prozessen benutzt werden, indem diese die entsprechende Seite in ihrem virtuellen Speicherraum einblenden lassen.
MMAP verschiedene Objekte werden im Prozess-Adressraum abgebildet Virtuelle Adressraum Prozess- Adressraum MMAP Frame buffer Festplatte Shared memory 26
#include <sys/mman.h>... MMAP MAP_SHARED file descriptor void * mmap (void *addr, size_t len, int prot, int flags, int fd, off_t offset); Virtuellen Speicher des Prozesses Prozessabbild Stapel mmap (addr, len, flags, fd, offset) BSS Daten A Programm
Memory-Mapped-Dateien void * mmap( void * addr, size_t len, int prot, int flags, int fildes, off_t off); - addr "Wunschadresse", ab welcher die Daten im Adressraum erscheinen sollen. Die Adresse muss ein vielfaches der Seitengröße sein. Wenn eine 0 angegeben wird, entscheidet das Betriebssystem über die Adresse. Als Rückgabewert wird die tatsächliche Adresse zurückgegeben, oder MAP_FAILED (-1). - len Länge des Speicherbereiches. - fd file descriptor der Datei - prot Zugriffsrechte auf den Speicher, die mit den Zugriffsrechte auf die Datei passen müssen. - flags kann das Verhalten von mmap beeinflussen Folgende Makros sind definiert: MAP_FIXED ein Fehler wird zurückgegeben, Wenn die angegebene Adresse nicht benutzt werden kann. MAP_SHARED Der physikalische Speicher wird von verschiedenen Prozessen gemeinsam benutzt und Änderungen werden in allen Prozessen sofort sichtbar. MAP_PRIVATE Bei Änderungen bekommt der jeweilige Prozess eine eigene Kopie des Speichers. - Mit offset wird die Startposition innerhalb der Datei angegeben.
Memory-Mapped-Dateien physikalischer Speicher Logischer Speicher von Prozess A Logischer Speicher von Prozess B Datei 29
#include <unistd.h> #include < sys/mman.h> Memory-Mapped-Dateien void * mmap( void * addr, size_t len, int prot, int flags, int fildes, off_t off); #include <unistd.h> #include <sys/types.h> #include <sys/mman.h> int fd, pagesize; char *data; Adresse, auf die die Datei eingeblendet wird. Hier entscheidet sich das Betriebssystem Länge der Daten fd = fopen( "foo", O_RDONLY ); pagesize = getpagesize(); data = mmap((caddr_t)0, pagesize, PROT_READ, MAP_SHARED, fd, offset);
Memory-Mapped-Dateien void * munmap( void * addr, size_t len ); munmap macht das einblenden der Datei in den entsprechenden Speicherbereich wieder rückgängig. Der Speicherinhalt wird in die Datei zurückgeschrieben. Beim Beenden eines Prozesses werden alle eingeblendeten Speicherbereiche wieder freigegeben. 31
Unterbrechungen Zwei Kategorien: Interne Unterbrechungen (internal interrupts) Synchrone Unterbrechungen weil diese während der Ausführung von Anweisungen innerhalb des Prozessors verursacht und festgestellt werden Exceptions Programfehler: faults, traps, and aborts Software: INT 3 Machine-check exceptions Externe Unterbrechungen (external interrupts) Asynchrone Unterbrechungen verursacht von Hardware außerhalb des Prozessors - system timer Interrupt Sources Hardware-Ein-/Ausgabe-Geräte Software: INT n - keyboard - serial port - disk 32
Unterbrechungen in 80x86 Architektur Die 80x86 CPU-Architektur unterstützt 256 verschiedene "interrupts" es gibt 256 "interrupt handler", die in der IDT "interrupt descriptor table" definiert sind. 33
Systemaufrufe - von Betriebssystem bereitgestellte Schnittstelle - zum Interagieren mit der Hardware - für die Durchführung von privilegierten Operationen - verursachen Unterbrechungen - werden mit Hilfe von Wrapper-Funktionen innerhalb der C-Bibliotheken bereit gestellt. Z.B. malloc(), open(), socket(), read(), write(), usw. - Zwei Wege für die Umschaltung von User-Modus zum Kernel-Modus in der x80-architekturen sind: Was passiert? "int $0x80" oder "sysenter" - wechselt ins Kernel-Modus - Registern werden gespeichert - Systemaufruf wird gestartet. Die ID des Systemaufrufs zusammen mit den Parametern werden in dem Benutzerstapel gepuscht, bevor die Ausführung des Systemaufrufs gestartet wird. 34
Beispiel eines Systemaufrufs Benutzermodus #include <stdio.h> main() { printf("my first C-Program"); } /* C Standardbibliothek */ int printf( const char *format,...){ write() } Kernmodus trap return write()-systemaufruf 35
Benutzerprogramme und System-Aufrufe Benutzer-Modus Prozess A Prozess B... Systemaufruf Prozess C Kernel-Modus sys_call_table Syscall_Handler System service dispatcher.text ALIGN ENTRY(sys_call_table).long sys_ni_syscall.long sys_exit.long sys_fork.long sys_read.long sys_write.long sys_open.long sys_clos.long sys_waitpid.long sys_creat.long sys_link.long sys_chmod System services Treiber Treiber Treiber 36
Systemaufrufe Benutzer-Modus read()... read(){ int 0x80 } systemcall() sys_read() ret_from_sys_call()... Kernel-Modus sys_read(){ } zwei spezielle Befehle: int Interrupt-Befehl iret Interrupt-Return-Befehl 37
Parameter-Übergabe 1. Direkt durch die Register eax enthält die ID des Systemauf Intel ebx, ecx, edx, esi, edi für weitere Parameter Probleme: maximal 6 Parameter Werte-Bereich auf 32 Bits beschränkt Vorteile: sehr schnell 2. Ausführungsstapel die Parameter werden vom Stapel geholt 38
Parameter-Übergabe 3. Die Adresse einer Tabelle, in der die Parameter sind, wird in ein Register geschrieben. Linux und Solaris x Register Benutzerprogramm Betriebssystem 39
Systemaufrufe Nach der Parameterübergabe... EntryPoint: switch to kernel stack save context check Register 0 User stack User memory Registers call the real code pointed by Register 0 restore context switch to user stack iret (change to user mode and return) Kernel stack Kernel memory Registers 40
Entwurf-Entscheidungen Allgemeine Aspekte: - Was ist einfacher? - Was ist übersichtlicher? - wie groß ist der Ausführungs-Overhead? Spezielle Aspekte: - Wertbereich von Parameter bzw. Ergebnisse - wie sicher sind die Programme - wie viele CPUs? - Implementierung-Aufwand 41
x86 Schutz-Ringe Betriebssystem Kernel Betriebssystem Dienste Anwendungen Level 0 Level 1 Level 2 Level 3 Privilegierte Operationen nur in "Level 0" 42
Implementierung von Systemaufrufen Jeder Systemaufruf bekommt eine Zahl zugeordnet. Diese Zahl wird als Index in einer Tabelle verwendet, mit Hilfe derer der Systemaufruf stattfindet..text ALIGN ENTRY(sys_call_table).long sys_ni_syscall /* 0 - old "setup()" system call*/.long sys_exit.long sys_fork.long sys_read.long sys_write.long sys_open /* 5 */.long sys_close.long sys_waitpid.long sys_creat.long sys_link.long sys_unlink /* 10 */.long sys_execve.long sys_chdir.long sys_time.long sys_mknod.long sys_chmod /* 15 */... 43
Ausnahmen und Unterbrechungen Hardware Geräte- Unterbrechung System Service Call Hardware-Exceptions Software-Exceptions VM-Exceptions sys_call_table System service dispatcher Exception dispatcher VM manager s pager Interrupt service routines System services Exception handlers 44
Linux-Dateien für Systemaufrufe Wichtige Dateien: arch/i386/kernel/entry.s Systemaufrufe low-level fault handling routines. include/asm-i386/unistd.h Systemaufrufe-IDs und Makros. kernel/sys.c die eigentliche Implementierungen der Funktionen sind hier. 45