Системное программирование в UNIX средствами Free Pascal

Каналы и системный вызов ехес


Вспомним, как можно создать канал между двумя программами с помощью командного интерпретатора:

$ ls | wc

Как это происходит? Ответ состоит из двух частей. Во-первых, командный интерпретатор использует тот факт, что открытые дескрипторы файлов остаются открытыми (по умолчанию) после вызова ехес. Это означает, что два файловых дескриптора канала, которые были открыты до выполнения комбинации вызовов fork/ехес, останутся открытыми и когда дочерний процесс начнет выполнение новой программы. Во-вторых, перед вызовом ехес командный интерпретатор соединяет стандартный вывод программы ls

с входом канала, а стандартный ввод программы wc – с выходом канала. Это можно сделать при помощи вызова fcntl или dup2, как было показано в упражнении 5.10. Так как значения дескрипторов файлов, соответствующих стандартному вводу, стандартному выводу и стандартному выводу диагностики, равны 0, 1 и 2 соответственно, то можно, например, соединить стандартный вывод с другим дескриптором файла, используя вызов dup2 следующим образом. Обратите внимание, что перед переназначением вызов dup2 закрывает файл, представленный его вторым параметром.

(* Вызов dup2 будет копировать дескриптор файла 1 *)

dup2(filedes, 1);

.

.

.

(* Теперь программа будет записывать свой стандартный *)

(* вывод в файл, заданный дескриптором filedes *)

.

.



.

Следующий пример, программа join, демонстрирует механизм каналов, задействованный в упрощенном командном интерпретаторе. Программа join имеет два параметра, com1 и com2, каждый из которых соответствует выполняемой команде. Оба параметра в действительности являются массивами строк, которые будут переданы вызову execvp.

Родительский процесс

wait()

Потомок дочернего процесса

(com1)

Дочерний процесс

(com2)

> > fdin

fdread()

^

(stdin)

fdwrite()

fdout > >

(stdout)

<


Рис. 7.5. Программа join

Программа join запустит обе программы на выполнение и свяжет стандартный вывод программы com1 со стандартным вводом программы com2. Работа программы join изображена на рис. 7.5 и может быть описана следующей схемой (без учета обработки ошибок):

процесс порождает дочерний процесс и ожидает действий от него

дочерний процесс продолжает работу

дочерний процесс создает канал

затем дочерний процесс порождает еще один дочерний процесс

В потомке дочернего процесса:

стандартный вывод подключается

к входу канала при помощи вызова dup2

ненужные дескрипторы файлов закрываются

при помощи вызова ехес запускается программа,

заданная параметром 'com1'

В первом дочернем процессе:

стандартный ввод подключается

к выходу канала при помощи вызова dup2

ненужные дескрипторы файлов закрываются

при помощи вызова ехес запускается программа,

заданная параметром 'com2'

Далее следует реализация программы join; она также использует процедуру fatal, представленную в разделе 7.1.5.

(* Программа join - соединяет две программы каналом *)

function join (com1, com2:ppchar):integer;

var

  fdin,fdout:longint;

  status:integer;

begin

  (* Создать дочерний процесс для выполнения команд *)

  case fork of

    -1:               (* ошибка *)

      fatal ('Ошибка 1 вызова fork в программе join');

    0:                (* дочерний процесс *)

      ;

    else              (* родительский процесс *)

    begin

      wait(@status);

      join:=status;

      exit;

    end;

  end;

  (* Остаток процедуры, выполняемой дочерним процессом *)

  (* Создать канал *)

  if not assignpipe(fdin,fdout) then

    fatal ('Ошибка вызова pipe в программе join');

  (* Создать еще один процесс *)

  case fork of

    -1:

      (* ошибка *)

      fatal ('Ошибка 2 вызова fork в программе join');

    0:

    begin

      (* процесс, выполняющий запись *)

      dup2 (fdout, 1);       (* направить ст. вывод в канал *)



      fdclose (fdin);        (* сохранить дескрипторы файлов *)

      fdclose (fdout);

      execvp (com1[0], com1, envp);

      (* Если execvp возвращает значение, то произошла ошибка *)

      fatal ('Ошибка 1 вызова execvp в программе join');

    end;

    else

    begin

      (* процесс, выполняющий чтение *)

      dup2 (fdin, 0);        (* направить ст. ввод из канала *)

      fdclose (fdin);

      fdclose (fdout);

      execvp (com2[0], com2, envp);

      fatal ('Ошибка 2 вызова execvp в программе join');

    end;

  end;

end;

Эту процедуру можно вызвать следующим образом:

uses linux, stdio;

const

  one:array [0..3] of pchar = ('ls', '-l', '/usr/lib', nil);

  two:array [0..2] of pchar = ('grep', '^d', nil);

var

  ret:integer;

begin

  ret := join (one, two);

  writeln ('Возврат из программы join ', ret);

  halt (0);

end.

Упражнение 7.3. Как можно обобщить подход, показанный в программе join, для связи нескольких процессов при помощи каналов?

Упражнение 7.4. Добавьте возможность работы с каналами в командный интерпретатор smallsh, представленный в предыдущей главе.

Упражнение 7.5. Придумайте метод, позволяющий родительскому процессу запускать программу в качестве дочернего процесса, а затем считывать ее стандартный вывод при помощи канала. Стоит отметить, что эта идея лежит в основе процедур popen/pipeopen и pclose/pipeclose, которые входят в стандартную библиотеку ввода/вывода. Процедуры popen/pipeopen и pclose/pipeclose избавляют программиста от большинства утомительных деталей согласования вызовов fork, ехес, fdclose, dup или dup2. Эти процедуры обсуждаются в главе 11.


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