

12. Каналы в UNIX
Система UNIX обеспечивает конструкцию, которая называется программными каналами (pipe). Программный канал (или просто канал) служит для установления односторонний связи, соединяющей один процесс с другим, и является еще одним видом обобщенного ввода/вывода системы UNIX. Процесс может посылать данные в канал при помощи системного вызова write, а другой процесс может принимать данные из канала при помощи системного вызова read.
Каналы создаются в программе при помощи системного вызова pipe. В случае удачного завершения вызов сообщает два дескриптора файла: один для записи в канал, а другой для чтения из него.
int pipe(int filedes[2]);
Переменная filedes является массивом из двух целых чисел, который будет содержать дескрипторы файлов, обозначающие канал. После успешного вызова filedes[0] будет открыт для чтения из канала, a filedes[1] для записи в канал.В случае неудачи вызов pipe вернет значение -1.
После создания канала с ним можно работать просто при помощи вызовов read и write.
Сообщения считываются в том же порядке, в каком они были записаны. Каналы обращаются с данными в порядке «первый вошел - первым вышел» (first-in first-out, или сокращенно FIFO). lseek не работает с каналами.
Размеры блоков при записи в канал и чтении из него необязательно должны быть одинаковыми.
Настоящее значение каналов проявляется при использовании вместе с системным вызовом fork, тогда можно воспользоваться тем фактом, что файловые дескрипторы остаются открытыми в обоих процессах.
В таком случае канал соединяет два процесса. И в родительском, и в дочернем процессах открыто по два дескриптора файла, позволяя выполнять запись в канал и чтение из него
Важно заметить, что размер буфера канала конечен. Только определенное число байтов может находиться в канале, прежде чем следующий вызов write будет заблокирован. При программировании важно знать максимальный размер канала для системы, так как он влияет на оба вызова write и read. Если вызов write выполняется для канала, в котором есть свободное место, то данные посылаются в канал, и немедленно происходит возврат из вызова. Если же в момент вызова write происходит переполнение канала, то выполнение процесса обычно приостанавливается до тех пор, пока место не освободится в результате выполнения другим процессом чтения из канала.
Обычно вызов write для канала выполняется неделимыми порциями (atomically), и данные передаются ядром за одну непрерываемую операцию.
Взаимодействие вызова read с каналами является более простым. При выполнении вызова read система проверяет, является ли канал пустым. Если он пуст, то вызов read будет заблокирован до тех пор, пока другой процесс не запишет в канал какие-либо данные. При наличии в канале данных произойдет возврат из вызова read, даже если запрашивается больший объем данных, чем находится в канале.
Для простых приложений применение неблокирующих операций чтения и записи работает прекрасно. Для работы с множеством каналов одновременно существует другое решение, которое заключается в использовании системного вызова select.
Родительский процесс может выступить в качестве серверного процесса и может иметь произвольное число связанных с ним клиентских (дочерних) процессов. В этом случае серверный процесс должен как-то справляться с ситуацией, когда одновременно в нескольких каналах может находиться информация, ожидающая обработки. Кроме того, если ни в одном из каналов нет ожидающих данных, то может иметь смысл приостановить работу серверного процесса до их появления, а не опрашивать постоянно каналы. Если информация поступает более чем по одному каналу, то серверный процесс должен знать обо всех таких каналах для того, чтобы работать с ними в правильном порядке (например, согласно их приоритетам).
Это можно сделать при помощи системного вызова select. Системный вызов select используется не только для каналов, но и для обычных файлов, именованных каналов и сокетов. Системный вызов select показывает, какие дескрипторы файлов из заданных наборов готовы для чтения, записи или ожидают обработки ошибок. Иногда серверный процесс не должен совсем прекращать работу, даже если не происходит никаких событий, поэтому в вызове select также можно задать предельное время ожидания.
Вызов select будет заблокирован до тех пор, пока не произойдет интересующее процесс событие. Если в структуре timeout задано нулевое время, то вызов select завершится немедленно (без блокирования). Если структура timeout содержит ненулевое значение, то возврат из вызова select произойдет через заданное число секунд или микросекунд, если файловые дескрипторы неактивны.
Возвращаемое вызовом select значение равно -1 в случае ошибки, нулю - после истечения временного интервала или целому числу, равному числу «интересующих» программу дескрипторов файлов. Следует сделать предостережение: при возврате из вызова select он переустанавливает битовые маски, на которые указывают переменные readfds, writefds или errorfds, сбрасывая маску и снова задавая в ней дескрипторы файлов, содержащие искомую информацию. Поэтому необходимо сохранять копию исходных масок.
Именованные каналы
Каналы имеют ряд недостатков. Каналы могут использоваться только для связи процессов, имеющих общее происхождение, таких как родительский процесс и его потомок. Они не могут существовать постоянно. Каналы каждый раз должны создаваться заново, а после завершения обращающегося к ним процесса уничтожаются.
Для восполнения этих недостатков существует разновидность канала, называемая именованным каналом, или файлом типа FIFO. В отношении вызовов read и write именованные каналы идентичны обычным. Именованные каналы являются постоянными и им присвоено имя файла системы UNIX. Именованный канал также имеет владельца, размер и связанные с ним права доступа. Он может быть открыт, закрыт и удален, как и любой файл UNIX, но при чтении или записи ведет себя аналогично каналу.
Программирование при помощи каналов FIFO, в основном, идентично программированию с использованием обычных каналов. Единственное существенное различие заключается в их инициализации. Вместо использования вызова pipe канал FIFO создается при помощи вызова mkfifо.
Описание
int mkfifo(const char *pathname, mode_t mode);
Системный вызов mkfifо создает файл FIFO с именем, заданным первым параметром pathname. Канал FIFO будет иметь права доступа, заданные параметром mode и измененные в соответствии со значением umask процесса.
После создания канала FIFO он должен быть открыт при помощи вызова open.
Например:
mkfifo(“/tmp/fifo”, 0666);
…
fd = open(“/tmp/fifo”, O_WRONLY);
Вызов open будет заблокирован до тех пор, пока другой процесс не откроет канал FIFO для чтения.
Можно выполнить неблокирующий вызов open для канала FIFO. Для этого во время вызова должен быть установлен флаг O_NONBLOCK и один из флагов O_WRONLY или O_RDONLY.
Каналы создаются в программе при помощи системного вызова pipe. В случае удачного завершения вызов сообщает два дескриптора файла: один для записи в канал, а другой для чтения из него.
int pipe(int filedes[2]);
Переменная filedes является массивом из двух целых чисел, который будет содержать дескрипторы файлов, обозначающие канал. После успешного вызова filedes[0] будет открыт для чтения из канала, a filedes[1] для записи в канал.В случае неудачи вызов pipe вернет значение -1.
После создания канала с ним можно работать просто при помощи вызовов read и write.
Сообщения считываются в том же порядке, в каком они были записаны. Каналы обращаются с данными в порядке «первый вошел - первым вышел» (first-in first-out, или сокращенно FIFO). lseek не работает с каналами.
Размеры блоков при записи в канал и чтении из него необязательно должны быть одинаковыми.
Настоящее значение каналов проявляется при использовании вместе с системным вызовом fork, тогда можно воспользоваться тем фактом, что файловые дескрипторы остаются открытыми в обоих процессах.
В таком случае канал соединяет два процесса. И в родительском, и в дочернем процессах открыто по два дескриптора файла, позволяя выполнять запись в канал и чтение из него
Важно заметить, что размер буфера канала конечен. Только определенное число байтов может находиться в канале, прежде чем следующий вызов write будет заблокирован. При программировании важно знать максимальный размер канала для системы, так как он влияет на оба вызова write и read. Если вызов write выполняется для канала, в котором есть свободное место, то данные посылаются в канал, и немедленно происходит возврат из вызова. Если же в момент вызова write происходит переполнение канала, то выполнение процесса обычно приостанавливается до тех пор, пока место не освободится в результате выполнения другим процессом чтения из канала.
Обычно вызов write для канала выполняется неделимыми порциями (atomically), и данные передаются ядром за одну непрерываемую операцию.
Взаимодействие вызова read с каналами является более простым. При выполнении вызова read система проверяет, является ли канал пустым. Если он пуст, то вызов read будет заблокирован до тех пор, пока другой процесс не запишет в канал какие-либо данные. При наличии в канале данных произойдет возврат из вызова read, даже если запрашивается больший объем данных, чем находится в канале.
Для простых приложений применение неблокирующих операций чтения и записи работает прекрасно. Для работы с множеством каналов одновременно существует другое решение, которое заключается в использовании системного вызова select.
Родительский процесс может выступить в качестве серверного процесса и может иметь произвольное число связанных с ним клиентских (дочерних) процессов. В этом случае серверный процесс должен как-то справляться с ситуацией, когда одновременно в нескольких каналах может находиться информация, ожидающая обработки. Кроме того, если ни в одном из каналов нет ожидающих данных, то может иметь смысл приостановить работу серверного процесса до их появления, а не опрашивать постоянно каналы. Если информация поступает более чем по одному каналу, то серверный процесс должен знать обо всех таких каналах для того, чтобы работать с ними в правильном порядке (например, согласно их приоритетам).
Это можно сделать при помощи системного вызова select. Системный вызов select используется не только для каналов, но и для обычных файлов, именованных каналов и сокетов. Системный вызов select показывает, какие дескрипторы файлов из заданных наборов готовы для чтения, записи или ожидают обработки ошибок. Иногда серверный процесс не должен совсем прекращать работу, даже если не происходит никаких событий, поэтому в вызове select также можно задать предельное время ожидания.
Вызов select будет заблокирован до тех пор, пока не произойдет интересующее процесс событие. Если в структуре timeout задано нулевое время, то вызов select завершится немедленно (без блокирования). Если структура timeout содержит ненулевое значение, то возврат из вызова select произойдет через заданное число секунд или микросекунд, если файловые дескрипторы неактивны.
Возвращаемое вызовом select значение равно -1 в случае ошибки, нулю - после истечения временного интервала или целому числу, равному числу «интересующих» программу дескрипторов файлов. Следует сделать предостережение: при возврате из вызова select он переустанавливает битовые маски, на которые указывают переменные readfds, writefds или errorfds, сбрасывая маску и снова задавая в ней дескрипторы файлов, содержащие искомую информацию. Поэтому необходимо сохранять копию исходных масок.
Именованные каналы
Каналы имеют ряд недостатков. Каналы могут использоваться только для связи процессов, имеющих общее происхождение, таких как родительский процесс и его потомок. Они не могут существовать постоянно. Каналы каждый раз должны создаваться заново, а после завершения обращающегося к ним процесса уничтожаются.
Для восполнения этих недостатков существует разновидность канала, называемая именованным каналом, или файлом типа FIFO. В отношении вызовов read и write именованные каналы идентичны обычным. Именованные каналы являются постоянными и им присвоено имя файла системы UNIX. Именованный канал также имеет владельца, размер и связанные с ним права доступа. Он может быть открыт, закрыт и удален, как и любой файл UNIX, но при чтении или записи ведет себя аналогично каналу.
Программирование при помощи каналов FIFO, в основном, идентично программированию с использованием обычных каналов. Единственное существенное различие заключается в их инициализации. Вместо использования вызова pipe канал FIFO создается при помощи вызова mkfifо.
Описание
int mkfifo(const char *pathname, mode_t mode);
Системный вызов mkfifо создает файл FIFO с именем, заданным первым параметром pathname. Канал FIFO будет иметь права доступа, заданные параметром mode и измененные в соответствии со значением umask процесса.
После создания канала FIFO он должен быть открыт при помощи вызова open.
Например:
mkfifo(“/tmp/fifo”, 0666);
…
fd = open(“/tmp/fifo”, O_WRONLY);
Вызов open будет заблокирован до тех пор, пока другой процесс не откроет канал FIFO для чтения.
Можно выполнить неблокирующий вызов open для канала FIFO. Для этого во время вызова должен быть установлен флаг O_NONBLOCK и один из флагов O_WRONLY или O_RDONLY.