Системный вызов mkfifo создает файл
uses linux;
Function MkFifo(PathName:String; Mode:Longint):Boolean;
Системный вызов mkfifo создает файл FIFO с именем, заданным первым параметром pathname. Канал FIFO будет иметь права доступа, заданные параметром mode и измененные в соответствии со значением umask процесса.
После создания канала FIFO он должен быть открыт при помощи вызова fdореn. Поэтому, например, фрагмент кода
uses linux;
.
.
mkfifo('/tmp/fifo', octal(0666));
.
.
fd := fdopen('/tmp/fifo', Open_WRONLY);
открывает канал FIFO для записи. Вызов fdopen будет заблокирован до тех пор, пока другой процесс не откроет канал FIFO для чтения (конечно же, если канал FIFO уже был открыт для чтения, то возврат из вызова open произойдет немедленно).
Можно выполнить не блокирующий вызов fdopen для канала FIFO. Для этого во время вызова должен быть установлен флаг Open_NONBLOCK (определенный в файле linux) и один из флагов Open_RDONLY или Open_WRONLY, например:
fd := fdopen('/tmp/fifo', Open_WRONLY or Open_NONBLOCK);
if fd = -1 then
perror('Ошибка вызова open для канала FIFO');
Если не существует процесс, в котором канал FIFO открыт для чтения, то этот вызов fdopen вернет значение -1 вместо блокировки выполнения, а переменная linuxerror будет содержать значение Sys_ENXIO. В случае же успешного вызова fdopen последующие вызовы fdwrite для канала FIFO также будут не блокирующими.
Наступило время привести пример. Представим две программы, которые показывают, как можно использовать канал FIFO для реализации системы обмена сообщениями. Эти программы используют тот факт, что вызовы fdread или fdwrite для каналов FIFO, как и для программных каналов, являются неделимыми (для небольших порций данных). Если при помощи канала FIFO пересылаются сообщения фиксированного размера, то отдельные сообщения будут сохраняться, даже если несколько процессов одновременно выполняют запись в канал.
Рассмотрим вначале программу sendmessage, которая посылает отдельные сообщения в канал FIFO с именем fifo. Она вызывается следующим образом:
$ sendmessage 'текст сообщения 1' 'текст сообщения 2'
Обратите внимание на то, что каждое сообщение заключено в кавычки и поэтому считается просто одним длинным аргументом. Если не сделать этого, то каждое слово будет рассматриваться, как отдельное сообщение. Программа sendmessage имеет следующий исходный текст:
(* Программа sendmessage - пересылка сообщений через FIFO *)
uses linux,stdio,strings;
const
MSGSIZ=63;
fifo = 'fifo';
var
fd,j:integer;
nwrite:longint;
msgbuf:array [0..MSGSIZ] of char;
begin
if paramcount=0 then
begin
writeln (stderr, 'Применение: sendmessage сообщение');
halt (1);
end;
(* Открыть канал fifo, установив флаг Open_NONBLOCK *)
fd := fdopen (fifo, Open_WRONLY or Open_NONBLOCK);
if fd < 0 then
fatal ('Ошибка вызова open для fifo');
(* Посылка сообщений *)
for j := 1 to paramcount do
begin
if length(paramstr(j)) > MSGSIZ then
begin
writeln('Слишком длинное сообщение ', paramstr(j));
continue;
end;
strpcopy(msgbuf, paramstr(j));
nwrite := fdwrite (fd, msgbuf, MSGSIZ + 1);
if nwrite = -1 then
fatal ('Ошибка записи сообщения');
end;
halt(0);
end.
И снова для вывода сообщений об ошибках использована процедура fatal. Сообщения посылаются блоками по 64 байта при помощи не блокируемого вызова fdwrite. В действительности текст сообщения ограничен 63 символами, а последний символ является нулевым.
Программа rcvmessage принимает сообщения при помощи чтения из канала FIFO. Она не выполняет никаких полезных действий и служит только демонстрационным примером:
(* Программа rcvmessage - получение сообщений из канала fifo *)
uses linux,stdio;
const
MSGSIZ=63;
fifo = 'fifo';
var
fd:integer;
msgbuf:array [0..MSGSIZ] of char;
begin
(* Создать канал fifo, если он еще не существует *)
if not mkfifo (fifo, octal(0666)) then
if linuxerror <> Sys_EEXIST then
fatal ('Ошибка приемника: вызов mkfifo');
(* Открыть канал fifo для чтения и записи. *)
fd := fdopen (fifo, Open_RDWR);
if fd < 0 then
fatal ('Ошибка при открытии канала fifo');
(* Прием сообщений *)
while true do
begin
if fdread (fd, msgbuf, MSGSIZ + 1) < 0 then
fatal ('Ошибка при чтении сообщения');
(*
* вывести сообщение; в настоящей программе
* вместо этого могут выполняться какие-либо
* полезные действия.
*)
writeln('Получено сообщение: ', msgbuf);
end;
end.
Обратите внимание на то, что канал
FIFO открывается одновременно для чтения и записи (при помощи задания флага Open_RDWR). Чтобы понять, для чего это сделано, предположим, что канал FIFO был открыт только для чтения при помощи задания флага Open_RDONLY. Тогда выполнение программы rcvmessage будет сразу заблокировано в момент вызова fdopen. Когда после старта программы sendmessage в канал FIFO будет произведена запись, вызов fdopen будет разблокирован, программа rcvmessage будет читать все посылаемые сообщения. Когда же канал FIFO станет пустым, а процесс sendmessage завершит работу, вызов fdread начнет возвращать нулевое значение, так как канал
FIFO уже не будет открыт на запись ни в одном процессе. При этом программа rcvmessage войдет в бесконечный цикл. Использование флага Open_RDWR позволяет гарантировать, что, по крайней мере, в одном процессе, то есть самом процессе программы rcvmessage, канал FIFO будет открыт для записи. В результате вызов open всегда будет блокироваться то тех пор, пока в канал
FIFO снова не будут записаны данные.
Следующий диалог показывает, как можно использовать эти две программы. Программа rcvmessage выполняется в фоновом режиме для получения сообщений от разных процессов, выполняющих программу sendmessage.
$ rcvmessage &
40
$ sendmessage 'сообщение 1' 'сообщение 2'
Получено сообщение: сообщение 1
Получено сообщение: сообщение 2
$ sendmessage 'сообщение номер 3'
Получено сообщение: сообщение номер 3
Упражнение 7.6. Программы sendmessage и rcvmessage образуют основу простой системы обмена данными. Сообщения, посылаемые программе rcvmessage, могут, например, быть именами файлов, которые нужно обработать. Проблема заключается в том, что текущие каталоги программ sendmessage и rcvmessage могут быть различными, поэтому относительные пути будут восприняты неправильно. Как можно разрешить эту проблему? Можно ли создать, скажем, спулер печати в большой системе, используя только каналы FIFO?
Упражнение 7.7. Если программу rcvmessage нужно сделать настоящей серверной программой, то потребуется гарантия того, что в произвольный момент времени выполняется только одна копия сервера. Существует несколько способов достичь этого. Один из методов состоит в создании файла блокировки. Рассмотрим следующую процедуру:
uses linux;
const
lck = '/tmp/lockfile';
function makelock:integer;
var
fd:integer;
begin
fd := fdopen (lck, Open_RDWR or Open_CREAT or Open_EXCL, octal(0600));
if fd < 0 then
begin
if linuxerror = SYS_EEXIST then
halt (1) (* файл занят другим процессом *)
else
halt (127); (* неизвестная ошибка *)
end;
(* Файл блокировки создан, выход из процедуры *)
fdclose (fd);
makelock:=0;
end;
Эта процедура использует тот факт, что вызов open осуществляется за один шаг. Поэтому, если несколько процессов пытаются выполнить процедуру makelock, одному из них это удастся первым, и он создаст файл блокировки и «заблокирует» работу остальных. Добавьте эту процедуру к программе sendmessage. При этом, если выполнение программы sendmessage завершается при помощи сигнала SIGHUP или SIGTERM, то она должна удалять файл блокировки перед выходом. Как вы думаете, почему мы использовали в процедуре makelock вызов fdopen, а не fdcreat?