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

         

и pipe_out содержат дескрипторы файлов,


uses linux;
Function AssignPipe(var pipe_in,pipe_out:longint):boolean;
Function AssignPipe(var pipe_in,pipe_out:text):boolean;
Function AssignPipe(var pipe_in,pipe_out:file):boolean;
Переменные pipe_in и pipe_out содержат дескрипторы файлов, обозначающие канал. После успешного вызова pipe_in будет открыт для чтения из канала, a pipe_out для записи в канал.
В случае неудачи вызов pipe вернет значение false. Это может произойти, если в момент вызова произойдет превышение максимально возможного числа дескрипторов файлов, которые могут быть одновременно открыты процессами пользователя (в этом случае переменная linuxerror будет содержать значение Sys_EMFILE), или если произойдет переполнение таблицы открытых файлов в ядре (в этом случае переменная linuxerror будет содержать значение Sys_ENFILE).
После создания канала с ним можно работать просто при помощи вызовов fdread и fdwrite. Следующий пример демонстрирует это: он создает канал, записывает в него три сообщения, а затем считывает их из канала:
uses linux,stdio;
(* Первый пример работы с каналами *)
const
  MSGSIZE=16;
(* Эти строки заканчиваются нулевым символом *)
  msg1:array [0..MSGSIZE-1] of char = 'hello, world #1';


  msg2:array [0..MSGSIZE-1] of char = 'hello, world #2';
  msg3:array [0..MSGSIZE-1] of char = 'hello, world #3';
var
  inbuf:array [0..MSGSIZE-1] of char;
  fdr,fdw,j:longint;
begin
  (* Открыть канал *)
  if not assignpipe(fdr,fdw) then
  begin
    perror ('Ошибка вызова pipe');
    halt (1);
  end;
  (* Запись в канал *)
  fdwrite (fdw, msg1, MSGSIZE);
  fdwrite (fdw, msg2, MSGSIZE);
  fdwrite (fdw, msg3, MSGSIZE);
  (* Чтение из канала *)
  for j := 1 to 3 do
  begin
    fdread (fdr, inbuf, MSGSIZE);
    writeln(inbuf);
  end;
  halt (0);
end.
На выходе программы получим:
hello, world #1
hello, world #2
hello, world #3
Обратите внимание, что сообщения считываются в том же порядке, в каком они были записаны. Каналы обращаются с данными в порядке «первый вошел – первым вышел» (first-in first-out, или сокращенно FIFO). Другими словами, данные, которые помещаются в канал первыми, первыми и считываются на другом конце канала. Этот порядок нельзя изменить, поскольку вызов fdseek не работает с каналами.


Размеры блоков при записи в канал и чтении из него необязательно должны быть одинаковыми, хотя в нашем примере это и было так. Можно, например, писать в канал блоками по 512 байт, а затем считывать из него по одному символу, так же как и в случае обычного файла. Тем не менее, как будет показано в разделе 7.2, использование блоков фиксированного размера дает определенные преимущества.

Процесс
fdwrite()
fdw > >
v
fdread()
fdr < <

Рис. 7.1. Первый пример работы с каналами
Работа примера показана графически на рис. 7.1. Эта диаграмма позволяет более ясно представить, что процесс только посылает данные сам себе, используя канал в качестве некой разновидности механизма o6paтной связи. Это может показаться бессмысленным, поскольку процесс общается только сам с собой.
Настоящее значение каналов проявляется при использовании вместе с системным вызовом fork, тогда можно воспользоваться тем фактом, что файловые дескрипторы остаются открытыми в обоих процессах. Следующий пример демонстрирует это. Он создает канал и вызывает
fork, затем дочерний процесс обменивается несколькими сообщениями с родительским:
(* Второй пример работы с каналами *)
uses linux, stdio;
const
  MSGSIZE=16;
  msg1:array [0..MSGSIZE-1] of char = 'hello, world #1';
  msg2:array [0..MSGSIZE-1] of char = 'hello, world #2';
  msg3:array [0..MSGSIZE-1] of char = 'hello, world #3';
var
  inbuf:array [0..MSGSIZE-1] of char;
  fdr,fdw,j,pid:longint;
begin
  (* Открыть канал *)
  if not assignpipe (fdr,fdw) then
  begin
    perror ('Ошибка вызова pipe ');
    halt (1);
  end;
  pid := fork;
  case pid of
    -1:
    begin
      perror ('Ошибка вызова fork');
      halt (2);
    end;
    0:
    begin
      (* Это дочерний процесс, выполнить запись в канал *)
      fdwrite (fdw, msg1, MSGSIZE);
      fdwrite (fdw, msg2, MSGSIZE);


  msg3:array [0..MSGSIZE-1] of char = 'hello, world #3';
var
  inbuf:array [0..MSGSIZE-1] of char;
  fdr,fdw,j,pid:longint;
begin
  (* Открыть канал *)
  if not assignpipe (fdr,fdw) then
  begin
    perror ('Ошибка вызова pipe ');
    halt (1);
  end;
  pid := fork;
  case pid of
    -1:
    begin
      perror ('Ошибка вызова fork');
      halt (2);
    end;
    0:
    begin
      (* Дочерний процесс, закрывает дескриптор файла,
       * открытого для чтения и выполняет запись в канал
       *)
      fdclose (fdr);
      fdwrite (fdw, msg1, MSGSIZE);
      fdwrite (fdw, msg2, MSGSIZE);
      fdwrite (fdw, msg3, MSGSIZE);
    end;
    else
    begin
      (* Родительский процесс, закрывает дескриптор файла,
       * открытого для записи и выполняет чтение из канала
       *)
      fdclose (fdw);
      for j := 1 to 3 do
      begin
        fdread (fdr, inbuf, MSGSIZE);
        writeln (inbuf);
      end;
      wait(nil);
    end;
  end;
  halt (0);
end.
В конечном итоге получится однонаправленный поток данных от дочернего процесса к родительскому. Эта упрощенная ситуация показана на рис. 7.3.

Дочерний процесс
Родительский процесс
> > fdr
fdread()
^
fdwrite()
fdw > >

Рис. 7.3. Третий пример работы с каналами
Упражнение 7.1. В последнем примере канал использовался для установления связи между родительским и дочерним процессами. Но дескрипторы файлов канала могут передаваться и сквозь через несколько вызовов fork. Это означает, что несколько процессов могут писать в канал и несколько процессов могут читать из него. Для демонстрации этого поведения напишите программу, которая создает три процесса, два из которых выполняют запись в канал, а один – чтение из него. Процесс, выполняющей чтение, должен выводить все получаемые им сообщения на свой стандартный вывод.
Упражнение 7.2. Для установления двусторонней связи между процессами можно создать два канала, работающих в разных направлениях. Придумайте возможный диалог между процессами и реализуйте его при помощи двух каналов.

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