Архитектура Unix

       

Использование функций pipe, dup и fork



Рисунок 7.5. Использование функций pipe, dup и fork

#include <string.h> char string[] = "hello world"; main() { int count,i; int to_par[2],to_chil[2]; /* для каналов родителя и потомка */ char buf[256]; pipe(to_par); pipe(to_chil); if (fork() == 0) { /* выполнение порожденного процесса */ close(0); /* закрытие прежнего стандартного ввода */ dup(to_chil[0]); /* дублирование дескриптора чтения из канала в позицию стандартного ввода */ close(1); /* закрытие прежнего стандартного вывода */ dup(to_par[0]); /* дублирование дескриптора записи в канал в позицию стандартного вывода */ close(to_par[1]); /* закрытие ненужных дескрипторов close(to_chil[0]); канала */ close(to_par[0]); close(to_chil[1]); for (;;) { if ((count = read(0,buf,sizeof(buf))) == 0) exit(); write(1,buf,count); } } /* выполнение родительского процесса */ close(1); /* перенастройка стандартного ввода-вывода */ dup(to_chil[1]); close(0); dup(to_par[0]); close(to_chil[1]); close(to_par[0]); close(to_chil[0]); close(to_par[1]); for (i = 0; i < 15; i++) { write(1,string,strlen(string)); read(0,buf,sizeof(buf)); } }


Результаты этой программы не зависят от того, в какой очередности процессы выполняют свои действия. Таким образом, нет никакой разницы, возвращается ли управление родительскому процессу из функции fork раньше или позже, чем порожденному процессу. И так же безразличен порядок, в котором процессы вызывают системные функции перед тем, как войти в свой собственный цикл, ибо они используют идентичные структуры ядра. Если процесс-потомок исполняет функцию read раньше, чем его родитель выполнит write, он будет приостановлен до тех пор, пока родительский процесс не произведет запись в канал и тем самым не возобновит выполнение потомка. Если родительский процесс записывает в канал до того, как его потомок приступит к чтению из канала, первый процесс не сможет в свою очередь считать данные из стандартного ввода, пока второй процесс не прочитает все из своего стандартного ввода и не произведет запись данных в стандартный вывод. С этого места порядок работы жестко фиксирован: каждый процесс завершает выполнение функций read и write и не может выполнить следующую операцию read до тех пор, пока другой процесс не выполнит пару read-write. Родительский процесс после 15 итераций завершает работу; порожденный процесс наталкивается на конец файла ("end-of-file"), поскольку канал не связан больше ни с одним из записывающих процессов, и тоже завершает работу. Если порожденный процесс попытается произвести запись в канал после завершения родительского процесса, он получит сигнал о том, что канал не связан ни с одним из процессов чтения.

Мы упомянули о том, что хорошей традицией в программировании является закрытие ненужных файловых дескрипторов. В пользу этого говорят три довода. Во-первых, дескрипторы файлов постоянно находятся под контролем системы, которая накладывает ограничение на их количество. Во-вторых, во время исполнения порожденного процесса присвоение дескрипторов в новом контексте сохраняется (в чем мы еще убедимся). Закрытие ненужных файлов до запуска процесса открывает перед программами возможность исполнения в "стерильных" условиях, свободных от любых неожиданностей, имея открытыми только файлы стандартного ввода-вывода и ошибок. Наконец, функция read для канала возвращает признак конца файла только в том случае, если канал не был открыт для записи ни одним из процессов. Если считывающий процесс будет держать дескриптор записи в канал открытым, он никогда не узнает, закрыл ли записывающий процесс работу на своем конце канала или нет. Вышеприведенная программа не работала бы надлежащим образом, если бы перед входом в цикл выполнения процессом-потомком не были закрыты дескрипторы записи в канал.



Содержание раздела