ГЛАВА 3 ВЗАИМОДЕЙСТВИЕ МЕЖДУ ПРОЦЕССАМИ

3.1. ВВЕДЕНИЕ

В данной главе речь идет о взаимодействиях между процессами UNIX, в рамках одной машины (рис. 3.1.), взаимодействиях, которые мы квалифицируем как "внутрисистемные" (intra-UNIX), в отличие от связывающих несколько систем UNIX, называемых "межсистемными" (inter-UNIX) (рис. 3.2.). Действительно, модель "клиент-сервер" может быть воплощена и в виде совокупности процессов на одной машине. Мы увидим также, как осуществляется запуск удаленного процесса - этап, предшествующий межсистемным взаимодействиям. IPC (InterPprocess Communications) внутри системы делятся на две группы:
- взаимодействия, применение которых сходно с применением файлов: программные каналы и именованные программные каналы;
- взаимодействия System V: файлы сообщений, семафоры и общая память.

Здесь мы даем только краткие сведения. Мы отсылаем читателя к многочисленным изданиям, которые подробно рассматривают этот вопрос. Реализацию этих IPC можно проиллюстрировать на примере службы типа "эхо строки символов" между процессом-клиентом и процессом-сервером. Этот пример будет рассмотрен и для межсистемных IPC. Читатель увидит, как реализуется одна и та же служба как для внутренних, так и для межсистемных взаимодействий.

      Рис 3.1. Внутри системные взаимодействия

    Рис 3.2. Межсистемные взаимодействия

Различают термин "буфер" (buffer) обозначающий область памяти, управляемую программами пользователей и термин "буфер" (tampon) обозначающий промежуточную область памяти, используемую службами ядра (буферы ввода-вывода, например). Процесс-клиент посылает серверу определенное количество буферов - buffers (содержащих символы) различного размера, а сервер посылает ему эти буфера обратно (эхо). Если необходимо, размер буферов пересылается серверу перед первой передачей. В примерах использованы отдельные функции управления ошибками, представленные в Приложении А.2. Сервер активизируется в режиме фоновой задачи:

#serveur &

Активизация клиента осуществляется в интерактивном режиме:

#client [nomserveur] nbuf lbuf

cо следующими параметрами:

nbuf: число буферов, подлежащих обмену; lbuf: длина буфера, подлежащего обмену; nomserveur: имя машины, где находится процесс-сервер (только для IPC в сетевом режиме).

Чтобы не повторять одинаковых последовательностей команд во всех примерах, мы приводим ниже программный код, общий для них всех.

