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) двадцать лет назад, никогда не переводился нигде в другом месте, например.)