char c;

main(argc, argv)

int argc;

char *argv[];

{

 if (argc != 3) exit(1);

 if ((fdrd = open(argv[1], O_RDONLY)) == -1) exit(1);

 if ((fdwt = creat(argv[2], 0666)) == -1) exit(1);

 fork(); /* оба процесса исполняют одну и ту же программу */

 rdwrt();

 exit(0);

}

rdwrt() {

 for(;;) {

  if (read(fdrd, &c,1) != 1)  return;

   write(fdwt, &c,1);

 }

}

Рисунок 7.4. Программа, в которой родительский и порожденный процессы разделяют доступ к файлу

Теперь перейдем к программе, представленной на Рисунке 7.5, в которой процесс-потомок наследует от своего родителя файловые дескрипторы 0 и 1 (соответствующие стандартному вводу и стандартному выводу). При каждом выполнении системной функции pipe производится назначение двух файловых дескрипторов в массивах to_par и to_chil. Процесс вызывает функцию fork и делает копию своего контекста: каждый из процессов имеет доступ только к своим собственным данным, так же как и в предыдущем примере. Родительский процесс закрывает файл стандартного вывода (дескриптор 1) и дублирует дескриптор записи, возвращаемый в канал to_chil. Поскольку первое свободное место в таблице дескрипторов родительского процесса образовалось в результате только что выполненной операции закрытия (close) файла вывода, ядро переписывает туда дескриптор записи в канал и этот дескриптор становится дескриптором файла стандартного вывода для to_chil. Те же самые действия родительский процесс выполняет в отношении дескриптора файла стандартного ввода, заменяя его дескриптором чтения из канала to_par. И порожденный процесс закрывает файл стандартного ввода (дескриптор 0) и так же дублирует дескриптор чтения из канала to_chil. Поскольку первое свободное место в таблице дескрипторов файлов прежде было занято файлом стандартного ввода, его дескриптором становится дескриптор чтения из канала to_chil. Аналогичные действия выполняются и в отношении дескриптора файла стандартного вывода, заменяя его дескриптором записи в канал to_par. И тот, и другой процессы закрывают файлы, дескрипторы которых возвратила функция pipe — хорошая традиция, в чем нам еще предстоит убедиться. В результате, когда родительский процесс переписывает данные в стандартный вывод, запись ведется в канал to_chil и данные поступают к порожденному процессу, который считывает их через свой стандартный ввод. Когда же порожденный процесс пишет данные в стандартный вывод, запись ведется в канал to_par и данные поступают к родительскому процессу, считывающему их через свой стандартный ввод. Так через два канала оба процесса обмениваются сообщениями.

#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));

 }

}

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

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

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

7.2 СИГНАЛЫ

Добавить отзыв
ВСЕ ОТЗЫВЫ О КНИГЕ В ИЗБРАННОЕ

0

Вы можете отметить интересные вам фрагменты текста, которые будут доступны по уникальной ссылке в адресной строке браузера.

Отметить Добавить цитату