E / S non bloquantes UNIX: O_NONBLOCK vs FIONBIO

92

Dans chaque exemple et discussion que je rencontre dans le contexte de la programmation de socket BSD, il semble que la manière recommandée de définir un descripteur de fichier en mode E / S non bloquant utilise l' O_NONBLOCKindicateur to fcntl(), par exemple

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

Je fais de la programmation réseau sous UNIX depuis plus de dix ans et j'ai toujours utilisé l' FIONBIO ioctl()appel pour le faire:

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

Je n'ai jamais vraiment réfléchi à pourquoi. Je viens de l'apprendre de cette façon.

Quelqu'un at-il des commentaires sur les mérites respectifs possibles de l'un ou de l'autre? J'imagine que le lieu de la portabilité diffère quelque peu, mais je ne sais pas dans quelle mesure ioctl_list(2)il ne parle pas de cet aspect des ioctlméthodes individuelles .

Alex Balashov
la source

Réponses:

135

Avant la normalisation, il y avait ioctl(... FIONBIO... )et fcntl(... O_NDELAY... ), mais ceux-ci se comportaient de manière incohérente entre les systèmes, et même au sein du même système. Par exemple, il était courant FIONBIOde travailler sur des sockets et O_NDELAYde travailler sur des ttys, avec beaucoup d'incohérences pour des choses comme les tuyaux, les fifos et les périphériques. Et si vous ne saviez pas quel type de descripteur de fichier vous aviez, vous devrez définir les deux pour être sûr. Mais en outre, une lecture non bloquante sans données disponibles était également indiquée de manière incohérente; selon le système d'exploitation et le type de descripteur de fichier, la lecture peut renvoyer 0, ou -1 avec errno EAGAIN, ou -1 avec errno EWOULDBLOCK. Même aujourd'hui, le réglage FIONBIOouO_NDELAYsous Solaris, une lecture sans données renvoie 0 sur un tty ou un tube, ou -1 avec errno EAGAIN sur un socket. Cependant 0 est ambigu car il est également renvoyé pour EOF.

POSIX a résolu ce problème avec l'introduction de O_NONBLOCK, qui a normalisé le comportement de différents systèmes et types de descripteurs de fichiers. Parce que les systèmes existants veulent généralement éviter tout changement de comportement qui pourrait briser la compatibilité descendante, POSIX a défini un nouvel indicateur plutôt que d'imposer un comportement spécifique pour l'un des autres. Certains systèmes comme Linux traitent les trois de la même manière et définissent également EAGAIN et EWOULDBLOCK à la même valeur, mais les systèmes souhaitant conserver un autre comportement hérité pour la compatibilité descendante peuvent le faire lorsque les mécanismes plus anciens sont utilisés.

Les nouveaux programmes devraient utiliser fcntl(... O_NONBLOCK... ), tel que standardisé par POSIX.

mark4o
la source
6
J'ai tendance à utiliser ioctl () pour cela car cela ne me coûte qu'un seul appel système pour activer le mode non bloquant plutôt que deux pour fcntl (). De plus, l'API Windows ioctlsocket () équivaut à ioctl () pour les besoins de cette fonctionnalité.
Wez Furlong
nginx le fait s'il le peut, et le marque avec un commentaire "ioctl (FIONBIO) définit un mode non bloquant avec un seul appel système." Maintenant, il y a accept2 qui vous permet d'accepter une connexion et de la mettre en mode non bloquant dans le même appel système.
Eloff
6

Comme @Sean l'a dit, il fcntl()est largement standardisé et donc disponible sur toutes les plateformes. La ioctl()fonction est antérieure fcntl()à Unix, mais n'est pas du tout standardisée. Le fait que cela ioctl()fonctionne pour vous sur toutes les plates-formes pertinentes pour vous est une chance, mais pas garanti. En particulier, les noms utilisés pour le deuxième argument sont obscurs et non fiables sur toutes les plates-formes. En effet, ils sont souvent propres au pilote de périphérique particulier auquel le descripteur de fichier fait référence. (Les ioctl()appels utilisés pour un périphérique graphique bitmap fonctionnant sur un ICL Perq exécutant PNX (Perq Unix) d'il y a vingt ans ne se sont jamais traduits nulle part ailleurs, par exemple.)

Jonathan Leffler
la source
6

Je crois que fcntl()c'est une fonction POSIX. Où ioctl()est une chose UNIX standard. Voici une liste de POSIX io . ioctl()est une chose très spécifique au noyau / pilote / système d'exploitation, mais je suis sûr que ce que vous utilisez fonctionne sur la plupart des versions d'Unix. certains autres ioctl()éléments peuvent ne fonctionner que sur certains OS ou même sur certains régimes de son noyau.

EdH
la source
J'ai utilisé FIONBIO sur AIX, Solaris, Linux, * BSD et IRIX sans problème. Mais oui, je comprends que cela ne fonctionnera pas sous Windows, par exemple - c'est une interface de bas niveau vers une implémentation de noyau très spécifique. Pourtant, je me demande s'il existe d'autres facteurs de différenciation.
Alex Balashov