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

Пример передачи сообщений: очередь с приоритетами


В этом разделе разработаем простое приложение для передачи сообщений. Его целью является реализация очереди, в которой для каждого элемента может быть задан приоритет. Серверный процесс будет выбирать элементы из очереди и обрабатывать их каким-либо образом. Например, элементы очереди могут быть именами файлов, а серверный процесс может копировать их на принтер. Этот пример аналогичен примеру использования FIFO из раздела 7.2.1.

Отправной точкой будет следующий заголовочный файл q.inc:

(* q.h - заголовок для примера очереди сообщений *)

const

  QKEY:tkey=(1 shl 6) + 5{0105};       (* ключ очереди *)

  QPERM=(6 shl 6) + (6 shl 3){0660};   (* права доступа *)

  MAXOBN=50;                           (* макс. длина имени объекта *)

  MAXPRIOR=10;                         (* максимальный приоритет *)

type

  q_entry=record

    mtype:longint;

    mtext:array [0..MAXOBN] of char;

  end;



  pq_entry=^q_entry;

Определение QKEY задает значение ключа, которое будет обозначать очередь сообщений в системе. Определение QPERM устанавливает связанные с очередью права доступа. Так как код доступа равен octal(0660), то владелец очереди и члены его группы смогут выполнять чтение и запись. Как увидим позже, определения MAXOBN и MAXPRIOR будут налагать ограничения на сообщения, помещаемые в очередь. Последняя часть этого включаемого файла содержит определение структуры q_entry. Структуры этого типа будут использоваться в качестве сообщений, передаваемых и принимаемых следующими процедурами.

Первая рассматриваемая процедура называется enter, она помещает в очередь имя объекта, заканчивающееся нулевым символом, и имеет следующую форму:

{$i q.inc}

(* Процедура enter - поместить объект в очередь *)

function enter (objname:string;priority:longint):boolean;

var

  len, s_qid:longint;

  s_entry:q_entry;     (* структура для хранения сообщений *)

begin

  (* Проверка длины имени и уровня приоритета *)

  len := length (objname);

  if len > MAXOBN then




  begin
    warn ('слишком длинное имя');
    enter:=false;
    exit;
  end;
  if (priority > MAXPRIOR) or (priority < 0) then
  begin
    warn ('недопустимый уровень приоритета');
    enter:=false;
    exit;
  end;
  (* Инициализация очереди сообщений, если это необходимо *)
  s_qid := init_queue;
  if s_qid = -1 then
  begin
    enter:=false;
    exit;
  end;
  (* Инициализация структуры переменной s_entry *)
  s_entry.mtype := priority;
  strlcopy (s_entry.mtext, @objname[1], MAXOBN);
  (* Посылаем сообщение, выполнив ожидание, если это необходимо *)
  if not msgsnd (s_qid, @s_entry, len, 0) then
  begin
    perror ('Ошибка вызова msgsnd');
    enter:=false;
    exit;
  end
  else
    enter:=true;
end;
Первое действие, выполняемое процедурой
enter, заключается в проверке длины имени объекта и уровня приоритета. Обратите внимание на то, что минимальное значение переменной приоритета priority равно 1, так как нулевое значение приведет к неудачному завершению вызова msgsnd. Затем процедура enter «открывает» очередь, вызывая процедуру init_queue, реализацию которой приведем позже.
После завершения этих действий процедура формирует сообщение и пытается послать его при помощи вызова msgsnd. Здесь для хранения сообщения использована структура s_entry типа q_entry, и последний параметр вызова msgsnd равен нулю. Это означает, что система приостановит выполнение текущего процесса, если очередь заполнена (так как не задан флаг IPC_NOWAIT).
Процедура enter сообщает о возникших проблемах при помощи функции warn или библиотечной функции perror. Для простоты функция warn реализована следующим образом:
procedure warn (s:pchar);
begin
  writeln(stderr, 'Предупреждение: ', s);
end;
В реальных системах функция warn должна записывать сообщения в специальный файл протокола.
Назначение функции init_queue очевидно. Она инициализирует идентификатор очереди сообщений или возвращает идентификатор очереди сообщений, который с ней уже связан.


{$i q.inc}
(* Инициализация очереди - получить идентификатор очереди *)
function init_queue:longint;
var
  queue_id:longint;
begin
  (* Попытка создания или открытия очереди сообщений *)
  queue_id := msgget (QKEY, IPC_CREAT or QPERM);
  if queue_id = -1 then
    perror ('Ошибка вызова msgget');
  init_queue:=queue_id;
end;
Следующая процедура, serve, используется серверным процессом для получения сообщений из очереди и противоположна процедуре
enter.
{$i q.inc}
(* Процедура serve - принимает и обрабатывает сообщение обслуживает
 * объект очереди с наивысшим приоритетом
 *)
function serve:integer;
var
  r_qid:longint;
  r_entry:q_entry;
begin
  (* Инициализация очереди сообщений, если это необходимо *)
  r_qid := init_queue;
  if r_qid = -1 then
  begin
    serve:=-1;
    exit;
  end;
  (* Получить и обработать следующее сообщение *)
  while true do
  begin
    if not msgrcv(r_qid, @r_entry, MAXOBN, -1*MAXPRIOR, MSG_NOERROR) then
    begin
      perror ('Ошибка вызова msgrcv');
      serve:=-1;
      exit;
    end
    else
    begin
      (* Обработать имя объекта *)
      proc_obj (@r_entry);
    end;
  end;
end;
Обратите внимание на вызов msgrcv. Так как в качестве параметра типа задано отрицательное значение (-1 * MAXPRIOR), то система вначале проверяет очередь на наличие сообщений со значением
mtype равным 1, затем равным 2 и так далее, до значения MAXPRIOR включительно. Другими словами, сообщения с наименьшим номером будут иметь наивысший приоритет. Процедура proc_obj работает с объектом. Для системы печати она может просто копировать файл на принтер.
Две следующих простых программы демонстрируют взаимодействие этих процедур: программа etest помещает элемент в очередь, а программа stest обрабатывает его (в действительности она всего лишь выводит содержимое и тип сообщения).

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