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

Пример работы с разделяемой памятью: программа shmcopy


В этом разделе создадим простую программу shmcopy для демонстрации практического использования разделяемой памяти. Программа shmcopy просто копирует данные со своего стандартного ввода на стандартный вывод, но позволяет избежать лишних простоев в вызовах fdread и fdwrite. При запуске программы shmcopy создаются два процесса, один из которых выполняет чтение, а другой – запись, и которые совместно используют два буфера, реализованных в виде сегментов разделяемой памяти. Когда первый процесс считывает данные в первый буфер, второй записывает содержимое второго буфера, и наоборот. Так как чтение и запись выполняются одновременно, пропускная способность возрастает. Этот подход используется, например, в программах, которые выводят информацию на ленточный накопитель.

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

Программа shmcopy использует следующий заголовочный файл share_ex.inc:

(* Заголовочный файл для примера работы с разделяемой памятью *)

const

  SHMKEY1:tkey=$10; (* ключ разделяемой памяти *)

  SHMKEY2:tkey=$15; (* ключ разделяемой памяти *)

  SEMKEY :tkey=$20; (* ключ семафора *)

  

  (* Размер буфера для чтения и записи *)

  BUFSIZ=8192;

  SIZ=5*BUFSIZ;

  



(* В этой структуре будут находиться данные и счетчик чтения *)

type

  databuf=record

    d_nread:integer;

    d_buf:array [0..SIZ-1] of char;

  end;

  pdatabuf=^databuf;

Напомним, что постоянная BUFSIZ определена в файле stdio и задает оптимальный размер порций данных при работе с файловой системой. Шаблон databuf показывает структуру, которая связывается с каждым сегментом разделяемой памяти. В частности, элемент d_nread позволит процессу, выполняющему чтение, передавать другому, осуществляющему запись, через участок разделяемой памяти число считанных символов.




Следующий файл содержит процедуры для инициализации двух участков разделяемой памяти и набора семафоров. Он также содержит процедуру remobj, которая удаляет различные объекты межпроцессного взаимодействия в конце работы программы. Обратите внимание на способ вызова shmat для подключения участков разделяемой памяти к адресному пространству процесса.
(* Процедуры инициализации *)
{$i share_ex.inc}
const
  IFLAGS=IPC_CREAT or IPC_EXCL;
  ERR:pdatabuf=pdatabuf(-1);
var
  shmid1, shmid2, semid : longint;
procedure getseg (var p1,p2:pdatabuf);
begin
  (* Создать участок разделяемой памяти *)
  shmid1 := shmget (SHMKEY1, sizeof (databuf), octal(0600) or IFLAGS);
  if shmid1 = -1 then
    fatal ('shmget');
  shmid2 := shmget (SHMKEY2, sizeof (databuf), octal(0600) or IFLAGS);
  if shmid2 = -1 then
    fatal ('shmget');
  (* Подключить участки разделяемой памяти. *)
  p1 := pdatabuf( shmat (shmid1, 0, 0));
  if p1 = ERR then
    fatal ('shmat');
  p2 := pdatabuf( shmat (shmid2, 0, 0));
  if p2 = ERR then
    fatal ('shmat');
end;
function getsem:longint;     (* Получить набор семафоров *)
var
  x:tsemun;
begin
  x.val := 0;
  (* Создать два набора семафоров *)
  semid := semget (SEMKEY, 2, octal(0600) or IFLAGS);
  if semid = -1 then
    fatal ('semget');
  (* Задать начальные значения *)
  if semctl (semid, 0, SETVAL, x) = -1 then
    fatal ('semctl');
  if semctl (semid, 1, SETVAL, x) = -1 then
    fatal ('semctl');
  getsem:=semid;
end;
(* Удаляет идентификаторы разделяемой памяти
  * и идентификатор набора семафоров
  *)
procedure remobj;
var
  x:tsemun;
begin
  if not shmctl (shmid1, IPC_RMID, nil) then
    fatal ('shmctl');
  if not shmctl (shmid2, IPC_RMID, nil) then
    fatal ('shmctl');
  if semctl (semid, 0, IPC_RMID, x) = -1 then
    fatal ('semctl');
end;
Ошибки в этих процедурах обрабатываются при помощи процедуры fatal, которая использовалась в предыдущих примерах. Она просто вызывает perror, а затем halt.


