и 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. Для установления двусторонней связи между процессами можно создать два канала, работающих в разных направлениях. Придумайте возможный диалог между процессами и реализуйте его при помощи двух каналов.