Строки 57–68 являются кодом для левого потомка. Процедура следует приведенным выше шагам, закрывая ненужный конец канала, закрывая первоначальный стандартный вывод, помещая с помощью
dup
записываемый конец канала на номер 1 и закрывая затем первоначальный записываемый конец. В этот момент строка 66 вызывает
execvp
, и если она завершается неудачей, строка 67 вызывает
_exit
. (Помните, что строка 67 никогда не выполняется, если
execvp
завершается удачно.)
Строки 72–83 делают подобные же шаги для правого потомка. Вот что происходит при запуске:
$ ch09-pipeline /* Запуск программы */
left child terminated, status: 0 /* Левый потомок завершается до вывода (!) */
hello there /* Вывод от правого потомка */
right child terminated, status: 0
$ ch09-pipeline /*
Повторный запуск программы */
hello there /* Вывод от правого потомка и ... */
right child terminated, status: 0 /* Правый потомок завершается до левого */
left child terminated, status: 0
Обратите внимание, что порядок, в котором завершаются потомки, не является детерминированным. Он зависит от загрузки системы и многих других факторов, которые могут повлиять на планирование процессов. Вам следует проявить осторожность, чтобы избежать предположений о порядке действий при написании кода, создающего несколько процессов, в особенности для кода, который вызывает семейство функций
wait
.
Весь процесс показан на рис. 9.5.
Рис. 9.5. Создание конвейера родителем
На рис. 9.5 (а) изображена ситуация после создания родителем канала (строки 22–25) и двух порожденных процессов (строки 27–37).
На рис. 9.5 (b) показана ситуация после закрытия родителем канала (строки 39–40) и начала ожидания порожденных процессов (строки 42–50). Каждый порожденный процесс поместил канал на место стандартного вывода (левый потомок, строки 61–63) и стандартного ввода (строки 76–78).
Наконец, рис. 9.5 (с) изображает ситуацию после закрытия потомками первоначального канала (строки 64 и 79) и вызова
execvp
(строки 66 и 81).
9.4.2. Создание нелинейных конвейеров:
/dev/fd/XX
Многие современные системы Unix, включая GNU/Linux, поддерживают в каталоге
/dev/fd
[98] специальные файлы. Эти файлы представляют дескрипторы открытых файлов с именами
/dev/fd/0
,
/dev/fd/1
и т.д. Передача такого имени функции
open
возвращает новый дескриптор файла, что в сущности является тем же самым, что и вызов
dup
для данного номера дескриптора.
98
На системах GNU/Linux
/dev/fd
является символической ссылкой на
/proc/self/fd
, но поскольку
/dev/fd
является общеизвестным, в своем коде следует использовать именно его — Примеч. автора.
Эти специальные файлы находят свое применение на уровне оболочки: Bash,
ksh88
(некоторые версии) и
ksh93
предоставляют возможность замещения процесса (process substitution), что позволяет создавать нелинейные конвейеры. На уровне оболочки для входного конвейера используется запись '
<(...)
', а для выходного конвейера запись '
>(...)
'. Например, предположим, вам нужно применить команду
diff
к выводу двух команд. Обычно вам пришлось бы использовать временные файлы: