4.1. Die Programmierschnittstelle der Applikation

Applikationen fordern über Systemcalls die Dienste des Kernels an. Die wesentlichen Systemcalls des Interfaces für den Zugriff auf Dateien und Geräte heißen:

Sämtliche hier aufgeführten Systemcalls stehen dem Programmierer in Form von Funktionen der Standardbibliothek, die den Namen des jeweiligen Systemcalls tragen, zur Verfügung. Die genaue Aufrufsyntax lässt sich über so genannte Manpages, siehe Kapitel Theorie ist notwendig, erfahren. In der Shell wird dazu das Kommando man abgesetzt. Um beispielsweise eine Beschreibung der Funktion open des namensgleichen Systemcalls zu erhalten, lautet das vollständige Kommando »man 2 open«.

Viele Programmierer verwenden für den Zugriff auf Dateien die Funktionen fopen, fclose, fread etc. Diese Funktionen gehören zur so genannten gepufferten Ein-/Ausgabe (Buffered IO). Es handelt sich um Bibliotheksfunktionen, die intern auf die oben genannten Funktionen respektive Systemcalls aufsetzen. Für den Zugriff auf Geräte sind sie im Regelfall nicht geeignet.

Damit Geräte für das Anwendungsprogramm wie Dateien aussehen, wird jedes Gerät im Dateisystem als so genannte Gerätedatei repräsentiert. Anhand eines Flags kann der Betriebssystemkern die Gerätedateien von normalen Dateien unterscheiden.

Möchte ein Anwendungsprogramm auf ein Gerät bzw. eine Datei schreiben oder lesen, muss es sich zunächst beim Kernel anmelden. Dazu dient der Systemcall open. Der Name der Datei, von der gelesen werden soll bzw. auf die geschrieben werden soll, wird dieser Funktion als Parameter übergeben. Der zweite Aufrufparameter spezifiziert die Art des Zugriffs: Wird lesend oder schreibend, blockierend oder nicht blockierend zugegriffen?

Innerhalb des Kernels wird nach Aufruf von open überprüft, ob die Applikation die notwendigen Zugriffsrechte besitzt. Innerhalb des Treibers wird darüber hinaus festgestellt, ob ein Zugriff auf das angeforderte Gerät überhaupt möglich ist. Sind entweder die Zugriffsrechte verletzt oder verweigert der Treiber den Zugriff auf das Gerät, erhält die Applikation einen negativen Rückgabewert.

Ein positiver Rückgabewert hingegen stellt einen gültigen Filedeskriptor dar, der für die weiteren Zugriffe innerhalb des Anwenderprogramms verwendet wird. Der eigentliche Datenaustausch zwischen der Applikation und dem Gerät wird unter Verwendung des Filedeskriptors über die Systemcalls read und write abgewickelt.

int read( int fd, void *buf, int MaxBytesToRead). Der Systemcall read bekommt neben dem Filedeskriptor noch die Adresse und Größe eines Speicherbereiches übergeben, in den die Daten vom Gerät geschrieben werden sollen. Der Systemcall read kopiert minimal 1 Byte in diesen Speicherbereich, maximal so viele Bytes, wie im dritten Parameter (»MaxBytesToRead«) angegeben sind.

read gibt die Anzahl der gelesenen Bytes zurück. Wird Null (»0«) zurückgegeben, bedeutet dies »Dateiende«: Es gibt keine weiteren Daten, die gelesen werden können.

Die Bearbeitung des read-Systemcalls durch den Betriebssystemkern verläuft nicht immer störungs- bzw. fehlerfrei. Zuweilen wird eine Applikation genau zum Abarbeitungszeitpunkt unterbrochen. Unterbrechungen sind unter Linux grundsätzlich in Form von Signalen (Signals) realisiert. Treten bei der Abarbeitung Störungen auf, gibt read einen Fehlercode in Form eines negativen Integerwertes zurück. Diese Fehlercodes sind für die C-Programmierer als Defines in der Header-Datei <asm/errno.h> abgelegt. Für read sind unter anderem folgende Fehlercodes spezifiziert:

EINTR

Die Bearbeitung des Systemcalls ist durch ein Signal unterbrochen worden. Es wurden keine Daten gelesen.

EAGAIN

Zum Zeitpunkt des Aufrufes sind keine Daten vorhanden. Dieser Fehlercode kann nur dann auftreten, wenn auf das Gerät nicht blockierend zugegriffen wird.

EIO

Bei der Ein-/Ausgabe ist ein Fehler aufgetreten.

EFAULT

Auf den von der Applikation übergebenen Speicherbereich (Buffer) kann vom Treiber aus nicht zugegriffen werden.

Beispiel 4-1. Lesezugriff innerhalb einer Applikation

#include <stdio.h>

int main( int argc, char **argv )
{
    int FileDeskriptor, AnzahlBytesGelesen;
    char buf[128];

    FileDeskriptor = open( "/dev/stdin", O_RDONLY );
    if( FileDeskriptor < 0 ) {
        return -1;
    }
    AnzahlBytesGelesen = read( FileDeskriptor, buf, sizeof(buf) );
    if( AnzahlBytesGelesen <= 0 ) {
        printf("Systemcall read wurde abgebrochen...\n");
        return -2;
    }
    return AnzahlBytesGelesen;
}

int write( int fd, const void *buf, int MaxBytesToWrite). Über den Systemcall write werden Daten an den Treiber und damit an das Gerät übergeben. Die Daten befinden sich im Speicher ab der Adresse »buf«. Es werden maximal »MaxBytesToWrite« Bytes geschrieben.

write gibt die Anzahl der tatsächlich geschriebenen Bytes zurück. Es ist kein Fehler, wenn weniger als »MaxBytesToWrite« Bytes geschrieben werden.