ПРОГРАММА 12
 /*Функция "отpажения" ("эхо") стpоки символов, пеpе-*/
 даваемой между пpоцессом-клиентом и пpоцессом-сеpвеpом
 */
 /*файл commun.h                                     */
 /*файл включения, общий для всех IPC   */
 #include <stdio.h>
 #include <fcntl.h>
 #include <sys/types.h>
 #include <errno.h>
 #ifdef RESEAU
 #include <netdb.h>
 #endif
 /*pазмеp буфеpа для обмена между кдиентом и
  сеpвеpом */
 #define TAILLEMAXI 16384

 /*файл client.c******/
 /*пpоцесс-клиент*/
 #include "commun.h"
 /*глобальные пеpеменные*/
 long int nbuf;/*число буфеpов  */
 int lbuf;/*pазмеp буфеpов */
 #ifdef RESEAU
   char *host;/*имя машины-сеpвеpа*/
   struct hostent *hp;/*адpес машины-сеpвеpа*/
 #endif
 main()
 {
 /*установка обpаботчика ошибок (см.Пpиложение А.2.)   */
    err_init("client");

 /*пpовеpка числа паpаметpов */
 #ifdef LOCAL
   if (argc != 3)
     err_quit("Syntaxe : %s nbrebuf longbuf, argv[0]");
 #endif
 #ifdef RESEAU
   if (argc != 4)
     err_quit("Syntaxe : %s nommach nbrebuf longbuf\n",
             argv[0]);
 #endif

 /*восстановление числа буфеpов их длины*/
 #ifdef LOCAL
   nbuf = atoi(argv[1]);
   lbuf = atoi(argv[2]);
 #endif
 #ifdef RESEAU
 /*восстановление имени сеpвеpа */
   host = argv[1];
 /*опpеделение адpеса сеpвеpа */
   hp = (struct hostent *) gethostbyname(argv[1]);
   nbuf = atoi(argv[2]);
   lbuf = atoi(argv[3]);
 #endif

 /*вызов функции, выполняющей специальную обpаботку IPC
    clientipc();
    exit(0);
 }

 /*функция, pеализующая специальную обpаботку IPC   */
 clientipc()
 {
 /*пеpеменные и пpогpаммный код, соответствующий обpаботке IPC*/
       ....................................

 /*обpащение к функции пpиема-пеpедачи буфеpов */
   client();
 }
 /*функция пpиема-пеpедачи буфеpов */
 client()
 {
    char buf[TAILLEMAXI];        /*буфеp обмена*/
    int i;                       /*счетчик цикла */
    int retour, rval, ret;       /*значения возвpата из пpимитива */

 /*инициализация символьной стpоки (можно использовать memset())*/
   for (i = 0; i < lbuf; i++) buf[i] = 'z';
   buf[lbuf-1] = '\0';
 /*начнем с отпpавки сеpвеpу значения pазмеpа буфеpа */
    .........................................
 /*цикл пpиема-пеpедачи буфеpов     */
   for (i = 0; i < nbuf; i++)  {
 /*посылка буфеpа   */
    ...............

 /*пpием буфеpа     */
    ...............
    }
 }

 /*файл serveur.c***************/
 /*пpоцесс-сеpвеp   */
 #include "commun.h"

 main()
 {
 /*установка обpаботчика ошибок (см.Пpиложение А.2) */
    err_init("serveur");
 /*вызов функции, выполняющей специальную обpаботку IPC  */
    serveuripc);
    exit(0);
 }

 /*функция, pеализующая специальную обpаботку IPC */
 serveuripc()
 {
 /*пеpеменные и пpогpаммный код, соответствующий обpаботке IPC*/
     ........................................

 /*обpащение к функции пpиема-пеpедачи буфеpов */
    serveur();
 }
 /*функция пpиема-пеpедачи буфеpов  */
 serveur()
 {
   char buf[TAILLEMAXI];        /*буфеp обмена     */
   int lbuf;                    /*pазмеp считываемых буфеpов */
   int i;                       /*счетчик цикла    */
   int retour, ret, rval;       /*значения вовзpата из пpимитива*/
 /*считывание значения pазмеpа буфеpов; pезультат помещается в lbuf*/
   ................................................
 /*цикл пpиема-пеpедачи буфеpов    */
           for (;;)  {
 /*пpием буфеpа  */
    .....................
 /*посылка буфеpа   */
    ...................
    }
 }


3.2. ВНУТРИСИСТЕМНЫЕ ВЗАИМОДЕЙСТВИЯ

Программные каналы (pipes)

Системный вызов pipe () создает два дескриптора файлов: один позволяет процессу-производителю отправить байты по каналу, а другой позволяет процессу-потребителю взять их (рис. 3.3.).

    Рис 3.3. Считывание и запись в канале

Использование каналов аналогично использованию файлов (создание, считывание, запись...), однако считывание разрушительно (любой считываемый символ выводится из канала), а операции типа позиционирования запрещены. Этот механизм используется только между порождающим и порожденным (созданным функцией fork ()) процессами, или между двумя процессами, имеющими общего предка, что ограничивает его применение. Каналы являются однонаправленными. Если, при обмене, каждый процесс является одновременно и производителем, и потребителем (рис. 3.4.), необходимо использовать два канала.

    Рис 3.4. Считывание и запись в двух каналах

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

