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

       

Системный вызов 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?

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