Реферат: Interprocess Communication
Реферат: Interprocess Communication
Лекция №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; /* тип сообщения */
Страницы: 1, 2, 3, 4, 5
|
|