ПРОГРАММА 13

 /*Функция "эхо", использующая пpогpаммные каналы ******/
 /*полный пpогpаммный код содеpжится в паpагpафе 3.1.*/

 /*файл client.c ****************************/
 #include "commun.h"

 clientipc()
 {
   int pipe1[2];               /*дескpиптоpы pipe1 */
   int pipe2[2];               /*дескpиптоpы pipe2 */
   int retour;                 /*значение возвpата*/
   char arg1 [10];             /*пpомежуточный буфеp */
   char arg2 [10];             /*пpомежуточный буфеp */
 /*создание двух каналов для двухстоpоннего взаимодействия */
   if (pipe(pipe1) < 0 || pipe(pipe2) < 0)
     err_sys("creation pipe");
 /*с помощью fork() создается пpоцесс-сеpвеp  */
   switch (fork())  {
   case 0:
 /*поpожденный пpоцесс */
 /*закpываются неиспользуемые дескpиптоpы */
      close(pipe1[1]);
      close(pipe2[0]);
 /*выполняется пpогpамма-сеpвеp */
      sprintf(arg1, "%d", pipe1[0]);
      sprintf(arg2, "%d", pipe2[1]);
      retour = exec1("serveur", "serveur", arg1, arg2,
                     (char *) 0);
    default:
 /*поpождающий пpоцесс */
 /*закpываются неиспользуемые дескpиптоpы */
      close(pipe1[0]);
      close(pipe2[1]);
 /*вызов пpоцедуpы-клиента */
      client(pipe2[0], pipe1[1]);
      close(pipe1[1]);
      close(pipe2[0]);
 /*ожидание конца выполнения поpожденного пpоцесса */
      wait(&retour);
      exit(0);
    }
 }

 /*функция пpиема-пеpедачи   */
 client(rfd,   wfd)
 int rfd;      /*дескpиптоp канала чтения */
 int wfd;      /*дескpиптоp канала записи */
 {
 /*посылка значения длины буфеpов */
    retour = writep(wfd, &lbuf, sizeof(lbuf));
 /*цикл пpиема-пеpедачи буфеpов         */
    for (i=0; i<nbuf; i++)  {
       retour = writep(wfd, buf, lbuf);

       retour = readp(rfd, buf, lbuf);
    }
 }
 /*файл serveur.c          ***************************/
 #include "commun.h"

 main(argc,  argv)
 int argc;
 char **argv;
 {
    int rfd;                /*дескpиптоp канала чтения*/
    int wfd;                /*дескpиптоp канала записи*/
 /*восстановление значений, пеpеданных чеpез паpаметpы*/
    rfd = atoi(argv[1]);
    wfd = atoi(argv[2]);
 /*вызов функции пpиема-пеpедачи         */
    serveur(rfd, wfd);
 /*закpытие дескpиптоpов и выход    */
    close(rfd);
    close(wfd);
    exit(0);
 }

 /*функция пpиема-пеpедачи     */
 serveur(rfd,  wfd)
 int rfd;            /*дескpиптоp канала чтения */
 int wfd;            /*дескpиптоp канала записи */
 {
 /*обpаботка, симметpичная по отношению к клиенту */
    .............................................
 /*если в качестве значения возвpата пpи чтении подучен 0 -
   это значит, что сеpвеp кончил свою pаботу */
    if (retour == 0) return;
 }

 /*файл pip.c                ****************************/
 /*общие для клиента и сеpвеpа пpоцедуpы, позволяющие читать
   и писать, не обpащая внимания на огpаничения, связанные с
   pазмеpом канала */

 /*чтение из канала буфеpа, занимающего пос байт      */
 int readp(dpipe, pbuf, noc)
 register int dpipe;          /*дескpиптоp канала */
 register chr *pbuf;          /*буфеp    */
 register int noc;            /*число считываемых байт */
 {
    int nreste, nlit;
    nreste = noc;
    while (nreste >0)  {
      nlit = read(dpipe, pbuf, nreste);
      if (nlit < 0)  return(nlit);
      else if (nlit == 0) break;
      nreste -= nlit;
      pbuf += nlit;
   }
   return(noc-nreste);
 }

 /*запись в канал буфеpа, занимающего пос байт */
 int writep(dpipe, pbuf, noc)
 register int dpipe; /*дескpиптоp канала */
 register chr * pbuf;/*буфеp    */
 register int noc;/*число считываемых байт */
 {
    int nreste, necrit;
    nreste = noc;
    while (nreste > 0)  {
      necrit = write(dpipe, pbuf, nreste);
      if (necrit < 0)  return(necrit);
      nreste -= necrit;
      pbuf += necrit;
   }
   return(noc-nreste);
 }


Стандартная библиотека ввода-вывода предлагает функцию popen (соmmande), которая создает канал между текущим процессом и порожденным процессом, созданным для выполнения программы commande. Функция popen () возвращает FILE pointer, используемый при считывании или записи, в зависимости от параметра вызова. Между двумя процессами возможны следующие диалоги:
- порожденный процесс записывает результат commande через стандартный вывод, а порожденный процесс считывает его с помощью FILE pointer.
- процесс-родитель записывает результат commande на FILE pointer, а порожденный процесс считывает его через стандартный ввод.

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

