Interprocess Communication
Страница 3
Понятно, что механизм сообщений может выступать в двух ролях: как средство передача данных, и как средство синхронизации (понятно каким образом).
Итак, к сегодняшнему дню мы познакомились с двумя этими механизмами. Напомню, как только мы переходим к работе от однопроцессной задачи к задаче многопроцессной, у нас сразу же возникают проблемы, связанные с тем, что любой параллелизм накладывает определенную ответственность на программу. Это ответственность по синхронизации доступа к разделяемой памяти, ответственность за правильность подпрограммы, занимающейся приемом и передачей сообщений и т.д. Можно, например, ошибиться в механизме передачи и приема сообщений за счет того, что какой-то процесс будет бесконечно долго ожидать несуществующее сообщение, то, которое никогда в очереди не появится, и система вам никогда такую ошибку не зафиксирует. Т.е. возможны зависания процессов, могут образоваться неосвобожденные ресурсы ("мусор"), и это приводит к деградации системы.
Сейчас мы напишем следующую программу: первый процесс будет читать некоторую текстовую строку из стандартного ввода и в случае, если строка начинается с буквы '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). Если эта сумма больше либо равна нулю, то значение данного семафора устанавливается равным сумме предыдущего значения и кода операции. Если эта сумма меньше нуля, то действие процесса будет приостановлено до наступления одного из следующих событий:
1. Значение суммы (sem_val + sem_op) станет больше либо равно нулю.
2. Пришел какой-то сигнал. (Значение semop в этом случае будет равно -1).
Если код операции semop равен нулю, то процесс будет ожидать обнуления семафора. Если мы обратились к функции semop с нулевым кодом операции, а к этому моменту значение семафора стало равным нулю, то никакого ожидания не происходит.
Рассмотрим третий параметр - флаги. Если третий параметр равен нулю, то это означает, что флаги не используются. Флагов имеется большое количество в т.ч. IPC_NOWAIT (при этом флаге во всех тех случаях, когда мы говорили, что процесс будет ожидать, он не будет ожидать).
Обращаю ваше внимание, что за одно обращение к функции semop можно передать n структур и соответственно выполнить действия с n семафорами из этого массива. Если в этой последовательности будут присутствовать две разных операции с одни семафором, то скорее всего, выполнится последняя.