Лекция №17

Interprocess Communication

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

1.   Именование;

2.   Синхронизация;

Проблемы именования связаны с тем, что родственных связей нет и по наследству передать ничего нельзя.

Если проблема именования решена, то возникает проблема синхронизации доступа - как организовать обмен с ресурсами, чтобы этот обмен был корректным. Если у нас есть, например, ресурс “оперативная память”, то когда один процесс еще не дописал информацию, а другой процесс уже прочитал весь блок, то возникает некорректная ситуация.

Решения этих проблем мы и будем рассматривать.

Проблема именования решается за счет ассоциирования с каждым ресурсом некоторого ключа. В общем случае это целочисленное значение. То есть при создании разделяемого ресурса его автор приписывает ему номер и определяет права доступа к этому ресурсу. После этого любой процесс, который укажет системе, что он хочет общаться с разделяемым ресурсом с ключом N, и обладает необходимыми правами доступа, будет допущен для работы с этим ресурсом.

Однако такое решение не является идеальным, так как вполне возможна коллизия номеров - когда совпадают номера разделяемых ресурсов. В этом случае процессы будут путаться, что неизбежно приведет к ошибкам. Поэтому в системе предусмотрено стандартное средство генерации уникальных ключей. Для генерации уникального ключа используется функция ftok

#include <sys/types.h>

#include <sys/ipc.h>

key_t ftok(char *s, char c);

Суть ее действия - по текстовой строке и символу генерируется уникальное для каждой такой пары значение ключа. После этого сгенерированным ключом можно пользоваться как для создания ресурса, так и для подтверждения использования ресурса. Более того, для исключения коллизий, рекомендуется указывать в качестве параметра "указателя на строку" путь к некоторому своему файлу. Второй аргумент - символьный, который позволяет создавать некоторые варианты ключа, связанного с этим именем, этот аргумент называется проектом (project). При таком подходе можно добиться отсутствия коллизий.

Давайте посмотрим конкретные средства работы с разделяемыми ресурсами.

Разделяемая память.

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

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

Первая функция - создание общей памяти.

int shmget (key_t key, int size, int shmemflg);

key - ключ разделяемой памяти

size - размер раздела памяти, который должен быть создан

shmemflg - флаги

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

С помощью этой функции можно как создать новый разделяемый ресурс “память” (в этом случае во флагах должен быть указан IPC_CREAT)?, а также можно подключиться к существующему разделяемому ресурсу. Кроме того, в возможных флагах может быть указан флаг IPC_EXECL, он позволяет проверить и подключиться к существующему ресурсу - если ресурс существует, то функция подключает к нему процесс и возвращает код идентификатора, если же ресурс не существует, то функция возвращает -1 и соответствующий код в errno.

Следующая функция - доступ к разделяемой памяти:

char *shmat(int shmid, char *shmaddr, int shmflg);

shmid - идентификатор разделяемого ресурса

shmaddr - адрес, с которого мы хотели бы разместить разделяемую память

При этом, если значение shmaddr - адрес, то память будет подключена, начиная с этого адреса, если его значение - нуль, то система сама подберет адрес начала. Также в качестве значений этого аргумента могут быть некоторые предопределенные константы, которые позволяют организовать, в частности выравнивание адреса по странице или началу сегмента памяти.

shmflg - флаги. Они определяют разные режимы доступа, в частности, есть флаг SHM_RDONLY.

Эта функция возвращает указатель на адрес, начиная с которого будет начинаться запрашиваемая разделяемая память. Если происходит ошибка, то возвращается -1.

Хотелось бы немного поговорить о правах доступа. Они реально могут использоваться и корректно работать не всегда. Так как, если аппаратно не поддерживается закрытие области данных на чтение или на запись, то в этом случае могут возникнуть проблемы с реализацией такого рода флагов. Во-первых, они не будут работать, так как мы получаем указатель и начинаем работать с указателем, как с указателем, и общая схема здесь не предусматривает защиты. Второе, можно программно сделать так, чтобы работали флаги, но тогда мы не сможем указывать произвольный адрес, в этом случае система будет подставлять и возвращать в качестве адрес разделенной памяти некоторые свои адреса, обращение к которым будет создавать заведомо ошибочную ситуацию, возникнет прерывание процесса, во время которого система посмотрит - кто и почему был инициатором некорректного обращения к памяти, и если тот процесс имеет нужные права доступа - система подставит нужные адреса, иначе доступ для процесса будет заблокирован. Это похоже на установку контрольной точки в программе при отладке, когда создавалась заведомо ошибочная ситуация для того, чтобы можно было прервать процесс и оценить его состояние.

