Nicht blockierende UNIX-E / A: O_NONBLOCK vs. FIONBIO

92

In jedem Beispiel und jeder Diskussion, auf die ich im Zusammenhang mit der BSD-Socket-Programmierung stoße, scheint es, dass die empfohlene Methode, einen Dateideskriptor auf den nicht blockierenden E / A-Modus zu setzen, darin besteht, das O_NONBLOCKFlag zu verwenden fcntl(), z

int flags = fcntl(fd, F_GETFL, 0);
fcntl(fd, F_SETFL, flags | O_NONBLOCK);

Ich mache seit über zehn Jahren Netzwerkprogrammierung unter UNIX und habe den FIONBIO ioctl()Aufruf immer dazu verwendet :

int opt = 1;
ioctl(fd, FIONBIO, &opt);

Ich habe nie wirklich darüber nachgedacht, warum. Habe es gerade so gelernt.

Hat jemand einen Kommentar zu den möglichen jeweiligen Vorzügen des einen oder anderen? Ich stelle mir vor, dass sich der Portabilitätsort etwas unterscheidet, weiß aber nicht, inwieweit ioctl_list(2)dieser Aspekt einzelner ioctlMethoden nicht angesprochen wird .

Alex Balashov
quelle

Antworten:

134

Vor der Standardisierung gab es ioctl(... FIONBIO... )und fcntl(... O_NDELAY... ), aber diese verhielten sich inkonsistent zwischen Systemen und sogar innerhalb desselben Systems. Zum Beispiel war es üblich FIONBIO, an Sockets und O_NDELAYan ttys zu arbeiten, mit vielen Inkonsistenzen für Dinge wie Pipes, Fifos und Geräte. Und wenn Sie nicht wüssten, welche Art von Dateideskriptor Sie haben, müssten Sie beide festlegen, um sicherzugehen. Darüber hinaus wurde jedoch auch ein nicht blockierender Lesevorgang ohne verfügbare Daten inkonsistent angezeigt. Abhängig vom Betriebssystem und der Art des Dateideskriptors kann der Lesevorgang 0 oder -1 mit errno EAGAIN oder -1 mit errno EWOULDBLOCK zurückgeben. Noch heute Einstellung FIONBIOoderO_NDELAYUnter Solaris gibt ein Lesevorgang ohne Daten 0 für ein tty oder eine Pipe oder -1 mit errno EAGAIN für einen Socket zurück. 0 ist jedoch nicht eindeutig, da es auch für EOF zurückgegeben wird.

POSIX hat dies mit der Einführung von angegangen O_NONBLOCK, das ein standardisiertes Verhalten über verschiedene Systeme und Dateideskriptortypen hinweg aufweist. Da vorhandene Systeme normalerweise Verhaltensänderungen vermeiden möchten, die die Abwärtskompatibilität beeinträchtigen könnten, hat POSIX ein neues Flag definiert, anstatt ein bestimmtes Verhalten für eines der anderen zu fordern. Einige Systeme wie Linux behandeln alle 3 gleich und definieren EAGAIN und EWOULDBLOCK auf den gleichen Wert. Systeme, die aus Gründen der Abwärtskompatibilität ein anderes Legacy-Verhalten beibehalten möchten, können dies jedoch tun, wenn ältere Mechanismen verwendet werden.

Neue Programme sollten fcntl(... O_NONBLOCK... verwenden ), wie von POSIX standardisiert.

mark4o
quelle
6
Ich neige dazu, ioctl () dafür zu verwenden, da es mich nur einen Systemaufruf kostet, den nicht blockierenden Modus zu aktivieren, anstatt zwei für fcntl (). Darüber hinaus entspricht die Windows-API ioctlsocket () für die Zwecke dieser Funktionalität ioctl ().
Wez Furlong
nginx tut dies, wenn es kann, und markiert es mit einem Kommentar "ioctl (FIONBIO) legt mit dem einzelnen Systemaufruf einen nicht blockierenden Modus fest." Jetzt gibt es accept2, mit dem Sie eine Verbindung akzeptieren und im selben Systemaufruf in den nicht blockierenden Modus versetzen können.
Eloff
6

Wie @Sean sagte, fcntl()ist weitgehend standardisiert und daher plattformübergreifend verfügbar. Die ioctl()Funktion ist älter fcntl()als Unix, aber überhaupt nicht standardisiert. Dass das ioctl()für Sie auf allen für Sie relevanten Plattformen funktioniert hat, ist ein Glücksfall, aber nicht garantiert. Insbesondere sind die für das zweite Argument verwendeten Namen geheimnisvoll und plattformübergreifend nicht zuverlässig. In der Tat sind sie häufig nur für den bestimmten Gerätetreiber gültig, auf den sich der Dateideskriptor bezieht. (Die ioctl()Aufrufe für ein Bitmap-Grafikgerät, das vor zwanzig Jahren auf einem ICL Perq mit PNX (Perq Unix) ausgeführt wurde, wurden beispielsweise nirgendwo anders übersetzt.)

Jonathan Leffler
quelle
6

Ich glaube, es fcntl()ist eine POSIX-Funktion. Wo wie ioctl()ist eine Standard-UNIX-Sache. Hier ist eine Liste von POSIX io . ioctl()ist eine sehr kernel- / treiber- / betriebssystemspezifische Sache, aber ich bin sicher, dass das, was Sie verwenden, auf den meisten Unix-Varianten funktioniert. Einige andere ioctl()Dinge funktionieren möglicherweise nur unter bestimmten Betriebssystemen oder sogar bestimmten Umdrehungen des Kernels.

EdH
quelle
Ich habe FIONBIO unter AIX, Solaris, Linux, * BSD und IRIX ohne Probleme verwendet. Aber ja, ich verstehe, dass es zum Beispiel unter Windows nicht funktionieren wird - es ist eine einfache Schnittstelle zu einer ganz bestimmten Kernel-Implementierung. Trotzdem frage ich mich, ob es noch andere Unterscheidungsmerkmale gibt.
Alex