Скачиваний:
65
Добавлен:
08.01.2014
Размер:
2.6 Mб
Скачать

7.1.2. Использование каналов в программе

Каналы создаются в программе при помощи системного вызова AssignPipe. В случае удачного завершения вызов сообщает два дескриптора файла: один для записи в канал, а другой для чтения из него. Вызов AssignPipe определяется следующим образом:

Описание

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 → →

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

fdwrite (fdw, msg3, MSGSIZE);

end;

else

begin

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

for j := 1 to 3 do

begin

fdread (fdr, inbuf, MSGSIZE);

writeln (inbuf);

end;

wait(nil);

end;

end;

halt (0);

end.

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

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

fdwrite()

fdw → →

← ← fdw

fdwrite()

fdread()

fdr ← ←

→ → fdr

fdread()

Рис. 7.2. Второй пример работы с каналами

Этот пример представлен графически на рис. 7.2. На нем показано, как канал соединяет два процесса. Здесь видно, что и в родительском, и в дочернем процессах открыто по два дескриптора файла, позволяя выполнять запись в канал и чтение из него. Поэтому любой из процессов может выполнять запись в файл с дескриптором fdw и чтение из файла с дескриптором fdr. Это создает определенную проблему. Каналы предназначены для использования в качестве однонаправленного средства связи. Если оба процесса будут одновременно выполнять чтение из канала и запись в него, то это приведет к путанице.

Чтобы избежать этого, каждый процесс должен выполнять либо чтение из канала, либо запись в него и закрывать дескриптор файла, как только он стал не нужен. Фактически программа должна выполнять это для того, чтобы избежать неприятностей, если посылающий данные процесс закроет дескриптор файла, открытого на запись, – раздел 7.1.4 объясняет возможные последствия. Приведенные до сих пор примеры работают только потому, что принимающий процесс в точности знает, какое количество данных он может ожидать. Следующий пример представляет собой законченное решение:

(* Третий пример работы с каналами *)

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

(* Дочерний процесс, закрывает дескриптор файла,

* открытого для чтения и выполняет запись в канал

*)

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

Соседние файлы в папке Полищук, Семериков. Системное программирование в UNIX средствами Free Pascal