Systemaufruf – Wikipedia

Ein Systemaufruf, auch Systemcall (von englisch 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 den Kernel übergeben.[1]

In modernen Computersystemen (z. B. der IA-32-Architektur von Intel) läuft der Kernel, also der Betriebssystemkern, in einem privilegierten Kernel-Modus (z. B. Ring 0 auf x86, EL1 auf ARM, S-Mode auf RISC-V; siehe Privilegienstufe) und hat damit meist Zugriff auf den kompletten Befehlssatz der CPU und den gesamten Speicherbereich. Aus Sicherheitsgründen laufen normale Benutzerprozesse dagegen im unprivilegierten Benutzer-Modus (z. B. Ringe 1–3 auf x86, EL0 auf ARM, U-Mode auf RISC-V), 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 Modus 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 Modus und kann so auch nicht Gefahr laufen, andere Prozesse oder gar die Stabilität des Systemkernels 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.

Die Systemaufruf-Schnittstellen, d. h. die ABIs der meisten modernen Betriebssysteme sind nicht standardisiert und können sich zwischen Versionen drastisch ändern. Eine prominente Ausnahme zu dieser Regel ist der Linux-Kernel, welcher eine stabile Systemaufruf-Schnittstelle garantiert.[2] Im Linux-Kernel 4.4 (LTS) für die x86-Architektur sind momentan 376 Aufrufe definiert.[3] Die Anzahl der Systemaufrufe in Microsoft Windows Vista beträgt laut inoffiziellen Quellen 360.[4]

Bibliotheksfunktionen

[Bearbeiten | Quelltext bearbeiten]

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 Bibliotheksfunktionen (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. So ist es möglich, je nach Betriebssystem eine Funktion komplett ohne Systemaufruf bzw. durch eine Kombination mehrerer Systemaufrufe zu implementieren.

Die meisten dieser POSIX-Funktionen haben äquivalente Entsprechungen unter Win32. time unter POSIX entspricht beispielsweise GetLocalTime unter Win32.[5]

Implementierung

[Bearbeiten | Quelltext bearbeiten]

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.

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.

; AT&T Syntax: Instruktion Quelle Ziel 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.[6][7] 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

[Bearbeiten | Quelltext bearbeiten]

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.

Systemaufrufe in Microsoft 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.

; Intel Syntax: Instruktion Ziel Quelle  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 

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.[8]

 LDA #15         ; 15 in das Register A legen  JSR SYS4        ; Zur Kernel-Routine SYS4 (CLOSE) springen 

Mit der Instruktion SVC (Supervisor Call) wird beim IBM System/360 ein Supervisor Call Interrupt ausgelöst, die Instruktion hat ein 1 Byte Feld, mit dem der Supervisor Call ausgewählt wird. Die Parameter werden in Registern übergeben.[9]

 OPEN (PRINTOUT,(OUTPUT)) Makro: Open DCB PRINTOUT, Parameter Register laden, SVC 19 aufrufen  PRINTOUT DCB DDNAME=SYSPRINT,DSORG=PS,MACRF=(PM),LRECL=80 Makro: Data Control Block * 

Einzelnachweise

[Bearbeiten | Quelltext bearbeiten]
  1. A. Tanenbaum: Moderne Betriebssysteme. Pearson Studium, 2009, ISBN 978-3-8273-7342-7, S. 85.
  2. Adding a New System Call — The Linux Kernel documentation. Abgerufen am 25. Dezember 2023 (englisch): „A new system call forms part of the API of the kernel, and has to be supported indefinitely.“
  3. git.kernel.org (21. November 2016)
  4. Windows X86 System Call Table (NT/2000/XP/2003/Vista/2008/7/8), abgerufen am 14. April 2015
  5. A. Tanenbaum: Moderne Betriebssysteme. Pearson Studium, 2009, ISBN 978-3-8273-7342-7, S. 97.
  6. linux-magazin.de
  7. manugarg.googlepages.com
  8. commodore.ca Anhang H (21. Juni 2007)
  9. IBM System/360 Principles of Operation. IBM, abgerufen am 15. März 2022 (englisch).