- Systemaufruf
-
Ein Systemaufruf, auch Systemcall (von engl. system call) oder kurz Syscall, ist in der Computertechnik eine von Anwendungsprogrammen benutzte Methode, um vom Betriebssystem bereitgestellte Funktionalitäten auszuführen, wie etwa das Lesen einer Datei. Dabei wird die Kontrolle vom Programm an das Betriebssystem übergeben.[1]
Inhaltsverzeichnis
Details
In modernen Computersystemen (z. B. der IA-32-Architektur von Intel) läuft der Kernel, also der Betriebssystemkern, im so genannten privilegierten Ring 0 (auch Kernel-Modus genannt) und hat damit Zugriff auf den kompletten Befehlssatz der CPU und den gesamten Speicherbereich. Aus Sicherheitsgründen laufen normale Benutzerprozesse dagegen in den unprivilegierten Ringen 1 bis 3 (Benutzer-Modus), in denen ihnen weniger Befehle zur Verfügung stehen und sie daher gewisse Aufgaben nicht direkt erledigen können. Muss ein im Benutzer-Modus laufender Prozess eine Aufgabe erfüllen, die nur in einem höher privilegierten Ring möglich ist, wie z. B. der Zugriff auf die Festplatte oder andere Hardware, kann er dies dem Kernel durch einen Systemaufruf mitteilen und so einen Kontextwechsel veranlassen. Dabei gibt der Prozess die Kontrolle über die CPU an den Kernel ab und wird so lange unterbrochen, bis die Anfrage komplett bearbeitet ist. Nach dem Systemaufruf gibt der Kernel die CPU wieder an den Prozess im Benutzer-Modus ab und führt den Programmcode an der Stelle fort, an der der Kontextwechsel zuvor gefordert wurde. Zu keiner Zeit verlässt der Prozess seinen unprivilegierten Ring und kann so auch nicht Gefahr laufen, andere Prozesse oder gar die Stabilität des Systemkerns selbst zu gefährden, da nur vertrauenswürdiger Code aus dem Kernel im privilegierten Modus ausgeführt wird.
Ein Systemaufruf kann sowohl dafür zuständig sein, Informationen an die Hardware, den Kernel selbst oder andere Prozesse zu schicken, als auch solche zu lesen. Dafür ist es manchmal notwendig, Daten aus dem privaten Speicherbereich des Kernels, auf den ein normaler Prozess keinen Zugriff hat, in den Adressbereich eines Prozesses zu kopieren, damit der Prozess auch nach dem Aufruf noch Zugang zu den Daten hat und mit den Ergebnissen seiner Anfrage überhaupt etwas anfangen kann. Auch dies ist eine Folge der strikten Trennung zwischen Kernel-Modus und Benutzer-Modus.
Alle POSIX-kompatiblen Betriebssysteme müssen bestimmte Aufrufe implementieren, damit eine gewisse Portabilität zwischen den Systemen gewährleistet ist. Viele Hersteller fügen dem Standard aber auch eigene Erweiterungen hinzu, um dem Programmierer zusätzliche Möglichkeiten zu eröffnen. Im Linux-Kernel 2.6 für die x86-Architektur sind momentan 326 Aufrufe definiert.[2] Die Anzahl der Systemaufrufe in Microsoft Windows Vista beträgt laut inoffiziellen Quellen 360.[3]
Bibliotheksfunktionen
Die meisten Systeme stellen eine Programmierschnittstelle (API) für Systemaufrufe in Form von Bibliotheksfunktionen zur Verfügung, die es einem Programmierer erleichtern, Arbeiten zu erledigen, die einen erweiterten Zugriff auf den Befehlssatz der CPU erfordern.
Häufig verwendete Funktionen (beispielsweise unter POSIX), die auf Systemaufrufen basieren, sind unter anderem die Dateiverarbeitungsfunktionen open, close, read und write, sowie exec, fork oder exit. Diese können vom Programmierer wie normale Benutzer-Modus-Funktionen genutzt werden, führen aber unbemerkt im Hintergrund einen Kontextwechsel durch. Die Abkapselung der Systemaufrufe über eine API befreit den Programmierer vollständig von Überlegungen über die interne Funktionsweise des Betriebssystems oder der Hardware und erlaubt eine abstraktere Softwareentwicklung.
Die meisten dieser POSIX-Funktionen haben äquivalente Entsprechungen unter Win32. time unter POSIX entspricht beispielsweise GetLocalTime unter Win32.[4]
Implementierung
Die Implementierung von Systemaufrufen hängt stark von der verwendeten Hardware, der Architektur und letztlich auch dem benutzten Betriebssystem ab. In der Regel wird heute ein Systemaufruf mit Softwareinterrupts oder anderen Spezialinstruktionen der CPU realisiert. Bei älteren Systemen findet meist einfach nur ein Sprung an eine fest definierte Adresse statt, an der der Systemaufruf oder ein Sprungbefehl zu diesem implementiert ist. Für einen Programmierer, der die zur Verfügung gestellte Programmierschnittstelle des Betriebssystems nutzt, ist die Implementation von Systemcalls irrelevant.
In den Beispielen wird eine geöffnete Datei mit dem Dateideskriptor/Handle 15 geschlossen.
Linux
Der Linux-Kernel beherbergt eine Liste aller ihm bekannten Systemaufrufe, die so genannte System Call Table. Jedem Systemaufruf wird dort eine eindeutige Nummer und eine Kernel-interne Funktion zugeordnet, die für die eigentliche Erledigung der erforderlichen Aufgaben zuständig ist. Um einen Systemcall durchzuführen, wird die Nummer des gewünschten Aufrufs in das EAX-Register der CPU gespeichert und anschließend der Softwareinterrupt 128 (in hexadezimaler Schreibweise 0x80) ausgelöst. Argumente an den Systemaufruf werden gemäß der FastCall-Aufrufkonvention in den CPU-Registern abgelegt.
Der Softwareinterrupt (auch Exception genannt) unterbricht die Programmausführung im Benutzer-Modus und erzwingt das Ausführen eines Exception-Handlers im Kernel-Modus. Dadurch wird der Kontextwechsel von einem unprivilegierten Ring auf Ring 0 gewährleistet. Der aufgerufene Exception-Handler ist eine Funktion im Kernel, die das EAX-Register ausliest und dann, sofern sich darin eine gültige Systemaufruf-Nummer befindet, die entsprechende Kernel-Funktion aus der System Call Table mit den in den weiteren Registern liegenden Argumenten aufruft. Nach der Überprüfung der Argumente werden letztlich die aus dem Benutzer-Modus angeforderten Aufgaben vom Kernel erledigt. Kehrt diese Funktion zurück, wird auch der Exception-Handler erfolgreich abgeschlossen und der normale Programmfluss im unprivilegierten Modus fortgesetzt.
mov $6, %eax ; close() ist Systemaufruf 6 mov $15, %ebx ; Dateideskriptor als erstes Argument int $0x80 ; Softwareinterrupt auslösen
Softwareinterrupts haben aber nur eine sehr geringe Ausführungsgeschwindigkeit. Deshalb haben sowohl Intel als auch AMD in ihren x86-Prozessoren Befehle implementiert (sysenter/sysexit, bzw. syscall/sysret, letztere beiden erst ab der 64-Bit-Architektur AMD64), die die Aufrufe beschleunigt durchführen können. Da jedoch nicht jeder x86-Prozessor einen kompatiblen Befehl unterstützt, wird bei aktuellen Linux-Versionen die sogenannte vsyscall-Page verwendet, in der der für die benutzte Architektur passende Code hinterlegt wird.[5][6] Wenn ein Programm nun einen Systemcall ausführen will, springt es zu dieser Speicherseite und führt dort den Programmfluss fort.
Unix und Unix-Varianten
Bei vielen Unix-Varianten (z.B. älteren Solaris-Versionen) wird eine sogenannte "call gate" verwendet. Dabei verwendet die Anwendung (im Ring 3) einen Sprungbefehl an eine spezielle Adresse, die das Call-Gate beschreibt.
Call Gates sind zwar schneller als Software-Interrupts (Cyrix 6x86: 23%), benötigen jedoch 7 anstatt 2 Bytes Code.
Neuere Solaris-Versionen beherrschen alternativ Software-Interrupts sowie Syscall und Sysenter-Befehle.
Windows
Systemaufrufe in Microsofts Windows werden ähnlich wie in Linux gehandhabt. Die im Programmcode aufgerufene Bibliotheksfunktion aus der Windows API wird zunächst intern in einen Aufruf der so genannten Native API umgewandelt. Dort wird eine für jeden Aufruf eindeutige Nummer in das EAX-Register gelegt und ein Zeiger auf die Argumente für die Funktion im EDX-Register gespeichert. Über die Assembler-Instruktion sysenter wird die Kontrolle aus dem Benutzer-Modus an den privilegierten Kernel abgegeben, der die Parameter überprüft und anschließend eine der im EAX-Register liegenden Nummer zugeordnete Kernel-Funktion ausführt.
mov eax, 0x2f ; NtClose() trägt die Nummer 0x2f in Windows Vista push 15 ; 15 auf den Stack legen mov edx, esp ; Pointer auf 15 in EDX speichern sysenter ; Systemaufruf durchführen
Commodore
Beispielhaft für die Funktionsweise von Systemaufrufen in älteren Systemen sei hier die Methode der frühen Commodore-Rechner genannt. Die dort verwendeten Prozessoren der MOS Technology 6502-Familie kannten noch keine Unterscheidung zwischen Benutzer-Modus und Kernel-Modus und so war es möglich, die Kernel-internen Systemcall-Funktionen direkt aus dem normalen Programmfluss aufzurufen. Der einzige Sinn von Systemaufrufen war zu dieser Zeit daher nicht das Durchführen von Tätigkeiten, die bestimmte Privilegien verlangten, sondern eine vom Kernel implementierte Menge an standardisierten Funktionen bereitzustellen, die unabhängig von weiteren Bibliotheken sind und sich auch noch auf späteren Versionen des Systems benutzen lassen sollten (siehe auch bei Sprungtabelle).
Der Programmierer konnte, nachdem er die Argumente der Funktion in die entsprechenden CPU-Register gelegt hatte, einfach mittels des Assembler-Befehls JSR (Jump to SubRoutine), gefolgt von einer Adresse oder einem symbolischen Funktionsnamen, eine im Kernel-Adressraum liegende Routine anspringen. Zur Zeit von Commodore BASIC 4.0 gab es 28 Systemaufrufe.[7]
LDA #15 ; 15 in das Register A legen JSR SYS4 ; Zur Kernel-Routine SYS4 (CLOSE) springen
Einzelnachweise
- ↑ A. Tanenbaum: Moderne Betriebssysteme. Pearson Studium, 2009, ISBN 978-3827373427, S. 85.
- ↑ http://lxr.linux.no/linux/arch/x86/kernel/syscall_table_32.S (1. Oktober 2008)
- ↑ http://www.metasploit.com/users/opcode/syscalls.html (20. Juni 2007)
- ↑ A. Tanenbaum: Moderne Betriebssysteme. Pearson Studium, 2009, ISBN 978-3827373427, S. 97.
- ↑ http://www.linux-magazin.de/heft_abo/ausgaben/2004/08/kern_technik
- ↑ http://manugarg.googlepages.com/systemcallinlinux2_6.html
- ↑ http://www.commodore.ca/manuals/pdfs/Commodore_Basic_4_Users_Reference%20Manual.pdf, Anhang H (21. Juni 2007)
Kategorie:- Programmiersprachelement
Wikimedia Foundation.