4.2. Zugriffsmodi

Abbildung 4-1. Blockierender Zugriff

Für den Fall, dass ein Treiber einer Applikation Daten nicht sofort übergeben kann, lassen sich zwei Vorgaben einstellen. Beim blockierenden Lesen wartet die Applikation so lange, bis die Daten zur Verfügung stehen. Unter Linux wird der Rechenprozess dabei entweder in den Zustand TASK_INTERRUPTIBLE (wieder lauffähig, wenn ein Signal unterbricht) oder in den Zustand TASK_UNINTERRUPTIBLE (überhaupt nicht unterbrechbar) versetzt.

Abbildung 4-2. Nicht blockierender Zugriff

Beim nicht blockierenden Lesen gibt der Systemcall read die Kontrolle gleich wieder an die Applikation mit einem Rückgabewert von »-EAGAIN« zurück. In diesem Fall wird die Applikation nicht durch den Treiber in den Wartezustand versetzt.

Für das Schreiben gilt Ähnliches. Kann ein Gerät die Daten zum Zeitpunkt des Aufrufes nicht schreiben, wird beim blockierenden Zugriff die Applikation so lange »schlafen« gelegt, bis die Daten geschrieben werden können oder der Rechenprozess (während er im Systemcall schläft) durch ein Signal unterbrochen wird. Beim nicht blockierenden Schreiben gibt der Treiber die Kontrolle unverzüglich an den Rechenprozess zurück. In diesem Fall hat der Systemcall den Rückgabewert »-EAGAIN«.

Der blockierende Zugriff auf ein Gerät ist der Normalfall. Schließlich kann hierbei die Wartezeit durch den Rechner sinnvoll genutzt werden, indem ein anderer Rechenprozess die zur Verfügung stehende Rechenzeit nutzt.

Die Einstellung des Zugriffsmodus (blockierend oder nicht blockierend) erfolgt entweder beim Öffnen des Gerätes (open) oder ansonsten über die Funktion fcntl.

Beispiel 4-4. Auswahl des Zugriffsmodus in einer Applikation

#include <fcntl.h>                                         (1)
...
    fd = open("/dev/lift", O_RDWR );                       (2)
    fd_nonblocking = open("/dev/robot", O_RDWR | O_NONBLOCK );(3)
...
    FileFlags = fcntl( fd, F_GETFL );                      (4)
    fcntl( fd, F_SETFL, FileFlags | O_NONBLOCK );          (5)
(1)
Diese Header-Datei enthält den Prototyp der Funktion fcntl und wird außerdem für die Definitionen O_RDWR, O_NONBLOCK, F_GETFL und F_SETFL benötigt.
(2)
Wird beim Öffnen eines Gerätes bzw. einer Datei kein Zugriffsmodus-Flag mit angegeben, wird das Gerät grundsätzlich im blockierenden Modus geöffnet.
(3)
Hier wird die Datei im nicht blockierenden Modus geöffnet.
(4)
Damit bei der Änderung des Zugriffsmodus (nächste Codezeile) keine zusätzlichen Flags verändert werden, werden diese zunächst eingelesen.
(5)
In dieser Zeile wird der Zugriffsmodus auf nicht blockierend geändert. Dazu werden die vorher gesetzten Flags mit dem »nicht blockieren«-Flag verodert.

Neben der Möglichkeit, blockierend oder nicht blockierend zuzugreifen, existiert noch eine Funktion, um festzustellen, ob auf einem oder mehreren Filedeskriptoren der nachfolgende Zugriff blockieren würde oder nicht. Dabei wird sogar noch zwischen lesendem und schreibendem Zugriff differenziert. Diese Funktion heißt select. Die Funktion select selbst wird prinzipiell immer im blockierenden Modus aufgerufen; allerdings kann durch Angabe eines Timeout-Wertes das befristete Blockieren (bzw. überhaupt nicht blockieren) eingestellt werden.

Select wird genutzt, um mehrere Ein- bzw. Ausgabekanäle auf Veränderungen hin zu überwachen. Sobald an einem der angegebenen Kanäle Daten zum Lesen bereit liegen bzw. zum Schreiben übergeben werden können, gibt die Funktion der Applikation den bzw. die zugehörigen Dateideskriptoren zurück.

Beispiel 4-5. Beispielcode mit select

#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>

int main( int argc, char **argv )
{
    int fd1, fd2, retval;
    fd_set rfds;                                           (1)
    char buffer[10];

    FD_ZERO(&rfds);                                        (2)
    fd1=open("/dev/lift1", O_RDONLY );
    fd2=open("/dev/lift2", O_RDONLY );
    if( (fd1<0) || (fd2<0) ) {
        perror( "mydevice" );
        exit( -1 );
    }
    FD_SET(fd1, &rfds);                                    (3)
    FD_SET(fd2, &rfds);                                    (4)
    retval = select(fd2+1, &rfds, NULL, NULL, NULL);       (5)
    /* Don't rely on the value of tv now! */
    if( FD_ISSET(fd1,&rfds) ) {                            (6)
        printf("starte lesen fd1 ...\n");
        read( fd1, buffer, sizeof(buffer) );
    }
    if( FD_ISSET(fd2,&rfds) ) {
        printf("starte lesen fd2 ...\n");
        read( fd2, buffer, sizeof(buffer) );
    }
    printf("end\n");
    return 0;
}
(1)
Definition der Variablen »rdfs«, die die zu überwachenden Filedeskriptoren aufnimmt. Hier sollen die Dateien beziehungsweise Geräte nur auf lesenden Zugriff hin überwacht werden.
(2)
Die Variable »rdfs« wird initialisiert.
(3)
Der erste zu überwachende Filedeskriptor wird eingetragen.
(4)
Der zweite zu überwachende Filedeskriptor wird eingetragen.
(5)
Mit diesem Aufruf blockiert die Applikation so lange, bis entweder über Filedeskriptor »fd1« oder über Filedeskriptor »fd2« gelesen werden kann. Da kein Timeout-Wert angegeben ist, kehrt die Funktion auch nicht vorzeitig zurück.
(6)
Mit dem Makro FD_ISSET kann überprüft werden, auf welchem der Filedeskriptoren die Daten gelesen werden können.


Lizenz