E / S não bloqueante do UNIX: O_NONBLOCK vs. FIONBIO
92
Em todos os exemplos e discussões que encontro no contexto da programação de socket BSD, parece que a maneira recomendada de definir um descritor de arquivo para o modo de E / S não bloqueante é usar o O_NONBLOCKsinalizador para fcntl(), por exemplo
int flags = fcntl(fd, F_GETFL,0);
fcntl(fd, F_SETFL, flags | O_NONBLOCK);
Tenho feito programação de rede no UNIX há mais de dez anos e sempre usei a FIONBIO ioctl()chamada para fazer isso:
int opt =1;
ioctl(fd, FIONBIO,&opt);
Nunca pensei muito sobre o porquê. Aprendi assim.
Alguém tem algum comentário sobre os possíveis méritos respectivos de um ou de outro? Imagino que o locus da portabilidade seja um pouco diferente, mas não sei até que ponto, pois ioctl_list(2)não fala a esse aspecto dos ioctlmétodos individuais .
Antes da padronização havia ioctl(... FIONBIO... )e fcntl(... O_NDELAY... ), mas esses se comportavam de maneira inconsistente entre os sistemas e até mesmo dentro do mesmo sistema. Por exemplo, era comum FIONBIOtrabalhar em soquetes e O_NDELAYtrabalhar em ttys, com muita inconsistência para coisas como tubos, fifos e dispositivos. E se você não sabia que tipo de descritor de arquivo você tinha, você teria que definir ambos para ter certeza. Mas, além disso, uma leitura sem bloqueio e sem dados disponíveis também foi indicada de forma inconsistente; dependendo do sistema operacional e do tipo de descritor de arquivo, a leitura pode retornar 0, ou -1 com errno EAGAIN, ou -1 com errno EWOULDBLOCK. Ainda hoje, definindo FIONBIOouO_NDELAYno Solaris faz com que uma leitura sem dados retorne 0 em um tty ou pipe, ou -1 com errno EAGAIN em um soquete. No entanto, 0 é ambíguo, pois também é retornado para EOF.
POSIX abordou isso com a introdução de O_NONBLOCK, que padronizou o comportamento em diferentes sistemas e tipos de descritores de arquivo. Como os sistemas existentes geralmente desejam evitar quaisquer mudanças no comportamento que possam quebrar a compatibilidade com versões anteriores, POSIX definiu um novo sinalizador em vez de obrigar um comportamento específico para um dos outros. Alguns sistemas como Linux tratam todos os 3 da mesma maneira e também definem EAGAIN e EWOULDBLOCK com o mesmo valor, mas sistemas que desejam manter algum outro comportamento legado para compatibilidade com versões anteriores podem fazê-lo quando os mecanismos mais antigos são usados.
Novos programas devem usar fcntl(... O_NONBLOCK... ), conforme padronizado por POSIX.
Eu tendo a usar ioctl () para isso porque me custa apenas uma syscall para habilitar o modo sem bloqueio em vez de duas para fcntl (). Além disso, a API ioctlsocket () do Windows é equivalente a ioctl () para os fins desta funcionalidade.
Wez Furlong
nginx faz isso se puder e marca com um comentário "ioctl (FIONBIO) define um modo sem bloqueio com o único syscall." Agora há o accept2 que permite aceitar uma conexão e colocá-la em modo sem bloqueio na mesma syscall.
Eloff
6
Como @Sean disse, fcntl()é amplamente padronizado e, portanto, disponível em várias plataformas. A ioctl()função é anterior fcntl()ao Unix, mas não é padronizada. O fato de ter ioctl()trabalhado para você em todas as plataformas relevantes para você é uma sorte, mas não é garantido. Em particular, os nomes usados para o segundo argumento são misteriosos e não confiáveis entre plataformas. Na verdade, eles geralmente são exclusivos do driver de dispositivo específico ao qual o descritor de arquivo faz referência. (As ioctl()chamadas usadas para um dispositivo gráfico mapeado em bits rodando em um ICL Perq rodando PNX (Perq Unix) de vinte anos atrás nunca foram traduzidas para qualquer outro lugar, por exemplo.)
Acredito que fcntl()seja uma função POSIX. Onde ioctl()é uma coisa padrão do UNIX. Aqui está uma lista de POSIX io . ioctl()é uma coisa muito específica do kernel / driver / sistema operacional, mas tenho certeza de que o que você usa funciona na maioria das versões do Unix. algumas outras ioctl()coisas podem funcionar apenas em certos sistemas operacionais ou mesmo certas rotações de seu kernel.
Usei FIONBIO em AIX, Solaris, Linux, * BSD e IRIX sem problemas. Mas sim, eu entendo que não vai funcionar no Windows, por exemplo - é uma interface de baixo nível para uma implementação de kernel muito específica. Ainda assim, me pergunto se existem outros fatores de diferenciação.
Como @Sean disse,
fcntl()
é amplamente padronizado e, portanto, disponível em várias plataformas. Aioctl()
função é anteriorfcntl()
ao Unix, mas não é padronizada. O fato de terioctl()
trabalhado para você em todas as plataformas relevantes para você é uma sorte, mas não é garantido. Em particular, os nomes usados para o segundo argumento são misteriosos e não confiáveis entre plataformas. Na verdade, eles geralmente são exclusivos do driver de dispositivo específico ao qual o descritor de arquivo faz referência. (Asioctl()
chamadas usadas para um dispositivo gráfico mapeado em bits rodando em um ICL Perq rodando PNX (Perq Unix) de vinte anos atrás nunca foram traduzidas para qualquer outro lugar, por exemplo.)fonte
Acredito que
fcntl()
seja uma função POSIX. Ondeioctl()
é uma coisa padrão do UNIX. Aqui está uma lista de POSIX io .ioctl()
é uma coisa muito específica do kernel / driver / sistema operacional, mas tenho certeza de que o que você usa funciona na maioria das versões do Unix. algumas outrasioctl()
coisas podem funcionar apenas em certos sistemas operacionais ou mesmo certas rotações de seu kernel.fonte