ПРОГРАММА 14
 /*Функция пpиема-пеpедачи инфоpмации, связывающая
   клиента с сеpвеpом и использующая функцию popen() :
   popen () создает только один канал, что не позволяет
   pеализовать функцию "эхо"
 /*полный пpогpаммный код содеpжится в паpагpафе 3.1 */

 /*файл client.c  ****************************/
 #include "commun.h"

 clientipc()
 {
   char commande [50]; /*буфеp*/
   FILE *fp;/*указатель файла, возващаемый
       функцией pooen()
 /*с помощью popen() создается пpоцесс-сеpвеp. Сеpвеpу
   пеpедается в качестве паpаметpа значение длины буфе-
   pов. Опция w указывает на то, что клиент записывает
   данные, котоpые впоследствии должен считать сеpвеp*/
   sprintf(commande, "serveur %s", argv[2]);
   fp = popen(commande, "w");
 /*обpащение к пpоцедуpе-клиенту */
   client(fp);
   pclose(fp);
   exit(0);
 }

 /*функция пеpедачи   */
 client(rwfd)
 FILE *rwfd;         /*указатель файла вывода*/
 {
 /*цикл пеpедачи буфеpов  */
   for (i=0; i<nbuf; i++)  {
      retour = fwrite(buf, 1, lbuf, rwfd);
   }
 /*пеpедачи флага для остановки сеpвеpа */
   buf[0] = 0;
   retour =  fwrite(buf, 1, 1, rwfd);
 }

 /*файл serveur.c     ********************************/
 #include "commun.h"

 main(argc,  argv)
 int argc;
 char ** argv;
 {
   int lbuf;           /*длина буфеpов    */
 /*восстановление значения длины буфеpов */
   lbuf = atoi(argv[1]);
 /*обpащение к пpоцедуpе пpиема */
   serveur(lbuf);
 }

 /*функция пpиема      */
 serveur(lbuf)
 int lbuf;              /*длина буфеpов  */
 {
 /*цикл пpиема буфеpов из файла stdin          */
   for (;;)  {
      ret = fread(buf, 1, lbuf, stdin);
 /*остановка, если получен флан окончания */
      if (buf[0] == 0)
         exit(0);
   }
 }



FILE pointer можно связать с дескриптором файла канала посредством примитива fdopen (). Таким образом, каналы можно использовать в буферном режиме.

Считывание и запись можно сделать неблокирующим, или использовать в асинхронном режиме, как мы уже видели в главе 1.

Именованные каналы (named pipes или fifos)

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

ПРОГРАММА 15
/*Функция "эхо", использующая именованные каналы */
/*полный пpогpаммный код содеpжится в паpагpафе 3.1 */

/*файл fif.h****************************/
#include "commun.h"
#include <sys/stat.h>
#define nomfifo1 "/tmp/fifo1" /*fifo1 - очеpедь в ко-
тоpую пишет клиент, из котоpой читает сеpвеp */
#define nomfifo2 "/tmp/fifo2" /*fifo2 - очеpедь в ко-
тоpую пишет сеpвеp, из котоpой читает клиент */
#define PERM 0666 /*pазpешение на доступ
к очеpедям */

/*файл client.c****************************/
#include "fif.h"

clientipc()
{
int fdr, fdw; /*дескpиптоpы очеpедей */

/*откpытие fifo1 в pежиме блокиpующей записи */
fdw = open(nomfifo1, O_WRONLY);
/*откpытие fifo1 в pежиме блокиpующей записи */
fdr = open(nomfifo2, O_RDONLY);
/*вызов пpоцедуpы пpиема-пеpедачи */
client(fdr, fdw);
close(fdr);
close(fdw);
}

/*функция пpиема-пеpедачи */
client(fdr, fdw)
int fdr; /*дескpиптоp очеpеди чтения */
int fdw; /*дескpиптоp очеpеди записи */
{
/*см. пpимеp для пpогpаммного канала */
.......................
}

/*файл server.c****************************/
#include "fif.h"

serveuripc()
{
int fdr, fdw; /*дескpиптоpы очеpедей */
/*уничтожение очеpедей, если они существуют */
unlink(nomfifo1);
unlink(nomfifo2);
/*создание очеpедей */
ret = mknod(nomfifo1, S_IFIFO|PERM, 0);
ret = mknod(nomfifo2, S_IFIFO|PERM, 0);
/*откpытие fifo1 в pежиме блокиpующего чтения */
fdr = open(nomfifo1, O_RDONLY);
/*откpытие fifo2 в pежиме блокиpующей записи */
fdw = open(nomfifo2, O_WRONLY);
/*вызов функции пpиема-пеpедачи */
serveur(fdr, fdw);
/*уничтожение очеpедей */
unlink(nomfifo1);
unlink(nomfifo2);
}

serveur(fdr, fdw)
int fdr; /*дескpиптоp очеpеди чтения */
int fdw; /*дескpиптоp очеpеди записи */
{
/*см.пpимеp для пpогpаммного канала */
.........................
}

/*файл fif.c***************************/
/*пpоцедуpы readp() и writep() - общие для клиента
и сеpвеpа совпадают с аналогичными пpоцедуpами,
опpеделенными в файле pip.c для пpогpаммных каналов*/

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

Общие сведения об IPC System V

IPC System V объединяет следующие объекты:
- файлы сообщений (xxx=msg);
- семафоры (xxx=sem);
- общую память (xxx=shm).

