Подтвердить что ты не робот

EINTR и неблокирующие вызовы

Как известно, некоторые блокирующие вызовы, такие как read и write, возвращают -1 и устанавливают errno в EINTR, и нам нужно справиться с этим.

Мой вопрос: применим ли это к неблокирующим вызовам, например, установить сокет на O_NONBLOCK?

Поскольку некоторые статьи и источники, которые я прочитал, говорят, что неблокирующие вызовы не нуждаются в этом, но я не нашел авторитетной справки об этом. Если да, применяется ли она в разных вариантах реализации?

4b9b3361

Ответ 1

Я не могу дать окончательный ответ на этот вопрос, и ответ может отличаться от системы к системе, но я бы ожидал, что неблокирующий сокет никогда не сработает с EINTR. Если вы посмотрите на страницы руководства различных систем для следующих функций сокетов bind(), connect(), send() и receive() или просмотрите их в стандарте POSIX, вы заметите что-то интересное: Все эти функции, кроме одного, могут возвращать -1 и устанавливать errno на EINTR. Функция EINTR, которая не документируется, когда-либо проваливается, bind(). И bind() также является единственной функцией этого списка, которая никогда не будет блокироваться по умолчанию. Похоже, что только блокирующие функции могут сбой из-за EINTR, включая read() и write(), но если эти функции никогда не блокируются, они также никогда не сбой с EINTR, и если вы используете O_NONBLOCK, эти функции никогда не будет блокироваться.

Это также не имеет смысла с логической точки зрения. Например. подумайте, что вы используете блокирующий ввод-вывод, и вы вызываете read(), и этот вызов должен блокироваться, но когда он блокируется, в ваш процесс отправляется сигнал, и, таким образом, запрос на чтение блокируется. Как система должна справиться с этой ситуацией? Утверждение, что read() преуспели? Это было бы ложью, это не удалось, потому что никаких данных не было прочитано. Утверждение о том, что это удалось, но данные с нулевым байтом были прочитаны? Это также было бы неверным, поскольку для указания конца потока (или конца потока) используется "результат с нулевым чтением", поэтому ваш процесс предполагает, что данные не были прочитаны, поскольку конец файл был достигнут (или сокет/труба была закрыта с другого конца), что просто не так. Конечный файл (или конец потока) не был достигнут, если вы снова вызовете read(), он сможет вернуть больше данных. Так что это тоже ложь. Вы ожидаете, что этот прочитанный вызов либо преуспеет, либо считывает данные, либо сбой происходит с ошибкой. Таким образом, вызов чтения должен завершиться неудачей и вернуть -1 в этом случае, но какое значение errno должно быть установлено системой? Все остальные значения ошибки указывают на критическую ошибку с файловым дескриптором, но критическая ошибка не была и указание такой ошибки также было бы ложью. Поэтому для errno установлено значение EINTR, что означает: "В потоке ничего не случилось. Ваш прочитанный вызов просто не удался, потому что он был прерван сигналом. Если он не был прерван, он все равно мог преуспеть, поэтому, если вам все еще нужны данные, повторите попытку."

Если теперь вы переключаетесь на неблокирующий ввод-вывод, ситуация выше никогда не возникает. Вызов чтения никогда не будет заблокирован, и если он не сможет сразу считывать данные, он будет с ошибкой EAGAIN (POSIX) или EWOULDBLOCK (неофициальный, в Linux обе имеют одинаковую ошибку, только альтернативные имена для него), что означает: "Сейчас нет данных, и поэтому ваш запрос на чтение должен будет блокировать и ждать прибытия данных, но блокировка не разрешена, поэтому вместо этого он не удался". Таким образом, существует ошибка для каждой ситуации, которая может возникнуть.

Конечно, даже при неблокирующем вводе-выводе, вызов чтения может временно прерываться сигналом, но почему система должна указывать это? Каждый вызов функции, будь то системная функция или одна, написанная пользователем, может быть временно прервана сигналом, на самом деле каждый, без исключения. Если система должна будет информировать пользователя, когда это произойдет, все системные функции могут выйти из строя из-за EINTR. Однако даже в случае прерывания сигнала функции обычно выполняют свою задачу до конца, поэтому это прерывание не имеет значения. Ошибка EINTR используется, чтобы сообщить вызывающему, что запрошенное им действие не было выполнено из-за прерывания сигнала, но в случае неблокирующего ввода-вывода нет причин, по которым функция не должна выполнять чтение или запрос на запись, если он не может быть выполнен прямо сейчас, но тогда это может быть указано соответствующей ошибкой.

Чтобы подтвердить мою теорию, я взглянул на ядро ​​MacOS (10.8), которое по-прежнему в значительной степени основано на ядре FreeBSD, и, похоже, подтверждает подозрение. Если вызов чтения в настоящее время невозможен, поскольку данные отсутствуют, ядро ​​проверяет флаг O_NONBLOCK в флагах дескриптора файла. Если этот флаг установлен, он немедленно сбрасывается с помощью EAGAIN. Если он не установлен, он помещает текущий поток в режим сна, вызывая функцию с именем msleep(). Функция задокументирована здесь(как я уже сказал, OS X использует большое количество кода FreeBSD в своем ядре). Эта функция заставляет текущий поток спать до тех пор, пока он явно не проснется (что в случае, если данные становятся готовыми к чтению) или был поражен тайм-аут (например, вы можете установить тайм-аут приема на сокетах). Тем не менее поток также разбуждается, если сигнал доставлен, и в этом случае msleep() сам возвращает EINTR, а следующий более высокий уровень просто пропускает эту ошибку. Таким образом, msleep() создает ошибку EINTR, но если установлен флаг O_NONBLOCK, msleep() никогда не вызывается в первую очередь, поэтому эта ошибка не может быть возвращена.

Конечно, это MacOS/FreeBSD, другие системы могут быть разными, но поскольку большинство систем пытаются поддерживать хотя бы определенный уровень согласованности между этими API, если система нарушает предположение, что неблокирующие вызовы ввода/вывода никогда не может потерпеть неудачу из-за EINTR, это, вероятно, не по намерению и даже может быть исправлено, если ваш отчет.