Для этих целей послужит вызов fcntl(), который включает или выключает флаг состояния открытого файла O_NONBLOCK. Чтобы включить данный флаг, можно воспользоваться следующим кодом (опуская проверку ошибок):
int flags;
flags = fcntl(fd, F_GETFL); /* Получаем флаги состояния открытых файлов */
flags |= O_NONBLOCK; /* Включаем бит O_NONBLOCK */
fcntl(fd, F_SETFL, flags); /* Обновляем флаги состояния открытых файлов */
А для его выключения достаточно следующих строк:
flags = fcntl(fd, F_GETFL);
flags &= ~O_NONBLOCK; /* Выключаем бит O_NONBLOCK */
fcntl(fd, F_SETFL, flags);
В табл. 44.2 описана семантика операции read() для каналов и очередей FIFO, в том числе и с учетом действия флага O_NONBLOCK.
Единственное различие между блокирующим и неблокирующим чтением проявляется, когда при отсутствии данных открывается записывающий конец. В этом случае обычный вызов read() блокируется, а его неблокирующая версия завершается ошибкой EAGAIN.
Таблица 44.2. Семантика чтения n байт из канала или очереди FIFO, содержащих p байт
O_NONBLOCK включен?
Данные, доступные в канале или очереди FIFO (p)
p = 0, записывающий конец открыт
p = 0, записывающий конец закрыт
p < n
p >= n
Нет
Блокируется
Возвращает 0 (EOF)
Считывает p байт
Считывает n байт
Да
Возникает ошибка (EAGAIN)
Возвращает 0 (EOF)
Считывает p байт
Считывает n байт
Влияние флага O_NONBLOCK на запись в канал или очередь FIFO оказывается более сложным из-за наличия ограничения PIPE_BUF. Поведение операции write() описано в табл. 44.3.
Таблица 44.3. Семантика записи n байт в канал или очередь FIFO
O_NONBLOCK включен?
Считывающий конец открыт
Считывающий конец закрыт
n <= PIPE_BUF
n > PIPE_BUF
Нет
Автоматически записывает n байт; может блокироваться, пока не будет прочитано достаточно данных, чтобы можно было выполнить write()
Записывает n байт; может блокироваться, пока не будет прочитано достаточно данных для завершения write(); данные разных записывающих процессов могут перемешаться
SIGPIPE + EPIPE
Да
Если для немедленной записи n байт достаточно места, write() завершается успешно автоматически; в противном случае возвращается ошибка EAGAIN
Если у нас хватает места для немедленной записи некоего количества данных, записывается от 1 до n байт (которые могут перемешаться с данными, записываемыми другими процессами); в противном случае write() завершается ошибкой EAGAIN
Флаг O_NONBLOCK приводит к сбою записи в канал или очередь FIFO (с ошибкой EAGAIN) в любой ситуации, в которой немедленная передача данных невозможна. Это значит следующее: при записи PIPE_BUF байт вызов write() завершится неудачей, если в канале или очереди FIFO окажется недостаточно места; такая ситуация связана с тем, что ядро не может немедленно завершить операцию и в то же время не может выполнить частичную запись, поскольку это бы нарушило требование к атомарности записи сообщений, не превышающих PIPE_BUF байт.
Если за один раз записывается больше PIPE_BUF байт, то процедура записи не обязательно должна быть атомарной. В связи с этим вызов write() передает как можно больше данных (частичная запись), чтобы заполнить канал или очередь FIFO. В таком случае write() возвращает количество переданных байтов, а вызывающий процесс должен повторить попытку записи позже — для передачи оставшихся данных. Но если канал или очередь FIFO оказываются заполненными и не могут принять ни одного байта, то вызов write() завершается ошибкой EAGAIN.
Каналы стали первым средством межпроцессного взаимодействия в UNIX-системах. Они часто используются в командной оболочке и других приложениях. Канал является однонаправленным потоком байтов с ограниченной пропускной способностью, который можно применять для организации взаимодействия родственных процессов. В него можно записать блок данных любого размера, но только блоки, не превышающие PIPE_BUF байт, являются гарантировано атомарными. Каналы применяются не только для IPC, но и как метод синхронизации процессов.
Задействуя каналы, следует проявлять осмотрительность при закрытии неиспользуемых дескрипторов, чтобы считывающий процесс смог обнаружить символ конца файла, а записывающий процесс — получить сигнал SIGPIPE или ошибку EPIPE (обычно проще всего сделать так, чтобы записывающий процесс игнорировал сигнал SIGPIPE и обнаруживал «сбой» канала с помощью ошибки EPIPE).