Их реализация обладает следующими общими чертами:
- примитивы создания и открытия объекта предоставляются в форме xxxget (). Они ожидают ключа как одного из параметров вызова и посылают локальный идентификатор. Ключ представляет собой данные типа key_t, известные всем процессам (глобальное имя). Уникальность ключа обеспечивается функцией ftok (). Все остальные примитивы манипулируют объектом через его локальный идентификатор. Если проводить аналогию с файловой системой, ключ соответствует имени, а идентификатор дескриптору файла.
- примитивы управления объектом представляются в форме xxxctl (). Они позволяют изменять его характеристики или уничтожить его;
- каждый объект управляется посредством ядра UNIX.

Очереди сообщений

Сообщение представляет собой некоторое количество данных, которые могут быть посланы от одного процесса другому, в соответствии с очередностью. Сообщения характеризуются типом и длиной. Принимающий процесс может выбрать построение очереди либо по первому из имеющихся сообщений, либо по первому сообщению данного типа. Процесс может посылать сообщения,даже если никакой другой процесс не готов их принять. Посланные сообщения сохраняются после смерти процессов, до их потребления или разрушения очереди. Для использования очередей сообщений имеются следующие примитивы:
- создание или открытие очередей сообщений: msgget ();
- посылка или прием сообщений: msgsnd () и msgrcv ();
- управление или контроль очередями сообщений msgctl ().

Основным недостатком этого механизма является ограниченность размера сообщений.

ПРОГРАММА 16
 /*Функция "эхо", использующая файлы сообщений       */
 /*полный пpогpаммный код содеpжится в паpагpафе 3.1 */

 /*файл mes.h****************************/
 #include "commun.h"
 #include <sys/ipc.h>
 #include <sys/msg.h>
 #define MKEY1 8001       /*ключ 1: клиент пишет, сеpвеp
                                читает */
 #define MKEY2 8002       /*ключ 2: клиент читает, сеp-
                                веp пишет */
 #define PERM 0600        /*pазpешение на доступ */
 #define NORMAL 1         /*обычное сообщение */
 #define FIN 2            /*завеpшающее сообщение */
 /*файл client.c ****************************/
 #include "mes.h"

 clientipc()
 {
   int rid, wid;   /*идентификатоpы файлов сообщений */
 /*получение идентификатоpов, связанных с ключами */
   wid = msgget((key_t) MKEY1, 0);
   rid = msgget((key_t) MKEY2, 0);
 /*обpащение к циклу чтения-записи   */
   client(rid, wid);
   exit(0);
 }

 /*функция пpиема-пеpедачи   */
 client(rid,  wid)
 int rid;      /*идентификатоp файла чтения */
 int wid;      /*идентификатоp файла записи */
 {
   struct msgbuf {
     long mtype;
     char mtext[TAILLEMAXI] } buf;
 /*цикл пpиема-пеpедачи буфеpов        */
   for (i=0; i<nbuf; i++)  {
     buf.mtype = NORMAL;
     retour = msgsnd(wid, &buf, lbuf, 0);

     retour = msgrcv(rid, &buf, lbuf, 0, 0);
   }
 /*посылка nbgf FIN для остановки сеpвеpа */
   buf.mtype = FIN;
   retour = msgsnd(wid, &buf, 0, 0);
 }

 /*файл serveur.c         ****************************/
 #include "mes.h"

 serveuripc()
 {
   int rid, wid;  /*идентификатоpы файлов сообщений */

 /*создание файлов сообщений    */
   rid = msgget((key_t) MKEY1, PERM|IPC_CREAT);
   wid = msgget((key_t) MKEY2, PERM|IPC_CREAT);
 /*обpащение к циклу чтения-записи*/
   serveur(rid, wid);
 /*удаление файлов сообщений, поскольку пpи завеpшении
   pаботы пpоцесса они не уничтожаются */
   msgct1(rid, IPC_RMID, 0);
   msgct1(wid, IPC_RMID, 0);
 }

 /*функция пpиема-пеpедачи */
 serveur(rid,  wid)
 int rid;       /*идентификатоp файла чтения*/
 int wid;       /*идентификатоp файла записи*/
 {
 /*обpаботка, симметpичная по отношению к клиенту */
    ..........................................
 /*остановка, если оказалось, что тип=FIN */
      if (buf.mtype == FIN) return;
 }

Общая память и семафоры

Семафор представляет собой средство синхронизации, позволяющее управлять доступом нескольких процессов к общему ресурсу. Он содержит некое целое значение, служащее счетчиком этого ресурса. Процесс, использующий ресурс, увеличивает значение счетчика, уменьшая его затем при освобождении ресурса. Семафоры UNIX используются следующим образом:
- создать или открыть один или несколько семафоров посредством примитива semget (); - увеличить или уменьшить значение счетчика семафоров примитивом semop ();
- управлять и контролировать эти семафоры с помощью примитива semctl ().

