UNIX неблокирующий ввод-вывод: O_NONBLOCK против FIONBIO
В каждом примере и обсуждении, которое я просматриваю в контексте программирования сокетов BSD, кажется, что рекомендуемый способ установки дескриптора файла в неблокирующий режим ввода-вывода использует флаг O_NONBLOCK
для fcntl()
, например
int flags = fcntl(fd, F_GETFL, 0);
fcntl(fd, F_SETFL, flags | O_NONBLOCK);
Я занимаюсь сетевым программированием в UNIX уже более десяти лет и всегда использовал вызов FIONBIO ioctl()
для этого:
int opt = 1;
ioctl(fd, FIONBIO, &opt);
Никогда не думал о том, почему. Просто научился этому.
Есть ли у кого-нибудь комментарии относительно возможных соответствующих достоинств того или другого? Я полагаю, что локус переносимости несколько отличается, но не знает, насколько ioctl_list(2)
не говорит об этом аспекте индивидуальных методов ioctl
.
Ответы
Ответ 1
До стандартизации было ioctl(
... FIONBIO
... )
и fcntl(
... O_NDELAY
... )
, но они вели непоследовательно между системами и даже внутри той же системы. Например, для FIONBIO
было принято работать над сокетами и O_NDELAY
для работы с ttys, с большим количеством несогласованности для таких вещей, как трубы, фиксы и устройства. И если вы не знаете, какой файловый дескриптор у вас был, вам придется установить оба, чтобы быть уверенными. Но кроме того, неблокируемое чтение без наличия данных также было указано непоследовательно; в зависимости от ОС и типа дескриптора файла чтение может возвращать 0 или -1 с errno EAGAIN или -1 с errno EWOULDBLOCK. Даже сегодня установка FIONBIO
или O_NDELAY
в Solaris вызывает чтение без каких-либо данных для возврата 0 на tty или pipe или -1 с errno EAGAIN в сокете. Однако 0 является неоднозначным, поскольку он также возвращается для EOF.
POSIX обратился к этому с введением O_NONBLOCK
, который имеет стандартизованное поведение в разных системах и типах дескрипторов файлов. Поскольку существующие системы обычно хотят избежать каких-либо изменений в поведении, которые могут нарушить обратную совместимость, POSIX определил новый флаг, а не мандат определенного поведения для одного из других. Некоторые системы, такие как Linux, обрабатывают все 3 одинаково, а также определяют EAGAIN и EWOULDBLOCK на одно и то же значение, но системы, желающие сохранить некоторые другие унаследованные свойства для обратной совместимости, могут делать это, когда используются более старые механизмы.
Новые программы должны использовать fcntl(
... O_NONBLOCK
... )
, как стандартизован POSIX.
Ответ 2
Я считаю, что fcntl()
- это функция POSIX. Где ioctl()
- стандартная вещь UNIX. Ниже приведен список POSIX io. ioctl()
- это очень специфичная вещь для ядра/драйвера/ОС, но я уверен, что вы используете, работает на большинстве вкусов Unix. некоторые другие вещи ioctl()
могут работать только с определенной ОС или даже с некоторыми оборотами ядра.
Ответ 3
Как сказал @Sean, fcntl()
в значительной степени стандартизирован и, следовательно, доступен на разных платформах. Функция ioctl()
предшествует fcntl()
в Unix, но вообще не стандартизирована. То, что ioctl()
работало для вас на всех платформах, имеющих отношение к вам, удачливее, но не гарантировано. В частности, имена, используемые для второго аргумента, являются тайными и ненадежными между платформами. Действительно, они часто уникальны для конкретного драйвера устройства, с которым ссылается дескриптор файла. (Вызов ioctl()
, используемый для графического устройства с битовым отображением, работающего на ICL Perq, работающем с PNX (Perq Unix) двадцать лет назад, никогда не переводился нигде в другом месте, например.)