Kernel- und Treiberprogrammierung mit dem Kernel 2.6 - Folge 13
Kern-Technik
Systemaufrufe bilden die Schnittstelle zwischen Userspace und Kernel. Dieser Artikel zeigt, wie sie funktionieren und wie man sogar eigene Systemcalls implementiert.
Systemaufrufe bilden die Schnittstelle zwischen Userspace und Kernel. Dieser Artikel zeigt, wie sie funktionieren und wie man sogar eigene Systemcalls implementiert.
Das Betriebssystem verwaltet Ressourcen wie Rechenzeit, Speicher oder Geräte und stellt Dienste dafür zur Verfügung: Will eine Anwendung auf Hardware zugreifen, tut sie das nicht direkt, sondern vermittelt durch den Kernel, der im Prozessormodus mit der dafür nötigen Priorität läuft. Für die meisten Zwecke bringt das Betriebssystem passende Interface-Funktionen mit, die so genannten Systemaufrufe, die einen definierten Übergang vom User- in den Kernelmodus darstellen. Mehr als 280 Systemcalls finden sich in Kernel 2.6.6.
Jeder Systemcall besitzt eine eindeutige Nummer, die ihm in der Headerdatei »asm/unistd.h« (siehe Listing 1) zugeordnet wird. Der Systemcall »fork«, der einen neuen Prozess erzeugt, trägt beispielsweise die Nummer 2, »write« entspricht 4 und »gettimeofday« der 78. Normalerweise ruft die C-Bibliothek Glibc hinter den Kulissen solche Systemcalls auf, wenn eine Anwendung entsprechende Funktionen benutzt. Um einen Systemcall direkt aufzurufen, muss manuell ein Software-Interrupt ausgelöst werden. Da die Programmiersprache C hierzu keine Funktion anbietet, braucht der Entwickler dafür Assembler.
Es gibt mehrere Software-Interrupts. Für Systemcalls kommt traditionell die Nummer 0x80 zum Einsatz, auch wenn es noch andere Wege gibt, um in den Kernel zu wechseln, siehe Kasten "Intel kann es schneller". Der Interrupt löst den Übergang in den privilegierten Kernelmodus aus. Der Kernel selbst erwartet die Nummer, die den auszuführenden Dienst kennzeichnet (also beispielsweise 4 für »write«), in einem Register, auf einer x86-Plattform im EAX. Die zum Systemcall gehörigen Parameter werden ebenfalls in Registern abgelegt. Abbildung 1 zeigt die Register eines x86-Prozessors, weitere Informationen finden sich unter[1].
Abbildung 1: Die Parameterübergabe zwischen User- und Kernelspace läuft über die Register des x86-Prozessors: Im EAX steht die Nummer des Syscall, die anderen nehmen dessen Parameter auf.
Listing 2 ruft in Assembler den Systemcall »write« auf. Der Filedeskriptor, die Adresse für die Ausgabe und die Anzahl der schreibende Bytes liegen nacheinander in den Registern EBX, ECX und EDX. Um aus dem Assembler-Code ein Programm zu erzeugen, reicht der Aufruf von »make hello« aus. Zum selben Ergebnis führt »gcc -o hello hello.S«.
In den seltensten Fällen schreibt der Anwendungsentwickler seinen Assembler- Code selbst. Für die wichtigsten Systemcalls gibt es in der Standard-C-Library entsprechende Funktionen, die er nur aufzurufen braucht. Aber auch wenn der Systemcall nicht über eine Bibliotheksfunktion zur Verfügung steht, ist Assembler-Programmierung kein Muss. Vielmehr stellt der Kernel einen Satz Makros bereit und die Standardbibliothek hat eine Systemcall-Funktion.
Die »_syscall X()«-Makros versehen einen Systemcall mit einem Funktionsnamen, den der Programmierer später verwenden kann. Das » X« steht dabei für die Anzahl der Parameter. Um beispielsweise den Systemcall »write« mit seinen drei Parametern zu nutzen, kommt das Makro »_syscall3()« zum Einsatz. Listing 4 zeigt den Aufruf.
Da die »_syscall X()«-Makros intern auf die globale Variable »errno« zugreifen, muss der entsprechende Header eingebunden sein (Zeile 2). Außerdem ist sicherzustellen, dass der Compiler die Headerdateien des Kernels 2.6 verwendet, zum Beispiel durch den Aufruf:
cc -Wall -I/usr/src/linux-2.6.6/include -o syscallX syscallX.c
Der erste Parameter des »_syscall X()«-Makros steht für den Typ des Systemcall-Rückgabewerts. Der zweite Parameter entspricht dem Namen des Systemcall, der als solcher in der Datei »asm/unistd .h« festgelegt ist. Der dortige Namensvorsatz »__NR_« ist allerdings wegzulassen; aus »__NR_write« wird damit »write«. Danach folgt - immer paarweise - die Beschreibung der einzelnen Parameter: zuerst der Typ, dann der Name. Zum Einsatz der Makros ist die Datei »asm/unistd.h« einzubinden.
Alle Rezensionen aus dem Linux-Magazin
Im Insecurity Bulletin widmet sich Mark Vogelsberger aktuellen Sicherheitslücken sowie Hintergründen und Security-Grundlagen. mehr...