Книга: Linux программирование в примерах
9.4.3.4. Неблокирующий ввод/вывод для каналов и FIFO
9.4.3.4. Неблокирующий ввод/вывод для каналов и FIFO
Ранее для описания способа работы каналов мы использовали сравнение с двумя людьми, моющими и вытирающими тарелки с использованием сушилки; когда сушилка заполняется, останавливается моющий, а когда она пустеет, останавливается вытирающий. Это блокирующее поведение: производитель или потребитель блокируются в вызове write()
или read()
, ожидая либо освобождения канала, либо появления в нем данных.
В действительности человек, ожидающий опустения или заполнения сушилки, не должен просто неподвижно стоять.[101] Вместо этого незанятый супруг мог бы пойти и найти другую работу по кухне (такую, как подметание всех крошек за детьми на полу), пока сушилка снова не будет готова.
На языке Unix/POSIX эта концепция обозначается термином неблокирующий ввод/вывод, т.е. запрошенный ввод/вывод либо завершается, либо возвращает значение ошибки, указывающее на отсутствие данных (для читающего) или отсутствие места (для записывающего). Неблокирующий ввод/вывод применяется к каналам и FIFO, а не к обычным файлам на диске. Он может применяться также и к определенным устройствам, таким как терминалы, и к сетевым соединениям, обе эти темы выходят за рамки данной книги.
С функцией open()
может использоваться флаг O_NONBLOCK
для указания неблокирующего ввода/вывода, он может быть установлен и сброшен с помощью fcntl()
. Для open()
и read()
неблокирующий ввод/вывод прост.
Открытие FIFO с установленным или сброшенным O_NONBLOCK
демонстрирует следующее поведение:
open("/fifо/file", O_RDONLY, mode)
Блокируется до открытия FIFO для записи.
open("/fifo/file", O_RDONLY | O_NONBLOCK, mode)
Открывает файл, возвращаясь немедленно.
open("/fifo/file", O_WRONLY, mode)
Блокирует до открытия FIFO для чтения.
open("/fifo/file", O_WRONLY | O_NONBLOCK, mode)
Если FIFO был открыт для чтения, открывает FIFO и немедленно возвращается. В противном случае возвращает ошибку (возвращаемое значение -1 и errno
установлен в ENXIO
).
Как описано для обычных каналов, вызов read()
для FIFO, который больше не открыт для чтения, возвращает конец файла (возвращаемое значение 0). Флаг O_NONBLOCK
в данном случае неуместен. Для пустого канала или FIFO (все еще открытых для записи, но не содержащих данных) все становится интереснее:
read(fd, buf, count) и сброшенный O_NONBLOCK
Функция read()
блокируется до тех пор, пока в канал или FIFO не поступят данные.
read(fd, buf, count) и установленный O_NONBLOCK
Функция read()
немедленно возвращает -1 с установленным в errno EAGAIN
.
В заключение, поведение write()
более сложно. Для обсуждения этого нам нужно сначала представить концепцию атомарной записи. Атомарная запись — это такая запись, при которой все данные записываются целиком, не чередуясь с данными от других записей. POSIX определяет в <unistd.h>
константу PIPE_BUF
. Запись в канал или FIFO данных размером менее или равным PIPE_BUF
байтов либо успешно завершается, либо блокируется в соответствии с подробностями, которые мы скоро приведем. Минимальным значением для PIPE_BUF
является _POSIX_PIPE_BUF
, что равняется 512. Само значение PIPE_BUF
может быть больше; современные системы GLIBC определяют ее размер в 4096, но в любом случае следует использовать эту именованную константу и не ожидать, что PIPE_BUF
будет иметь то же значение на разных системах.
Во всех случаях для каналов и FIFO write()
добавляет данные в конец канала. Это происходит от того факта, что у каналов нет файловых смещений: в них нельзя осуществлять поиск.
Также во всех случаях, как упоминалось, записи размером вплоть до PIPE_BUF
являются атомарными: данные не перемежаются с данными от других записей. Данные записи размером более PIPE_BUF
байтов могут перемежаться с данными других записей в произвольных границах. Это последнее означает, что вы не можете ожидать, что каждая порция размером PIPE_BUF
большого набора данных будет записана атомарно. Установка O_NONBLOCK
не влияет на это правило.
Как и в случае с read()
, когда O_NONBLOCK
не установлен, write()
блокируется до тех пор, пока все данные не будут записаны.
Наиболее все усложняется, когда установлен O_NONBLOCK
. Канал или FIFO ведут себя следующим образом:
размер ? nbytes | размер < abytes | |
---|---|---|
nbytes ? PIPE_BUF |
write() успешна |
write() возвращает (-1)/EAGAIN |
размер > 0 | размер = 0 | |
nbytes > PIPE_BUF |
write() записывает, что может |
write() возвращает (-1)/EAGAIN |
Для файлов, не являющихся каналами и FIFO и к которым может быть применен O_NONBLOCK
, поведение следующее:
размер > 0 write()
записывает, что может
размер = 0 write()
возвращает -1/EAGAIN
Хотя есть ряд сбивающих с толку изменений поведения в зависимости от того, канал это или не канал, установлен O_NONBLOCK
или сброшен, есть в канале место для записи или нет, а также в зависимости от размера предполагаемой записи, эти правила предназначены для упрощения программирования:
• Всегда можно отличить конец файла: read()
возвращает 0 байтов.
• Если нет доступных для чтения данных, read()
либо завершается успешно, либо возвращает указание «нет данных для чтения»: EAGAIN
, что означает «попытайтесь снова позже».
• Если для записи нет места, write()
либо блокируется до успешного завершения (O_NONBLOCK
сброшен), либо завершается неудачей с ошибкой «в данный момент нет места для записи»: EAGAIN
.
• Когда место есть, будет записано столько данных, сколько возможно, так что в конечном счете все данные будут переписаны.
Подводя итог, если вы собираетесь использовать неблокирующий ввод/вывод, любой код, который использует write()
, должен быть способен обработать укороченную запись, когда успешно записан меньший объем данных, чем было затребовано. Устойчивый код в любом случае должен быть написан таким способом: даже в случае обычного файла диск может оказаться заполненным и write()
сможет записать лишь часть данных.
Более того, вы должны быть готовы обработать EAGAIN
, понимая, что в этом случае неудача write()
не обязательно означает фатальную ошибку. То же верно для кода, использующего для чтения неблокирующий ввод/вывод: признайте, что и здесь EAGAIN
не является фатальным. (Однако, может стоит подсчитывать число таких отказов, оставив попытки, когда их слишком много.)
Неблокирующий ввод/вывод действительно усложняет вашу жизнь, в этом нет никакого сомнения. Но для многих приложений он является необходимостью, позволяющей выполнить задание. Снова рассмотрите спулер печати. Демон спулера не может позволить себе находиться в блокирующем read()
для файла FIFO, которому представлены входящие задания. Он должен иметь также возможность отслеживать запущенные задания и, возможно, периодически проверять состояние печатающих устройств (например, убедиться, что не заело бумагу).
- 9.4.3.4. Неблокирующий ввод
- 10.1.15. Неблокирующий ввод
- 1.6 Драйверы и буферы ввода-вывода
- 1.8 Ввод-вывод типичного приложения хранения данных
- Глава 6 BIOS – базовая система ввода-вывода
- 5.2.2.2. Устройства ввода информации в персональный компьютер
- Можно ли входить в систему без ввода имени и пароля?
- 13.3.1. Пакетный ввод
- Создание документов и ввод текста
- Ввод и форматирование текста в таблице
- Ввод данных в ячейки
- Вводящие в заблуждение заявления и прочие стратегии использования посланий