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