Выполнив эту программу, вы получите следующий вывод:
$ ./popen4
Reading:-
94
Как
это работает
Программа показывает, что вызывается командная оболочка для того, чтобы развернуть
popen*.с
в список всех файлов, начинающихся с
popen
и заканчивающихся
.с
, а также для обработки символа канала (
|
) и отправки вывода команды
cat
в команду
wс
. Вы вызываете командную оболочку, программы
cat
и
wc
и задаете перенаправление — все в одном вызове
popen
. Программа, вызвавшая команду, видит только заключительный вывод.
Вызов pipe
Вы познакомились с высокоуровневой функцией
popen
, а теперь пойдем дальше и рассмотрим низкоуровневую функцию
pipe
. Она предоставляет средства передачи данных между двумя программами без накладных расходов на вызов командной оболочки для интерпретации запрашиваемой команды. Эта функция также позволит вам лучше управлять чтением и записью данных.
У функции
pipe
следующее объявление:
#include <unistd.h>
int pipe(int file_descriptor[2]);
Функции
pipe
передается указатель на массив из двух целочисленных файловых дескрипторов. Она заполняет массив двумя новыми файловыми дескрипторами и возвращает 0. В случае неудачи она вернет -1 и установит переменную
errno
для указания причины сбоя. В интерактивном справочном руководстве Linux на странице, посвященной функций
pipe
(в разделе 2 руководства), определены следующие ошибки:
EMFILE
— процесс использует слишком много файловых дескрипторов;
ENFILE
— системная таблица файлов полна;
EFAULT
— некорректный файловый дескриптор.
Два возвращаемых файловых дескриптора подсоединяются специальным образом. Любые данные, записанные в
file_descriptor[1]
, могут быть считаны обратно из
file_descriptor[0]
. Данные обрабатываются по алгоритму "первым пришел, первым обслужен", обычно обозначаемому как FIFO. Это означает, что если вы записываете байты
1
,
2
,
3
в
file_descriptor[1]
, чтение из
file_descriptor[0]
выполняется в следующем порядке:
1
,
2
,
3
. Этот способ отличается от стека, который функционирует по алгоритму "последним пришел, первым обслужен", который обычно называют сокращенно LIFO.
Примечание
Важно уяснить, что речь идет о файловых дескрипторах, а не о файловых потоках, поэтому для доступа
к данным вы должны применять низкоуровневые системные вызовы
read
и
write
вместо библиотечных функций потоков
fread
и
fwrite
.
В упражнении 13.5 приведена программа pipe1.с, которая использует вызов
pipe
для создания канала.
Упражнение 13.5 Функция
pipe
Следующий пример — программа pipe1.c. Обратите внимание на массив
Если вы выполните программу, то получите следующий вывод:
$ ./pipe1
Wrote 3 bytes
Read 3 bytes: 123
Как это работает
Программа создает канал с помощью двух файловых дескрипторов из массива
file_pipes[]
. Далее она записывает данные в канал, используя файловый дескриптор
file_pipes[1]
, и считывает их обратно из
file_pipes[0]
. Учтите, что у канала есть внутренняя буферизация, позволяющая хранить данные между вызовами функций
write
и
read
.
Следует знать, что реакция на попытку писать с помощью дескриптора
file_descriptor[0]
или читать с помощью дескриптора
file_descriptor[1]
не определена, поэтому поведение программы может быть очень странным и меняться без каких-либо предупреждений. В системах авторов такие вызовы заканчивались аварийно и возвращали -1, что, по крайней мере, гарантирует легкость обнаружения такой ошибки.
На первый взгляд этот пример использования канала ничего не предлагает такого, чего мы не могли бы сделать с помощью простого файла. Действительные преимущества каналов проявятся, когда вам нужно будет передавать данные между двумя процессами. Как вы видели в главе 11, когда программа создает новый процесс с помощью вызова
fork
, уже открытые к этому моменту файловые дескрипторы так и остаются открытыми. Создав канал в исходном процессе и затем сформировав с помощью
fork
новый процесс, вы сможете передать данные из одного процесса в другой через канал (упражнение 13.6).