Der Systemaufruf select
Transcription
Der Systemaufruf select
Der Systemaufruf select Der Systemaufruf select ist ein Multiplex-Call, d. h. er dient dazu, auf eingehende Daten von mehreren Datenströmen zu warten und zu reagieren, sobald von irgendwo Daten ankommen. Schon in so einem auf den ersten Blick einfachen Programm wie telnet wird er unbedingt benötigt. telnet stellt eine Verbindung zwischen dem Terminal des Anwenders, das eine Sitzung auf einem Rechner geöffnet hat, und einer Sitzung auf einem weiteren Rechner her – der Remote Session. Nun fließen Daten von der Tastatur zur Sitzung, und es fließen Daten vom Rechner zurück zum Bildschirm. Das telnet-Programm muß in beiden Fällen reagieren und die Daten übertragen. Wenn man versucht, von einem geöffneten File Deskriptor zu lesen, auf dem keine Daten mehr anliegen – beispielsweise bei einer leeren Pipe –, so blockiert der Lese-Aufruf read. Würde man also von der Tastatur lesen wollen, so wäre das Programm blockiert, bis etwas eingegeben wird und in der Zeit vom Rechner ankommende Daten könnten nicht auf den Bildschirm weitergeleitet werden. Umgekehrt könnte das Programm blockiert werden, wenn es auf Daten vom Rechner wartet und der Benutzer versucht, Daten einzutippen. Beides ist nicht akzeptabel. Nun kann man beim read-Systemaufruf angeben, daß er nicht blockieren, sondern im Fall, daß keine Daten vorhanden sind, mit einem Fehler zurückkehren soll. Bei mehreren File Deskriptoren müßte das Nachschauen, ob Daten vorhanden sind, dann in einer Schleife geschehen, wo abwechselnd auf den Dateien geprüft wird, ob Daten anliegen. Damit würde auf dem Rechner, auf dem telnet läuft, sehr viel Rechenzeit auf dieses Polling verschwendet. Aus dieser Klemme hilft der Systemaufruf select. Hinweis: Bei gewöhnlichen Dateien ist das Arbeiten mit select sinnlos, sinnvoll ist es nur bei Gerätedateien, Pipes und ähnlichen Datenströmen, z. B. auch Verbindungen über ein Netz (sockets). Gewöhnliche Dateien sind immer „bereit“ und liefern immer eine 1 in der Maske zurück. Aufgabe von select select wartet eine vorbestimmte Zeit, ob an min- destens einem aus einer ganzen Reihe von File Deskriptoren Daten zur Verfügung stehen. Solange keine Daten vorliegen, bleibt select blockiert und verbraucht ebensowenig CPU-Zeit wie ein blokkierter read-Call. Nach Ablauf der vorgegebenen Zeit – dem timeout – kehrt der select-Call in jedem Fall zurück, meldet dann aber, daß kein File Deskriptor Daten hat. Der Wartezeitraum kann bis zu 31 Tagen betragen oder aber auf „unendlich“ eingestellt werden. An diesem Wert sieht man schon die enge Verwandtschaft mit dem System Call alarm, dessen Prototyp sich ebenfalls in time.h (bei HP-UX und LINUX, im Falle (b.i.b. GL Ja) SELECT.DOC 13.06.97 UNIXWARE gibt es select.h) befindet. Allerdings ist alarm POSIX-Standard, select dagegen eine Berkeley-Besonderheit. Der Prototyp von select zeigt schon, daß es sich bei diesem Systemaufruf um keinen gewöhnlichen, sondern einen relativ komplexen handelt, der sich auch von Unix zu Unix unterscheidet: Der Prototyp int select (int AnzahlFDs, fd_set *LeseFDs, fd_set *SchreibFDs, fd_set *AusnahmeFDs, struct timeval *Timeout); Man gibt über die Masken vom Typ fd_set an, welche File Deskriptoren überwacht werden sollen bezüglich eingehender Daten (LeseFDs), der Möglichkeit, Daten zu schreiben (SchreibFDs) und Ausnahmen (AusnahmeFDs). Hierzu ist ein Datentyp fd_set deklariert worden. Früher befand sich diese Datentyp in <sys/types.h>, wo er aus Kompatibilitätsgründen auch noch vorhanden ist; bei UNIXWARE und LINUX ist er immer noch lediglich dort. typedef struct fd_set { fd_mask fds_bits[howmany(FD_SETSIZE, NFDBITS)]; } fd_set; Dieser vordeklarierte Typ ist allerdings dafür ausgelegt, bis zu 2048 File Deskriptoren enthalten zu können. Möchte man ihn auf die praxisrelevanten 32 Bits „zurechtstutzen“, deklariert man vor dem Includen von <sys/time.h> bzw. <sys/types.h> folgendes: #define FD_SETSIZE 32 Als Besonderheit bei Linux gilt noch, daß der timeout-Wert verändert wird; er enthält die „Restzeit“ bis zum Timeout. Bei UNIXWARE kann der Wert in zukünftigen Versionen mal verändert werden, bei HP-UX kann er verändert werden. Anwendung von select Man öffnet eine Reihe von File Deskriptoren, die man mit select überwachen möchte. Mit Hilfe des Makros FD_SET nimmt man alle gewünschten File Deskriptoren in die „Menge“, d. h. in die Masken vom Typ fd_set (bzw. int, wenn man max. 32 Stück überwachen möchte) auf, die man vorher ordnungsgemäß mit FD_ZERO initialisiert hat. In der jeweiligen Maske wird jeder mit FD_SET eingetragene File Deskriptor als ein 1-Bit dargestellt, alle anderen Bits bleiben 0. Als ersten Parameter an select übergibt man die höchste vorkommende File-Deskriptor-Nummer + 1 oder einfach 32 für die maximal vorkommende Anzahl in unseren Fällen, da ja alle höheren Bits, die mit nicht geöffneten Deskriptoren korrespondieren, in den drei fd_sets sowieso 0 sind. Seite 1 von 3 Der Systemaufruf select Man braucht nicht alle drei Masken zu füllen, sondern nur die, die man tatsächlich benötigt. Für die anderen kann man NULL-Pointer angeben. Meist will man ja nur die ankommenden Daten feststellen, d. h. man benötigt nur readfds. Der Rückgabewert von select ist die Anzahl der „bereiten“ File Deskriptoren bzw. -1 im Fehlerfall. Wenn kein File Deskriptor „bereit“ wird und die Zeit abläuft, ist der Rückgabewert 0. Die File Deskriptor Masken werden bei jedem Aufruf von select verändert. Sie enthalten 1Bits für alle „bereiten“ File Deskriptoren und 0Bits für alle nicht „bereiten“ Deskriptoren. Bei einem Timeout sind alle Bits aller Masken 0. Nach Rückkehr aus select überprüft man mit Hilfe des Makros FD_ISSET, welche Bits nun gesetzt, d. h. welche Deskriptoren „bereit“ sind. Von genau diesen Deskriptoren kann man nun lesen (bzw. auf sie schreiben), ohne Gefahr zu laufen, daß der Leseaufruf blockiert. Anwendungsbeispiel Mit select kann man beispielsweise die in zwei benannten Pipes ankommenden Daten lesen und auf dem Bildschirm anzeigen. Hierzu öffnet man die beiden FIFOs und fragt select, ob Daten anliegen. Wenn ja, liest man sie und fragt select erneut. Gleiches könnte man auch mit Terminals oder Netz-Verbindungen (sockets) tun. Von den bereiten File Deskriptoren wird gelesen, und die Daten werden angezeigt. /* Beispielprogramm zu select: ~dozja/FORALL/Sysprog/selectbsp.c */ /* * Liest aus den beiden benannten Pipes FIFO0 und FIFO1, die sich im * aktuellen Verzeichnis befinden, und zeigt die gelesenen Daten an. * * In die FIFOs laesst man von anderen Prozessen etwas hineinschreiben * oder schreibt von anderen Sitzungen aus etwas "zu Fuss" hinein. * Die FIFOs bleiben immer geoeffnet, auch wenn die Schreiber spaeter * wieder schliessen und neue Schreiber oeffnen. So kann man also einen * Server erzeugen, der beliebig auf Schreiber wartet und auf sie * reagiert. * * Beide FIFOs werden am Anfang mit O_RDWR geoeffnet, weil bei einem * Oeffnen mit O_RDONLY (weil wir ja eigentlich nur lesen) das open() * blockieren wuerde, solange kein Schreiber geoeffnet hat. Das Blok* kieren koennte man auch mit dem Zusatz O_NDELAY verhindert, dann * wuerde aber das select() immer sofort zurueckkehren und alle File * Descriptors als "bereit" melden, ohne dass Daten vorliegen. Dieser * Zustand kann dann nicht mehr von EOF unterschieden werden. * */ #define #define #define #define _INCLUDE_POSIX_SOURCE _INCLUDE_HPUX_SOURCE _INCLUDE_XOPEN_SOURCE _INCLUDE_XOPEN_SOURCE_EXTENDED #define FD_SETSIZE 32 #define MAX 50 /* #include <sys/types.h> #include <fcntl.h> #include <sys/time.h> #include <unistd.h> #include <stdio.h> int int char bei HP-UX ab Version 10 entbehrlich */ fd [2]; fdmask [2]; fn0[] = "FIFO0", fn1[] = "FIFO1"; (b.i.b. GL Ja) SELECT.DOC 13.06.97 Seite 2 von 3 Der Systemaufruf select int main (void) { int anz; char buf [50]; char zk [MAX]; fd_set readmask, mask; int nfound, i; struct timeval timeout_value; char ch; FD_ZERO (&readmask); printf fd [0] if (fd FD_SET printf ("Oeffne ...\n"); = open (fn0, O_RDWR); /* kehrt sofort zurueck, auch ohne Leser */ [0] == -1) perror ("open fd[0]"), exit (-1); (fd [0], &readmask); ("fd [0] = %d\n", fd [0]); fd [1] if (fd FD_SET printf = open (fn1, O_RDWR); /* kehrt sofort zurueck, auch ohne Leser */ [1] == -1) perror ("open fd[1]"), exit (-1); (fd [1], &readmask); ("fd [1] = %d\n", fd [1]); for (i=15; i>=0; i--) { if (FD_ISSET (i, &readmask)) printf ("1"); else printf ("0"); } printf ("\n"); while (1) { timeout_value.tv_sec = 500; /* seconds */ timeout_value.tv_usec = 0; /* microseconds */ mask = readmask; if ((nfound = select (5, &mask, NULL, NULL, &timeout_value)) == -1) perror ("select failed"); else if (nfound == 0) printf ("select timed out\n"); else { printf ("\nnfound = %d\n", nfound); for (i=15; i>=0; i--) { if (FD_ISSET (i, &mask)) printf ("1"); else printf ("0"); } printf ("\n"); for (i=0; i<2; i++) { if (FD_ISSET (fd [i], &mask)) { printf ("FIFO %d has data: ", i); anz = read (fd [i], buf, sizeof (buf)); printf ("%d bytes read.\n", anz); fflush (stdout); write (1, buf, anz); write (1, "\n", 1); } else printf ("FIFO %d has none!\n", i), fflush (stdout); } } } } (b.i.b. GL Ja) SELECT.DOC 13.06.97 Seite 3 von 3