Третья функция - открепление разделяемой памяти:

int shmdt(char *shmaddr);

shmaddr - адрес прикрепленной к процессу памяти, который был получен при подключении памяти в начале работы.

Четвертая функция - управление разделяемой памятью:

int shmctl(int shmid, int cmd, struct shmid_ds *buf);

shmid - идентификатор разделяемой памяти

cmd - команда управления.

В частности, могут быть команды: IPC_SET (сменить права доступа и владельца ресурса - для этого надо иметь идентификатор автора данного ресурса или суперпользователя), IPC_STAT (запросить состояние ресурса - в этом случае заполняется информация в структуру, указатель на которую передается третьим параметром, IPC_RMID (уничтожение ресурса - после того, как автор создал процесс - с ним работают процессы, которые подключаются и отключаются, но не уничтожают ресурс, а с помощью данной команды мы уничтожаем ресурс в системе).

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

Передача сообщений.

Следующим средством взаимодействия процессов в системе IPC - это передача сообщений. Ее суть в следующем: в системе имеется так называемая очередь сообщений, в которой каждое сообщение представляет из себя структуру данных, с которой ассоциирован буфер, содержащий тело сообщения и признак, который называется типом сообщения. Очередь сообщений может быть рассмотрена двояко:

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

·    кроме того, так как каждое сообщение имеет тип (на схеме - буква рядом с номером сообщения), то эту очередь можно рассматривать, как суперпозицию очередей, связанную с сообщениями одного типа.

Система IPC позволяет создавать разделяемый ресурс, называемый “очередь сообщений” - таких очередей может быть произвольное количество. По аналогии с разделяемой памятью - мы можем создать очередь, подключиться к ней, послать сообщение, принять сообщение, уничтожить очередь и т.д. Рассмотрим функции работы с очередями сообщений:

Создание очереди сообщений:

int msgget(key_t key, int flags);

 çàâèñèìîñòè îò ôëàãîâ ïðè îáðàùåíèè ê äàííîé ôóíêöèè ëèáî ñîçäàåòñÿ ðàçäåëÿåìûé ðåñóðñ, ëèáî îñóùåñòâëÿåòñÿ ïîäêëþ÷åíèå ê уже ñóùåñòâóþùåìó.

Отправка сообщения:

int msgsnd( int id, struct msgbuf *buf, int size, int flags);

id - идентификатор очереди сообщения;

struct msgbuf {

long type; /* тип сообщения */

char mtext[s] /* указатель на тело сообщения */

}

size - размер сообщения, здесь указывается размер сообщения, размещенного по указателю buf;

flags - флаги, в частности, флагом может быть константа IPC_NOWAIT. При наличии такого флага будут следующие действия - возможна ситуация, когда буфера, предусмотренные системой под очередь сообщений, переполнены. В этом случае возможны два варианта - процесс будет ожидать освобождения пространства, если не указано IPC_NOWAIT, либо функция вернет -1 (с соответствующим кодом в errno), если было указано IPC_NOWAIT.

Прием сообщения:

int msgrcv( int id, struct msgbuf *buf, int size, long type, int flags);

id - идентификатор очереди;

buf - указатель на буфер, куда будет принято сообщение;

size - размер буфера, в котором будет размещено тело сообщения;

type - если тип равен нулю, то будет принято первое сообщение из сквозной очереди, если тип больше нуля, то в этом случае будет принято первое сообщение из очереди сообщений, связанной с типом, равным значению этого параметра.

flags - флаги, в частности, IPC_NOWAIT, он обеспечит работу запроса без ожидания прихода сообщения, если такого сообщения в момент обращения функции к ресурсу не было, иначе процесс будет ждать.

Управление очередью:

int msgctl( int id, int cmd, struct msgid_dl *buf);

id - идентификатор очереди;

cmd - команда управления, для нас интерес представляет IPC_RMID, которая уничтожит ресурс.

buf - этот параметр будет оставлен без комментария.

Мы описали два средства взаимодействия между процессами. Что же мы увидели? Понятно, что названия и описания интерфейсов мало понятны. Прежде всего следует заметить то, что как только мы переходим к вопросу взаимодействия процессов, у нас возникает проблема синхронизации. И здесь мы уже видим проблемы, связанные с тем, что после того, как мы поработали с разделяемой памятью или очередью сообщений, в системе может оставаться “хлам”, например, процессы, которые ожидают сообщений, которые в свою очередь не были посланы. Так, если мы обратились к функции получения сообщений с типом, которое вообще не пришло, и если не стоит ключ IPC_NOWAIT, то процесс будет ждать его появления, пока не исчезнет ресурс. Или мы можем забыть уничтожить ресурс (и система никого не поправит) - этот ресурс останется в виде загрязняющего элемента системы.

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

И это главное - не язык интерфейсов, а проблемы, которые могут возникнуть при взаимодействии параллельных процессов.


Лекция №18

К сегодняшнему дню мы разобрали два механизма взаимодействия процессов в системе IPC - это механизм общей (или разделяемой) памяти и механизм сообщений. Мы с вами выяснили, что одной из основных проблем, возникающей при взаимодействии процессов, является проблема синхронизации. Ярким примером механизма, для которого эта проблема является наиболее острой, является механизм взаимодействия процессов с использованием разделяемой памяти.

Вы помните, что механизм разделяемой памяти позволяет создавать объект, который становится доступным всем процессам, подтвердившим ключ доступа к этому объекту, а также имеют соответствующие права. После этого общая память становится, с точки зрения каждого из этих процессов, как бы фрагментом адресного пространства каждого из них, к которому этот процесс может добираться через указатель этого адресного пространства. С другой стороны нет никаких средств, которые позволили бы синхронизовать чтение и запись в эту область данных. Так как в эту область данных одновременно имеет доступ произвольное количество процессов, то проблема синхронизации здесь имеет место быть.

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

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

Порядок чтения и записи сообщений из очереди соответствует названию этой структуры - очередь. Кроме того, за счет того, что каждое сообщение типизировано, есть возможность рассмотрения этой очереди с нескольких точек зрения. Первая точка зрения - это одна очередь и порядок в ней хронологический. Вторая точка зрения - это возможность представление этой очереди в виде нескольких очередей, каждая из которых содержит элементы определенного типа.

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

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

Сейчас мы напишем следующую программу: первый процесс будет читать некоторую текстовую строку из стандартного ввода и в случае, если строка начинается с буквы 'a', то эта строка в качестве сообщения будет передана процессу А, если 'b' - процессу В, если 'q' - то процессам А и В и затем будет осуществлен выход. Процессы А и В распечатывают полученные строки на стандартный вывод.

Основной процесс

#include <sys/types.h>

#include <sys/ipc.h>

#include <sys/message.h>

#include <stdio.h>

struct { long mtype; /* тип сообщения */

char Data[256]; /* сообщение */

} Message;

int main()

{ key_t key; int msgid; char str[256];

 key=ftok("/usr/mash",'s'); /*получаем уникальный ключ, однозначно определяющий доступ к ресурсу данного типа */

 msgid=msgget(key, 0666 | IPC_CREAT);  /*создаем очередь сообщений , 0666 определяет права доступа */

 for(;;) { /* запускаем вечный цикл */

gets(str); /* читаем из стандартного ввода строку */

strcpy(Message.Data, str); /* и копируем ее в буфер сообщения */

switch(str[0]){

case 'a':

case 'A': Message.mtype=1; /* устанавливаем тип и посылаем сообщение в очередь*/

msgsnd(msgid, (struct msgbuf*) (&Message), strlen(str)+1, 0);

break;

case 'b':

case 'B': Message.mtype=2;

msgsnd(msgid, (struct msgbuf*) (&Message), strlen(str)+1, 0);

break;

case q':

case 'Q': Message.mtype=1;

msgsnd(msgid, (struct msgbuf*) (&Message), strlen(str)+1, 0);

Message.mtype=2;

msgsnd(msgid, (struct msgbuf*) (&Message), strlen(str)+1, 0);

sleep(10); /* ждем получения сообщений процессами А и В */

msgctl(msgid, IPC_RMID, NULL); /* уничтожаем очередь*/

exit(0);

default: break;

}

 }

}

Процесс-приемник А  /* процесс В аналогичен с точностью до четвертого параметра в msgrcv */

#include <sys/types.h>

#include <sys/ipc.h>

#include <sys/message.h>

#include <stdio.h>

struct { long mtype;

char Data[256];

} Message;

int main()

{ key_t key; int msgid;

 key=ftok("/usr/mash",'s'); /* получаем ключ по тем же параметрам */

 msgid=msgget(key, 0666 | IPC_CREAT);  /*создаем очередь сообщений */

 for(;;) { /* запускаем вечный цикл */

msgrcv(msgid, (struct msgbuf*) (&Message), 256, 1, 0); /* читаем сообщение с типом 1*/

if (Message.Data[0]='q' || Message.Data[0]='Q') break;

printf("%s",Message.Data);

 }

 exit(0);

}

Семафоры

С точки зрения тех проблем, с которыми мы знакомимся, семафоры - это есть вполне законное и существующее понятие. Впервые ввел это понятие достаточно известный ученый Дейкстра. Семафор - это некоторый объект, который имеет целочисленное значение S, и с которым связаны две операции: V(S) и P(S).

Операция P(S) уменьшает значение семафора на 1, и если S³0 процесс продолжает работу. Если S<0, то процесс будет приостановлен и встанет в очередь ожидания, связанную с семафором S, до тех пор, пока его не освободит другой процесс.

Операция V(S) увеличивает семафор на 1. Если S>0, то процесс продолжает выполнение. Если S£0, то разблокируется один из процессов, ожидающий в очереди процессов, связанной с семафором S, и текущий процесс продолжит выполнение.

Считается, что операции P(S) и V(S) неделимы. Это означает, что выполнение этих операций не может прерваться до их завершения. Т.е. если семафор реализован в системе, то это должна быть одна команда.

Частным случаем продекларированного семафора является двоичный семафор, максимальное значение которого равно единичке. При этом значение S может быть равно 1, это означает, что ни один из процессов (связанных с этим семафором) не находится в критическом участке. При S=0 один из процессов находится в критическом участке {вот-вот попадет в очередь}, а другой нормально функционирует. При S= -1 один из процессов находится в критическом участке, а другой заблокирован и находится в очереди.

На самом деле двоичные семафоры наиболее часто находили применение в аппаратных реализациях, например, в многомашинных комплексах с общей оперативной памятью.

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

Давайте рассмотрим средства, предоставляемые системой IPC, для создания, управления и взаимодействия с семафорами.

int semget(key_t key, int n, int flags);

int semop(int semid, struct sembuf * sops, int n);

struct sembuf{

 short sem_num; /* номер семафора в массиве семафоров */

 short sem_op; /* код операции, которую надо выполнить */

 short sem_flg; /* флаги */

 }

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

Первый параметр функции semop - идентификатор семафора, второй - указатель на структуру sembuf и третий параметр - количество указателей на эту структуру, которые передаются функцией semop. В структуре содержится вся информация о необходимом действии.

Поле операции интерпретируется следующим образом. Пусть значение семафора с номером sem_num равно sem_val. В этом случае, если значение операции не равно нулю, то оценивается значение суммы (sem_val + sem_op). Если эта сумма больше либо равна нулю, то значение данного семафора устанавливается равным сумме предыдущего значения и кода операции. Если эта сумма меньше нуля, то действие процесса будет приостановлено до наступления одного из следующих событий:


Информация о работе «Interprocess Communication»
Раздел: Информатика, программирование
Количество знаков с пробелами: 88065
Количество таблиц: 11
Количество изображений: 4

Похожие работы

Скачать
232852
0
0

... с приглашением по запросу (в машинной графике)required parameter обязательный параметрrequired space обязательный пробел (в системах подготовки текстов)requirements specification 1. техническое задание 2. описание требований к программному средствуrerun перезапуск, повторный запускreschedule переупорядочивать очередь (о диспетчере операционной системы)reschedule interval период переупорядочения ...

Скачать
225728
6
0

... ориентированы на 32 разрядные шинные архитектуры компьютеров с процессорами 80386, 80486 или Pentium. Фирма Novell также подготовила варианты сетевой ОС NetWare, предназначенные для работы под управлением многозадачных, многопользовательских операционных систем OS/2 и UNIX. Версию 3.12 ОС NetWare можно приобрести для 20, 100 или 250 пользователей, а версия 4.0 имеет возможность поддержки до 1000 ...

Скачать
38984
0
0

... было очень своевременным. Оно ознаменовало рождение операционной системы, распространяемой с открытыми исходными кодами. Р. Столлман, конечно, прав, когда настаивает на том, что операционная система Linux должна называться GNU/Linux. Но так уж сложилось, что название ядра стало служить названием всей операционной системы, и мы в этой книге будем поступать так же. 3 Основные характеристики ОС ...

Скачать
63665
1
3

... при создании и отличают ее до сих пор. Более того, несмотря на старания господина Гейтса, ситуация такова, что он повторяет те программные интерфейсы, которые используются для взаимодействия управления процессами, а не фирмы разработчики UNIX-ов повторяют те интерфейсы, которые появились в Windows. Первенство операционной системы UNIX очевидно. Мы говорили о том, что процесс в UNIX-е - это есть ...

0 комментариев


Наверх