EINTR и неблокирующие звонки
Как известно, некоторые блокирующие вызовы, такие как read
а также write
вернет -1 и установит errno
в EINTR
и нам нужно с этим справиться.
Мой вопрос: это относится к неблокирующим вызовам, например, установите сокет в O_NONBLOCK
?
Поскольку в некоторых статьях и источниках, которые я читал, говорилось, что неблокирующим вызовам это не нужно, но я не нашел никакой авторитетной ссылки на это. Если да, то применяется ли это кросс разных реализаций?
3 ответа
Я не могу дать вам однозначный ответ на этот вопрос, и ответ может варьироваться от системы к системе, но я ожидаю, что неблокирующий сокет никогда не выйдет из строя 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
Это, вероятно, не преднамеренно и даже может быть исправлено, если вы сообщите об этом.
@Mecki Отличное объяснение. Чтобы добавить к принятому ответу, книга "Сетевое программирование Unix - Том 1, третье издание" (Стивенс) проводит различие между медленными системными вызовами и другими в главе / разделе 5.9 - "Обработка прерванных системных вызовов". Цитирую из книги -
Мы использовали термин "медленный системный вызов" для описания
accept
, и мы используем этот термин для любого системного вызова, который может заблокировать навсегда. То есть системный вызов никогда не должен возвращаться.
В следующем абзаце того же раздела -
Основное правило, которое применяется здесь, заключается в том, что когда процесс блокируется в медленном системном вызове, и процесс улавливает сигнал, а обработчик сигнала возвращается, системный вызов может вернуть ошибку
EINTR
.
Следуя этому объяснению, read
/ write
на неблокирующем сокете не является медленным системным вызовом и, следовательно, не должен возвращать ошибку EINTR
.
Просто чтобы добавить некоторые доказательства к ответу @Mecki, я нашел это обсуждение об исправлении ошибки в Linux, когда патч заставлял неблокирующий recvmsg возвращать EINTR. Было сказано:
EINTR всегда означает, что вы запросили операцию блокировки, а сигнал тем временем пришел.
Как только вы инвертируете «блокирующую» часть этого набора условий, EINTR становится невозможным событием.
Также:
Посмотрите, что мы делаем для AF_INET. Мы справляемся с этим должным образом.
Если мы «прерваны» сигналом во время сна в lock_sock(),recvmsg() на неблокирующем сокете, мы вернем -EAGAIN правильно, а не -EINTR.
Тот факт, что мы потенциально спим, чтобы получить блокировку сокета, скрыт от пользователя, это деталь реализации ядра.
Мы никогда не возвращаем -EINTR, как указано в man-странице для неблокирующих сокетов.
Источник здесь: https://patchwork.ozlabs.org/project/netdev/patch/1395798147.12610.196.camel@edumazet-glaptop2.roam.corp.google.com / #741015