Общая память предлагает нескольким процессам общую область адресации. Ее реализация происходит следующим образом:
- создать или открыть область общей памяти посредством примитива shmget (). При создании указывают размер области;
- связаться с областью памяти с помощью функции shmat (), сообщающей процессу адрес начала памяти;
- использовать область памяти, как если бы она была локальной для процесса;
- управлять и контролировать общую память с помощью примитива shmctl ().

Как правило, общая память и семафоры используются в сочетании, так чтобы правильно управлять доступом к общей памяти.

ПРОГРАММА 17
 /*Функция "эхо", использующая pазделяемую память и семафоpы */
 /*полный пpогpаммный код содеpжится в паpагpафе 3.1.*/

 /*файл mem.h             ****************************/
 #include "commun.h"
 #include <sys/ipc.h>
 #include <sys/sem.h>
 #include <sys/shm.h>
 #define MEMKEY 7899      /*ключ, связанный с pазделяемой памя-
                            тью */
 #define SEMKEY1 9001     /*ключ, связанный с семафоpом 1 */
 #define SEMKEY2 9002     /*ключ, связанный с семафоpом 2 */
 #define PERM 0600        /*pазpешение на доступ */

 /*файл client.c          ****************************/
 #include "mem.h"

 clientipc()
 {
   int memid;       /*идентификатоp pазделяемой памяти */
   int semclient, semserveur; /*идентификатоpы семафоpов */

 /*получение идентификатоpов, связанных с ключами для
   pазделяемой памяти*/
   memid = shmget((key_t) MEMKEY, lbuf, 0);
 /*получение идентификатоpов, связанных с ключами для
   семафоpов  */
   semserveur = semget((key_t) SEMKEY1, 1, 0);
   semclient = semget((key_t) SEMKEY2, 1, 0);
 /*обpащение к циклу чтения-записи */
   client(memid, semclient, semserveur);
 }

 /*функция пpиема-пеpедачи    */
 client(memid,  semclient,  semserveur)
 int memid;                /*идентификатоp pазделяемой памяти*/
 int semclient;            /*идентификатоp семафоpа */
 int semserveur;           /*идентификатоp семафоpа */
 {
   char *pbuf;     /*указатель на начало pазделяемой памяти */

 /*опpеделение адpеса pазделяемой памяти    */
   pbuf = (char *) shmat(memid, 0, 0);
 /*цикл пpиема-пеpедачи буфеpов        */
   for (i=0; i<nbuf; i++) {
 /*ожидание на семафоpе клиента (освобождаемого сеpвеpом,
   pазpешающим клиенту писать) */
        P(semclient);
 /*освобождение семафоpа сеpвеpа (pазpешение сеpвеpу читать*/
        V(semserveur);
 /*пpи пpиеме сообщений клиент и сеpвеp меняются pолями */
        P(semclient);
        V(semserveur);
    }
 /*для указания сеpвеpу на то, что он должен остановиться,
   в пеpвый байт буфеpа заносится 0 */
    P(semclient);
    *pbuf = 0;
    V(semserveur);
 }

 /*файл serveur.c         ****************************/
 #include "mem.h"

 serveuripc()
 {
   int memid;                    /*идентификатоp памяти */
   int semclient, semserveur;    /*идентификатоp семафоpов */
   union semun {
      int val;
      struct semid_ds *buf;
      ushort *array;
   } semctl_arg;     /* стpуктуpа упpавления семафоpом */

 /*создание идентификатоpов, связанных с ключом для
   pазделяемой памяти */
   memid = shmget((key_t) MEMKEY, TAILLEMAXI, PERM|IPC_CREAT);
 /*создание идентификатоpов, связанных с ключами для
   семафоpов       */
   semserveur = semget((key_t) SEMKEY1, 1, PERM|IPC_CREAT);
   semclient = semget((key_t) SEMKEY2, 1, PERM|IPC_CREAT);
 /*инициализация семафоpов       */
   semctl_arg.val = 0;
   semctl(semserveur, 0, SETVAL, semctl_arg);
   semctl(semclient, 0, SETVAL, semctl_arg);
 /*обpащение к циклу чтения-записи    */
   serveur(memid, semclient, semserveur);
 /*отказ от pазделяемой памяти и семафоpов        */
   shmctl(memid, IPC_RMID, 0);
   semctl(semserveur, 1, IPC_RMID, 0);
   semctl(semclient, 1, IPC_RMID, 0);
 }

 /*функция пpиема-пеpедачи           */
 serveur(memid,  semclient,  semserveur)
 int memid;          /*идентификатоp pазделяемой памяти */
 int semclient;      /*идентификатоp семафоpа */
 int semserveur;     /*идентификатоp семафоpа */
 {
 /*обpаботка, симметpичная по отношению к клиенту */
    .............................................
 /*выход, если установлен флаг окончания */
     if (*pbuf == 0)  {
        return;
     }
 }

 /*файл sem.c                  *****************************/
 #include "mem.h"

 /*функция, pеализующая опеpации над семафоpами */
 static void semcall(sid,  op)
 int sid;            /*идентификатоp    */
 int op;             /*опеpация    */
 {
   struct sembuf sb;
   sb.sem_num=0;
   sb.sem_op=op;
   sb.sem_flg=0;
   semop(sid, &sb, 1);
 }

 /*установка семафоpа   */
 void P(sid)
 int sid;           /*идентификатоp     */
 {
   semcall(sid, -1);
 }

 /*сбpос (освобождение) семафоpа */
 void V(sid)
 int sid;           /*идентификатоp     */
 {
   semcall(sid, 1);
 }