Ниже следует главная функция для программы shmcopy. Она вызывает процедуры инициализации, а затем создает процесс для чтения (родительский) и для записи (дочерний). Обратите внимание на то, что именно выполняющий запись процесс вызывает процедуру remobj при завершении программы.
(* Программа shmcopy - главная функция *)
uses ipc,stdio,linux;
{$i share_ex.inc}
var
  pid : longint;
  buf1, buf2 : pdatabuf;
begin
  (* Инициализация набора семафоров. *)
  semid := getsem;
  (* Создать и подключить участки разделяемой памяти. *)
  getseg (buf1, buf2);
  pid := fork;
  case pid of
    -1:
      fatal ('fork');
    0:                (* дочерний процесс *)
    begin
      writer (semid, buf1, buf2);
      remobj;
    end;
    else                (* родительский процесс *)
      reader (semid, buf1, buf2);
  end;
  halt (0);
end.
Программа создает объекты межпроцессного взаимодействия до вызова fork. Обратите внимание на то, что адреса, определяющие сегменты разделяемой памяти (которые находятся в переменных buf1 и buf2), будут заданы в обоих процессах.
Процедура reader принимает данные со стандартного ввода, то есть из дескриптора файла 0, и является первой функцией, представляющей интерес. Ей передается идентификатор набора семафоров в параметре semid и адреса двух участков разделяемой памяти в переменных buf1 и buf2.
{$i share_ex.inc}
(* Определения процедур p() и v() для двух семафоров *)
const
  p1:tsembuf=(sem_num:0;sem_op:-1;sem_flg:0);
  p2:tsembuf=(sem_num:1;sem_op:-1;sem_flg:0);
  v1:tsembuf=(sem_num:0;sem_op:1;sem_flg:0);
  v2:tsembuf=(sem_num:1;sem_op:1;sem_flg:0);
(* Процедура reader - выполняет чтение из файла *)
procedure reader(semid:longint;buf1,buf2:pdatabuf);
begin
  while true do
  begin
    (* Считать в буфер buf1 *)
    buf1^.d_nread := fdread (0, buf1^.d_buf, SIZ);
    (* Точка синхронизации *)
    semop (semid, @v1, 1);
    semop (semid, @p2, 1);


    (* Чтобы процедура writer не была приостановлена. *)
    if buf1^.d_nread <= 0 then
      exit;
    buf2^.d_nread := fdread (0, buf2^.d_buf, SIZ);
    semop (semid, @v2, 1);
    semop (semid, @p1, 1);
    if buf2^.d_nread <= 0 then
      exit;
  end;
end;
Структуры sembuf просто определяют операции р() и v() для набора из
двух семафоров. Но на этот раз они используются не для блокировки критических участков кода, а для синхронизации процедур, выполняющих чтение и запись. Процедура reader использует операцию v2 для сообщения о том, что она завершила чтение и ожидает, вызвав semop с параметром р1, пока процедура writer сообщит о завершении записи. Это станет более очевидным при описании процедуры writer. Возможны другие подходы, включающие или четыре бинарных семафора, или семафоры, имеющие более двух значений.
Последней процедурой, вызываемой программой
shmcopy, является процедура writer:
{$i share_ex.inc}
(* Процедура writer - выполняет запись *)
procedure writer(semid:longint;buf1,buf2:pdatabuf);
begin
  while true do
  begin
    semop (semid, @p1, 1);
    semop (semid, @v2, 1);
    if buf1^.d_nread <= 0 then
      exit;
    fdwrite (1, buf1^.d_buf, buf1^.d_nread);
    semop (semid, @p2, 1);
    semop (semid, @v1, 1);
    if buf2^.d_nread <= 0 then
      exit;
    fdwrite (1, buf2^.d_buf, buf2^.d_nread);
  end;
end;
И снова следует обратить внимание на использование набора семафоров для согласования работы процедур reader и writer. На этот раз процедура writer использует операцию v2
для сигнализации и ждет р1. Важно также отметить, что значения buf1^.d_nread и buf2^.d_nread устанавливаются процессом, выполняющим чтение.
После компиляции можно использовать программу
shmcopy при помощи подобной команды:
$ shmcopy < big > /tmp/big
Упражнение 8.6. Усовершенствуйте обработку ошибок и вывод сообщений в программе shmcopy (в особенности для вызовов fdread и fdwrite). Сделайте так, чтобы программа shmcopy принимала в качестве аргументов имена файлов в форме команды cat. Какие последствия возникнут при прерывании программы shmcopy? Можете ли вы улучшить поведение программы?
Упражнение 8.7. Придумайте систему передачи сообщений, использующую разделяемую память. Измерьте ее производительность и сравните с производительностью стандартных процедур передачи сообщений.

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