Der Systemcall hat ähnliche Rückgabewerte wie der Systemcall read:

EINTR

Die Bearbeitung des Systemcalls ist durch ein Signal unterbrochen worden. Es wurden keine Daten geschrieben.

EAGAIN

Zum Zeitpunkt des Aufrufes können keine Daten geschrieben werden. Dieser Fehlercode kann nur dann auftreten, wenn auf das Gerät nicht blockierend zugegriffen wird.

EIO

Bei der Ein-/Ausgabe ist ein Fehler aufgetreten.

EFAULT

Auf den von der Applikation übergebenen Speicherbereich kann vom Treiber aus nicht zugegriffen werden.

ENOSPC

Auf dem Gerät steht nicht genügend Speicher zur Verfügung, um die Daten zu schreiben.

Bei einer »sauber« programmierten Applikation wird der Rückgabewert grundsätzlich ausgewertet.

Beispiel 4-2. Schreibzugriff innerhalb einer Applikation

AnzahlBytesGeschrieben = write( fd, buf, BytesToWrite );
if( AnzahlBytesGeschrieben < 0 ) {
    printf("Schreibvorgang nicht erfolgreich\");
    return -1;
}
if( AnzahlBytesGeschrieben != BytesToWrite ) {
    printf("Es konnten nicht alle Daten geschrieben werden ...\n");
    return -2;
}
...

int close( int fd ). Der Systemcall close gibt die systeminternen Ressourcen wieder frei. Dazu wird der Filedeskriptor, der das Gerät (die Treiberinstanz) repräsentiert, geschlossen. Bis die Applikation das Gerät möglicherweise neu geöffnet hat, darf sie nach Aufruf dieses Systemcalls also keine weiteren Lese- oder Schreibzugriffe auf das Gerät durchführen. Der Rückgabewert des Systemcalls ist bei erfolgreichem Abschluss »0«, im Fehlerfall beträgt er »-1«. Für close sind die folgenden Fehlercodes (abgelegt in der Fehlervariablen errno) spezifiziert:

EINTR

Die Bearbeitung des Systemcalls ist durch ein Signal unterbrochen worden.

EIO

Bei der Ein-/Ausgabe ist ein Fehler aufgetreten.

EBADF

Der angegebene Filedeskriptor ist nicht gültig geöffnet.

Da sich mit dem bisher vorgestellten Interface nicht alle Funktionalitäten realisieren lassen, gibt es noch eine weitere Funktion: ioctl.

int ioctl( int fd, int request, ... ). Diese Funktion ist universell einsetzbar. Standardmäßig besitzt ioctl drei Parameter: den Filedeskriptor, ein Kommando und einen Zeiger auf die mit dem Kommando assoziierten Daten. Theoretisch sind auch mehr als drei Parameter erlaubt, was in der Praxis aber nur selten vorkommt. Der Systemcall ist spezifisch für den Zugriff auf Gerätedateien und wird im Kontext von normalen Dateien nicht verwendet.

Die Kommandos, die daran gekoppelten Daten und das Ablaufverhalten (z.B. das Verhalten im Fehlerfall) werden durch den Treiberentwickler festgelegt! So könnte beispielsweise ein Kommando definiert werden, mit dem eine unteilbare write-read-Sequenz ausgeführt wird.

Die Funktion ioctl gibt im Fehlerfall »-1« zurück und die Fehlervariable errno spezifiziert die Fehlerursache:

EINTR

Die Bearbeitung des Systemcalls ist durch ein Signal unterbrochen worden.

EFAULT

Der übergebene Speicherbereich ist nicht gültig.

EBADF

Der angegebene Filedeskriptor ist nicht gültig. Beispielsweise könnte versucht worden sein, den Systemcall ioctl auf eine normale Datei anzuwenden.

Beispiel 4-3. Verwendung von ioctl in einer Applikation

    i=ioctl( fd, IOCTL_GETVALUE, buffer );
    if( i==-1 ) {
        perror( argv[1] );
        return -1;
    }

int lseek( int fd, offs_t offset, int Bezugspunkt ). Bei normalen Dateien und ebenso bei einigen Geräten kann wahlfrei auf Daten zugegriffen werden. Das heißt: Die Applikation kann bestimmen, ab welcher Position innerhalb einer Datei sie Daten lesen oder schreiben möchte. Bei Dateien und bei einigen Geräten ist es damit sogar möglich, bereits gelesene Daten ein zweites Mal zu lesen, indem der Lese- bzw. Schreibzeiger mit lseek wieder auf den Beginn der Datei gesetzt wird. Der Bezugspunkt wird über den Parameter »Bezugspunkt« angegeben. Er lässt sich auf den Beginn der Datei (SEEK_SET), auf das Ende (SEEK_END) oder aber auch unverändert auf die aktuelle Position (SEEK_CUR) setzen. Der Wert des Lese- oder Schreibzeigers wird dann durch Addition von »offset« auf diesen Wert hin berechnet. Um also die letzten 10 Bytes einer Datei einzulesen, wird per »lseek( fd, -10, SEEK_END );« der Lesezeiger auf die entsprechende Position gesetzt und dann per read zugegriffen.

Der Rückgabewert der Funktion lseek ist die aktuelle Position des Lese-/Schreibzeigers (vom Beginn der Datei gerechnet). Ist ein Fehler aufgetreten, wird »-1« zurückgegeben und die Fehlervariable errno wird mit einem Fehlercode belegt.

EINVAL

Der Parameter »Bezugspunkt« enthält einen nicht definierten Wert.

EBADF

Der angegebene Filedeskriptor ist nicht gültig.


Lizenz