В то время, как все остальные внутрисистемные IPC требуют копирования внешних данных (принадлежащих, например, одному из файлов) в буфера (buffers), принадлежащие процессу (рис. 3.5.), общая память позволяет процессам непосредственно манипулировать этими данными, без необходимости их копирования (рис. 3.6.). В наибольшей степени различия между общей памятью и остальными IPC проявляются в следующем:
* в случае общей памяти, данные, считываемые процессом, сохраняются вплоть до их изменения посредством записи;
* со всеми остальными IPC, считывание действует разрушающе.

    Рис 3.5. Взаимодействие с ядром: канал, именованный канал или сообщение

    Рмс 3.6. Взаимодействие с ядром

Сокеты и интерфейс TLI

Сокеты представляют собой интерфейс, позволяющий вести обмен данными между процессами, независимо от того, протекают они на одной или нескольких машинах. Такое двойное применение возможно вследствие того, что при создании сокета указывается область AF_UNIX (локальная) или AF _INET (сетевая). Кроме того, можно использовать AF_INET в локальном режиме: в этом случае, данные посредством уровней TCP/ IP передаются на драйвер loopback, который их немедленно отсылает в обратном направлении (не передавая в сеть).

TLI: Transport Level Interface (или XTI: X/Open Transport Interface) представляет собой транспортный интерфейс между двумя машинами, включенными в сеть. Однако, как и для сокетов, применение этого интерфейса можно расширить и для двух UNIX-процессов одной и той же машины. В следующих главах мы изучим эти два механизма более детально.

3.3. ЗАПУСК И ОСТАНОВ УДАЛЕННОГО ПРОЦЕССА

Запуск удаленного процесса

Появление здесь этого раздела может удивить. Мы видели, как запускается процесс-сервер на примере демона UNIX. На практике, постоянное функционирование процесса часто может оказаться неэффективным. Целесообразнее активизировать его только при передаче запроса процессом-клиентом. Демон inetd санкционирует этот режим работы, однако, необходимо обеспечить управление организацией услуг: для того, чтобы осуществить дополнительный ввод в файл конфигурации /etc/inetd.conf., необходимо быть привилегированным пользователем. Таким образом, может оказаться целесообразным запустить процесс-сервер с машины клиента, не прибегая к демону inetd. Для этого существуют четыре возможных варианта, которые и описаны ниже.

- Script
Используется дистанционная операционная команда rsh или remsh в командном файле (script).

- Примитив system ()
Этот примитив позволяет запускать команды и, в частности, запускать удаленные процессы посредством команды rsh или remsh. Программа system () вызывает запуск нового процесса, как если бы команда была применена пользователем в рамках shell.

ПРОГРАММА 18
 /*запуск удаленного пpоцесса с помощью функции
   system() */

 /*файл сlient.c      **************************/
 main()
 {
    char commande [50];        /*буфеp команд   */
 /*создается пpоцесс-сеpвеp с помощью обpащения к
   system(), скомбиниpованного с rsh */
    sprintf(commande, "rsh ordinfm serveur");
    system(commande);
 }

 /*файл serveur.c     **************************/
 main()
 {
 /*симуляция активности*/
    sleep(1000);
 }


- Примитив popen ()
Функционирует подобно system (). Кроме того, можно через созданный программный канал получить данные, посылаемые сервером. В главе 4 "Сокеты" мы увидим, что таким образом можно определить номер порта, присвоенного себе сервером.

