<p>Родительский и дочерний процессы</p>

Следующий логический шаг в нашем изучении вызова pipe — разрешить дочернему процессу быть другой программой, отличной от своего родителя, а не просто другим процессом, выполняющим ту же самую программу. Сделать это можно с помощью вызова exec. Единственная сложность заключается в том, что новому процессу, созданному exec, нужно знать, какой файловый дескриптор применять для доступа. В предыдущем примере этой проблемы не возникло, потому что дочерний процесс обращался к своей копии данных file_pipes. После вызова exec возникает другая ситуация, поскольку старый процесс заменен новым дочерним процессом. Эту проблему можно обойти, если передать файловый дескриптор (который, в конце концов, просто число) как параметр программе, вновь созданной с помощью вызова exec.

Для того чтобы посмотреть, как это работает, вам понадобятся две программы (упражнение 13.7). Первая — поставщик данных. Она создает канал и затем вызывает дочерний процесс, потребитель данных.

Упражнение 13.7. Каналы и exec

1. Для получения первой программы исправьте pipe2.c, превратив ее в pipe3.c. Измененные строки затенены.

#include 

#include 

#include 

#include 

int main() {

 int data_processed;

 int file_pipes[2];

 const char somedata[] = "123";

 char buffer[BUFSIZ + 1];

 pid_t fork_result;

 memset(buffer, '\0', sizeof(buffer));

 if (pipe(file_pipes) == 0) {

  fork_result = fork();

  if (fork_result == (pid_t)-1) {

   fprintf(stderr, "Fork failure");

   exit(EXIT_FAILURE);

  }

  if (fork_result == 0) {

   sprintf(buffer, "%d", file_pipes[0]);

   (void)execl("pipe4", "pipe4", buffer, (char*)0);

   exit(EXIT_FAILURE);

  } else {

   data_processed = write(file_pipes[1], some_data, strlen(some_data));

   printf ("%d - wrote %d bytes\n", getpid(), data_processed);

  }

 }

 exit(EXIT_SUCCESS);

}

2. Программа-потребитель pipe4.c, читающая данные, гораздо проще:

#include

#include

#include

#include

int main(int argc, char *argv[]) {

 int data_processed;

 char buffer[BUFSIZ + 1];

 int file_descriptor;

 memset(buffer, '\0', sizeof(buffer));

 sscanf(argv[1], "%d", &file_descriptor);

 data_processed = read(file_descriptor, buffer, BUFSIZ);

 printf("%d — read %d bytes: %s\n", getpid(), data_processed,

  buffer);

 exit(EXIT_SUCCESS);

}

Выполнив pipe3 и помня о том, что она вызывает программу pipe4, вы получите вывод, аналогичный приведенному далее:

$ ./pipe3

22460 - wrote 3 bytes

22461 - read 3 bytes: 123

Как это работает

Программа pipe3 начинается как предыдущий пример, используя вызов pipe для создания канала и затем вызов fork для создания нового процесса. Далее она применяет функцию sprintf для сохранения в буфере номера файлового дескриптора чтения из канала, который формирует аргумент программы pipe4.

Вызов execl применен для вызова программы pipe4. В нем использованы следующие аргументы:

□ вызванная программа;

□ argv[0], принимающий имя программы;

□ argv[1], содержащий номер файлового дескриптора, из которого программа должна читать;

□ (char *)0, завершающий список параметров.

Программа pipe4 извлекает номер файлового дескриптора из строки аргументов и затем читает из него данные.

<p>Чтение закрытых каналов</p>

Прежде чем двигаться дальше, необходимо более внимательно рассмотреть файловые дескрипторы, которые открыты. До этого момента вы разрешали читающему процессу просто читать какие-то данные и завершаться, полагая, что ОС Linux уберет файлы в ходе завершения процесса.

В большинстве программ, читающих данные из стандартного ввода, это делается несколько иначе, чем в виденных вами до сих пор примерах. Обычно программы не знают, сколько данных они должны считать, поэтому они, как правило, выполняют цикл — чтение данных, их обработка и затем снова чтение данных и так до тех пор, пока не останется данных для чтения.

Перейти на страницу:

Похожие книги