wake-up-neo.com

UNIX nicht blockierende E / A: O_NONBLOCK vs. FIONBIO

In jedem Beispiel und jeder Diskussion, die ich im Zusammenhang mit der BSD-Socket-Programmierung antrete, scheint die empfohlene Methode, einen Dateideskriptor auf den nicht blockierenden E/A-Modus zu setzen, die Verwendung des Flags O_NONBLOCK auf fcntl(), z.B.

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 immer den FIONBIO ioctl() -Aufruf verwendet, um dies zu tun:

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

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

Hat jemand einen Kommentar zu den möglichen jeweiligen Vorzügen des einen oder anderen? Ich stelle mir vor, der Portabilitäts-Locus unterscheidet sich etwas, weiß aber nicht, inwieweit ioctl_list(2) diesen Aspekt der einzelnen ioctl -Methoden nicht berücksichtigt.

88
Alex Balashov

Vor der Standardisierung gab es ioctl( ...FIONBIO... ) Und fcntl( ... O_NDELAY ... ), Aber diese haben sich inkonsistent zwischen Systemen und sogar innerhalb desselben Systems verhalten. Zum Beispiel war es üblich, dass FIONBIO an Sockets und O_NDELAY An ttys arbeitete, mit vielen Inkonsistenzen bei Dingen wie Pipes, Fifos und Geräten. 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. Je nach Betriebssystem und Art des Dateideskriptors kann der Lesevorgang 0 oder -1 mit errno EAGAIN oder -1 mit errno EWOULDBLOCK zurückgeben. Auch heute noch führt das Setzen von FIONBIO oder O_NDELAY Unter Solaris dazu, dass ein Lesevorgang ohne Daten 0 für ein tty oder eine Pipe oder -1 für einen Socket mit dem Fehler EAGAIN zurückgibt. 0 ist jedoch nicht eindeutig, da es auch für EOF zurückgegeben wird.

POSIX begegnete diesem Problem mit der Einführung von O_NONBLOCK, Das ein standardisiertes Verhalten für verschiedene Systeme und Dateideskriptortypen 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 die älteren Mechanismen verwendet werden.

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

127
mark4o

Ich glaube fcntl() ist eine POSIX-Funktion. Wobei als ioctl() ein Standard-UNIX-Ding ist. Hier ist eine Liste von POSIX io . ioctl() ist eine sehr kernel-/treiber-/betriebssystemspezifische Sache, aber ich bin mir sicher, was Sie verwenden, funktioniert auf den meisten Unix-Versionen. Einige andere ioctl() -Dateien funktionieren möglicherweise nur auf bestimmten Betriebssystemen oder sogar bestimmten Revisionen des Kernels.

6
EdH

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

5