E / S sin bloqueo de UNIX: O_NONBLOCK frente a FIONBIO

92

En cada ejemplo y discusión con el que me encuentro en el contexto de la programación de sockets BSD, parece que la forma recomendada de configurar un descriptor de archivo en el modo de E / S sin bloqueo es usar la O_NONBLOCKbandera para fcntl(), por ejemplo,

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

He estado haciendo programación de red en UNIX durante más de diez años y siempre he usado la FIONBIO ioctl()llamada para hacer esto:

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

Realmente nunca pensé mucho en por qué. Lo acabo de aprender de esa manera.

¿Alguien tiene algún comentario sobre los posibles méritos respectivos de uno u otro? Me imagino que el lugar de la portabilidad difiere un poco, pero no sé en qué medida ioctl_list(2)no habla de ese aspecto de los ioctlmétodos individuales .

Alex Balashov
fuente

Respuestas:

135

Antes de la estandarización había ioctl(... FIONBIO... )y fcntl(... O_NDELAY... ), pero estos se comportaban de manera inconsistente entre sistemas, e incluso dentro del mismo sistema. Por ejemplo, era común FIONBIOtrabajar en sockets y O_NDELAYtrabajar en ttys, con mucha inconsistencia para cosas como tuberías, FIFos y dispositivos. Y si no sabía qué tipo de descriptor de archivo tenía, tendría que configurar ambos para estar seguro. Pero además, una lectura sin bloqueo sin datos disponibles también se indicó de manera inconsistente; según el sistema operativo y el tipo de descriptor de archivo, la lectura puede devolver 0, o -1 con errno EAGAIN, o -1 con errno EWOULDBLOCK. Incluso hoy, el establecimiento FIONBIOoO_NDELAYen Solaris hace que una lectura sin datos devuelva 0 en un tty o pipe, o -1 con errno EAGAIN en un socket. Sin embargo, 0 es ambiguo ya que también se devuelve para EOF.

POSIX abordó esto con la introducción de O_NONBLOCK, que tiene un comportamiento estandarizado en diferentes sistemas y tipos de descriptores de archivos. Debido a que los sistemas existentes generalmente quieren evitar cualquier cambio en el comportamiento que pueda romper la compatibilidad con versiones anteriores, POSIX definió un nuevo indicador en lugar de imponer un comportamiento específico para uno de los otros. Algunos sistemas como Linux tratan a los 3 por igual y también definen EAGAIN y EWOULDBLOCK con el mismo valor, pero los sistemas que deseen mantener algún otro comportamiento heredado para la compatibilidad con versiones anteriores pueden hacerlo cuando se utilizan los mecanismos más antiguos.

Los nuevos programas deben usar fcntl(... O_NONBLOCK... ), como estandarizado por POSIX.

mark4o
fuente
6
Tiendo a usar ioctl () para esto porque me cuesta solo una llamada al sistema para habilitar el modo sin bloqueo en lugar de dos para fcntl (). Además, la API ioctlsocket () de Windows es equivalente a ioctl () a los efectos de esta funcionalidad.
Wez Furlong
nginx hace esto si puede, y lo marca con un comentario "ioctl (FIONBIO) establece un modo sin bloqueo con la única llamada al sistema". Ahora hay accept2 que le permite aceptar una conexión y ponerla en modo sin bloqueo en la misma llamada al sistema.
Eloff
6

Como dijo @Sean, fcntl()está ampliamente estandarizado y, por lo tanto, está disponible en todas las plataformas. La ioctl()función es anterior fcntl()a Unix, pero no está estandarizada en absoluto. Que haya ioctl()funcionado para usted en todas las plataformas relevantes para usted es una suerte, pero no está garantizado. En particular, los nombres utilizados para el segundo argumento son arcanos y no confiables en todas las plataformas. De hecho, a menudo son exclusivos del controlador de dispositivo particular al que hace referencia el descriptor de archivo. (Las ioctl()llamadas utilizadas para un dispositivo de gráficos de mapa de bits que se ejecuta en un ICL Perq con PNX (Perq Unix) de hace veinte años nunca se tradujeron a ningún otro lugar, por ejemplo).

Jonathan Leffler
fuente
6

Creo que fcntl()es una función POSIX. Donde ioctl()es una cosa estándar de UNIX. Aquí hay una lista de POSIX io . ioctl()es algo muy específico del kernel / controlador / sistema operativo, pero estoy seguro de que lo que usa funciona en la mayoría de versiones de Unix. algunas otras ioctl()cosas solo pueden funcionar en ciertos sistemas operativos o incluso en ciertas revoluciones de su kernel.

EdH
fuente
He usado FIONBIO en AIX, Solaris, Linux, * BSD e IRIX sin problemas. Pero sí, entiendo que no funcionará en Windows, por ejemplo, es una interfaz de bajo nivel para una implementación de kernel muy específica. Aún así, me pregunto si hay otros factores diferenciadores.
Alex Balashov