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

7.1.4. Закрытие каналов

Что произойдет, если дескриптор файла, соответствующий одному из концов канала, будет закрыт? Возможны два случая:

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

  • закрывается дескриптор файла, открытого только на чтение. Если еще есть процессы, в которых канал открыт на чтение, то снова ничего не произойдет. Если же больше не существует процессов, выполняющих чтение из канала, то ядро посылает всем процессам, ожидающим записи в канал, сигнал SIGPIPE. Если этот сигнал не перехватывается в процессе, то процесс при этом завершит свою работу. Если же сигнал перехватывается, то после завершения процедуры обработчика прерывания вызов fdwrite вернет значение -1 и переменная linuxerror после этого будет содержать значение Sys_EPIPE. Процессам, которые будут пытаться после этого выполнить запись в канал, также будет посылаться сигнал SIGPIPE.

7.1.5. Запись и чтение без блокирования

Как уже было упомянуто, при использовании и вызова fdread, и вызова fdwrite может возникнуть блокирование, которое иногда нежелательно. Может, например, понадобиться, чтобы программа выполняла процедуру обработки ошибок или опрашивала несколько каналов до тех пор, пока не получит данные из одного из них. К счастью, есть простые способы пресечения нежелательных остановов внутри fdread и fdwrite.

Первый метод заключается в использовании для вызова fstat. Поле size в возвращаемой вызовом структуре tstat сообщает текущее число символов, находящихся в канале. Если только один процесс выполняет чтение из канала, такой подход работает прекрасно. Если же несколько процессов выполняют чтение из канала, то за время, прошедшее между вызовами fstat и fdread, ситуация может измениться, если другой процесс успеет выполнить чтение из канала.

Второй метод заключается в использовании вызова fcntl. Помимо других выполняемых им функций этот вызов позволяет процессу устанавливать для дескриптора файла флаг Open_NONBLOCK. Это предотвращает блокировку последующих вызовов fdread или fdwrite. В этом контексте вызов fcntl может использоваться следующим образом:

uses linux;

.

.

.

fcntl(filedes, F_SETFL, Open_NONBLOCK);

if linuxerror <> 0 then

perror('fcntl');

Если дескриптор filedes является открытым только на запись, то следующие вызовы fdwrite не будут блокироваться при заполнении канала. Вместо этого они будут немедленно возвращать значение -1 и присваивать переменной linuxerror значение Sys_EAGAIN. Аналогично, если дескриптор filedes соответствует выходу канала, то процесс немедленно вернет значение -1, если в канале нет данных, а не приостановит работу. Так же, как и в случае вызова fdwrite, переменной linuxerror будет присвоено значение Sys_EAGAIN. (Если установлен другой флаг – Open_NDELAY, то поведение вызова fdread будет другим. Если канал пуст, то вызов вернет нулевое значение. Далее этот случай не будет рассматриваться.)

Следующая программа иллюстрирует применение вызова fcntl. В ней создается канал, для дескриптора чтения из канала устанавливается флаг Open_NONBLOCK, а затем выполняется вызов fork. Дочерний процесс посылает сообщения родительскому, выполняющему бесконечный цикл, опрашивая канал и проверяя, поступили ли данные.

(* Пример использования флага Open_NONBLOCK *)

uses linux,stdio;

const

MSGSIZE=6;

msg1:array [0..MSGSIZE-1] of char = 'hello';

msg2:array [0..MSGSIZE-1] of char = 'bye!!';

function parent(fdin,fdout:integer):integer; (* код родительского процесса *)

var

nread:longint;

buf:array [0..MSGSIZE-1] of char;

begin

fdclose (fdout);

while true do

begin

nread := fdread (fdin, buf, MSGSIZE);

case nread of

-1:

begin

(* Проверить, есть ли данные в канале. *)

if linuxerror = Sys_EAGAIN then

begin

writeln('(канал пуст)');

sleep (1);

end

else

fatal ('Ошибка вызова read');

end;

0:

begin

(* Канал был закрыт. *)

writeln('Конец связи');

halt (0);

end;

else

writeln('MSG=', buf);

end;

end;

end;

function child (fdin,fdout:integer):integer;

var

count:longint;

begin

fdclose (fdin);

for count := 1 to 3 do

begin

fdwrite (fdout, msg1, MSGSIZE);

sleep (3);

end;

(* Послать последнее сообщение *)

fdwrite (fdout, msg2, MSGSIZE);

halt (0);

end;

var

fdin,fdout:longint;

begin

(* Открыть канал *)

if not assignpipe (fdin,fdout) then

fatal ('Ошибка вызова pipe ');

(* Установить флаг Open_NONBLOCK для дескриптора fdin *)

fcntl (fdin, F_SETFL, Open_NONBLOCK);

if (linuxerror=sys_eagain) or (linuxerror=sys_eacces) then

fatal ('ошибка вызова fcntl');

case fork of

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

fatal ('ошибка вызова fork');

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

child (fdin,fdout);

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

parent (fdin,fdout);

end;

end.

Этот пример использует для вывода сообщений об ошибках процедуру fatal, описанную в предыдущей главе. Чтобы не возвращаться назад, приведем ее peaлизацию:

(* Вывести сообщение об ошибке и закончить работу *)

function fatal(s:pchar):integer;

begin

perror (s);

halt (1);

end;

Вывод программы не полностью предсказуем, так как число сообщений «канал пуст» может быть различным. На одном из компьютеров был получен следующий вывод:

MSG=hello

(канал пуст)

(канал пуст)

(канал пуст)

MSG=hello

(канал пуст)

(канал пуст)

(канал пуст)

MSG=hello

(канал пуст)

(канал пуст)

(канал пуст)

MSG=bye!!

Конец связи

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