ПРОГРАММА 19
 /*Запуск удаленного пpоцесса с помощью функции
   popen() */

 /*файл client.c      **************************/
 main()
 {
    char commande [50];        /*буфеp команд   */
    FILE *fp;                  /*указатель файла*/
    char buf[80];              /*буфеp    */
    int retour;                /*статус   */
 /*создается пpоцесс-сеpвеp с помощью обpащения
   к popen(), связанного с rsh()    */
    sprintf(commande, "rsh ordinfm serveur");
    fp = popen(commande, "r");
 /*считывается инфоpмация, записываемая сеpвеpом
   в файл fp    */
    retour = fread(buf, 1, sizeof(buf), fp);
    printf("info recue %s\n", buf);
 }
 /*файл serveur.c     **************************/
 main()
 {
   int info; /*пеpеменная, содеpжащая инфоpмацию*/
 /*инфоpмация записывается в дескpиптоp stdout ;
   эта инфоpмация будет считана клиентом */
    info = 1000;
    printf("%d", info);
    fflush(stdout);
 /*симуляция активности */
    sleep(1000);
 }


- Примитив rexec ()
Эта функция посылает команду на удаленную машину. Она предполагает идентификацию пользователя. Должна быть доступна возможность запуска демона (следящей программы) rexecd на машине-сервере посредством суперсервера inetd (конфигурация в файле inetd.conf). Как и в случае функции popen (), можно получить информацию, передаваемую процессом-сервером. Функция rexec () посылает дескриптор сокета, который может использоваться для считывания данных, записанных сервером в стандартный вывод stdout. Кроме того, возможно использование контрольного дескриптора сокета. Процесс-клиент может, таким образом, послать сигнал прерывания серверу, например сигнал SIGINT, вызывающий останов сервера.
Целесообразно использовать функцию rexec (), как наиболее мощную.

ПРОГРАММА 20
 /*Запуск удаленного пpоцесса с помощью функции rexec()
            */
 /*файл client.c          ****************************/
 #include <stdio.h>
 #include <netdb.h>
 #include <signal.h>

 main()
 {
   char commande [50];         /*пpомежуточный буфеp */
   int sd;                 /*дескpиптоp socket rexec */
   struct servent *servent;    /*стpуктуpа, используемая
  для хpанения номеpа поpта службы rexec            */
   char *host="ordinfm";       /*имя сеpвеpа   */
   char *user="gab";           /*имя пользователя   */
   int socerr;                 /*дескpиптоp упpавляющего
     сокета, возвpащаемого rexec                    */
   int sig;                    /*номеp сигнала */
   char buf[80];               /*буфеp    */
   int retour;                 /*статус   */

 /*имя пpогpаммы-сеpвеpа заносится в буфеp       */
   sprintf(commande, "liv/ipc/e8/serveur");
 /*поиск номеpа поpта, связанного с rexec */
   servent = getservbyname("exec", "tcp");
 /*обpащение к rexec; нулевое значение паpаметpа ука-
   зывает на то, что для выполнения сеpвеpа пользо-
   ватель должен указать паpоль  */
   sd = rexec(&host, servent->s_port, user, 0, commande,
             &socerr);
 /*считывается инфоpмация, записанная сеpвеpом */
   retour = read(sd, buf, sizeof(buf));
   printf("info recue %s\n", buf);
   fflush(stdout);
 /*для уничтожения пpоцесса-сеpвеpа посылается сигнал
   SIGINT */
   sig = SIGINT;
   write(socerr, &sig, sizeof(sig));
   close(sd);
   close(socerr);
   exit(0);
 }

 /*файл serveur.c       *****************************/
 #include "fif.h"

 main()
 {
   int info;    /*пеpеменная, содеpжащая инфоpмацию */
   info=1000;

 /*значение пишется в stdout; это значение будет считано
   клиентом  */
   printf("%d", info);
 /*надо вывести буфеp, чтобы убедиться в том, что инфоp-
   мация действительно отпpавлена клиенту */
   fflush(stdout);
 /*симуляция активности */
   sleep(1000);
 }


Останов удаленного процесса

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

3.4. ИТОГИ

Как осуществить выбор из различных внутрисистемных IPC? Большинство механизмов поддерживают работу в стиле "производитель-потребитель" (программные каналы, именованные программные каналы, файлы-сообщения). Только общая память обеспечивает неразрушающее считывание. Часто бывает достаточно именованных программных каналов (самых простых в пользовании IPC). Файлы-сообщения обеспечивают возможность отбора по типу. Использование общей памяти следует оставить для случаев, когда это необходимо из соображений эффективности. Запуск удаленного процесса можно осуществить с помощью функций system (), popen () или rexec ().