Текст книги "Операционная система UNIX"
Автор книги: Андрей Робачевский
Жанр:
ОС и Сети
сообщить о нарушении
Текущая страница: 21 (всего у книги 39 страниц)
Как уже обсуждалось, очереди сообщений являются составной частью UNIX System V, они обслуживаются операционной системой, размещаются в адресном пространстве ядра и являются разделяемым системным ресурсом. Каждая очередь сообщений имеет свой уникальный идентификатор. Процессы могут записывать и считывать сообщения из различных очередей. Процесс, пославший сообщение в очередь, может не ожидать чтения этого сообщения каким-либо другим процессом. Он может закончить свое выполнение, оставив в очереди сообщение, которое будет прочитано другим процессом позже.
Данная возможность позволяет процессам обмениваться структурированными данными, имеющими следующие атрибуты:
□ Тип сообщения (позволяет мультиплексировать сообщения в одной очереди)
□ Длина данных сообщения в байтах (может быть нулевой)
□ Собственно данные (если длина ненулевая, могут быть структурированными)
Очередь сообщений хранится в виде внутреннего однонаправленного связанного списка в адресном пространстве ядра. Для каждой очереди ядро создает заголовок очереди (msqid_ds
), где содержится информация о правах доступа к очереди (msg_perm
), ее текущем состоянии (msg_cbytes
– число байтов и msg_qnum
– число сообщений в очереди), а также указатели на первое (msg_first
) и последнее (msg_last
) сообщения, хранящиеся в виде связанного списка (рис. 3.18). Каждый элемент этого списка является отдельным сообщением.
Рис. 3.18. Структура очереди сообщений
Для создания новой очереди сообщений или для доступа к существующей используется системный вызов msgget(2):
#include
#include
#include
int msgget(key_t key, int msgflag);
Функция возвращает дескриптор объекта-очереди, либо -1 в случае ошибки. Подобно файловому дескриптору, этот идентификатор используется процессом для работы с очередью сообщений. В частности, процесс может:
□ Помещать в очередь сообщения с помощью функции msgsnd(2);
□ Получать сообщения определенного типа из очереди с помощью функции msgrcv(2);
□ Управлять сообщениями с помощью функции msgctl(2).
Перечисленные системные вызовы манипулирования сообщениями имеют следующий вид:
#include
#include
#include
int msgsnd(int msqid, const void *msgp,
size_t msgsz, int msgflg);
int msgrcv(int msqid, void *msgp,
size_t msgsz, long msgtyp, int msgflg);
Здесь msgid
является дескриптором объекта, полученного в результате вызова msgget(2). Параметр msgtyp
указывает на буфер, содержащий тип сообщения и его данные, размер которого равен msgsz
байт. Буфер имеет следующие поля:
long msgtype | тип сообщения |
char msgtext[] | данные сообщения |
Аргумент msgtyp
указывает на тип сообщения и используется для их выборочного получения. Если msgtyp
равен 0, функция msgrcv(2) получит первое сообщение из очереди. Если величина msgtyp
выше 0, будет получено первое сообщение указанного типа. Если msgtyp
меньше 0, функция msgrcv(2) получит сообщение с минимальным значением типа, меньше или равного абсолютному значению msgtyp
.
Очереди сообщений обладают весьма полезным свойством – в одной очереди можно мультиплексировать сообщения от различных процессов. Для демультиплексирования используется атрибут msgtype
, на основании которого любой процесс может фильтровать сообщения с помощью функции msgrcv(2) как это было показано выше.
Рассмотрим типичную ситуацию взаимодействия процессов, когда серверный процесс обменивается данными с несколькими клиентами. Свойство мультиплексирования позволяет использовать для такого обмена одну очередь сообщений. Для этого сообщениям, направляемым от любого из клиентов серверу, будем присваивать значение типа, скажем, равным 1. Если в теле сообщения клиент каким-либо образом идентифицирует себя (например, передает свой PID), то сервер сможет передать сообщение конкретному клиенту, присваивая тип сообщения равным этому идентификатору.
Поскольку функция msgrcv(2) позволяет принимать сообщения определенного типа (типов), сервер будет принимать сообщения с типом 1, а клиенты – сообщения с типами, равными идентификаторам их процессов. Схема такого взаимодействия представлена на рис. 3.19.
Рис. 3.19. Мультиплексирование сообщений в одной очереди
Атрибут msgtype
также можно использовать для изменения порядка извлечения сообщений из очереди. Стандартный порядок получения сообщений аналогичен принципу FIFO – сообщения получаются в порядке их записи. Однако используя тип, например, для назначения приоритета сообщений, этот порядок легко изменить.
Пример приложения "Здравствуй, Мир!", использующего сообщения:
Файл описания mesg.h
#define MAXBUFF 80
#define PERM 0666
/* Определим структуру нашего сообщения. Она может отличаться
от структуры msgbuf, но должна содержать поле mtype. В данном
случае структура сообщения состоит из буфера обмена */
typedef struct our msgbuf {
long mtype;
char buff[MAXBUFF];
} Message;
Сервер:
#include
#include
#include «mesg.h»
main() {
/* Структура нашего сообщения (может отличаться от
структуры msgbuf) */
Message message;
key_t key;
int msgid, length, n;
/* Получим ключ */
if ((key = ftok(«server», 'A')) < 0) {
printf(«Невозможно получить ключn»);
exit(1);
}
/* Тип принимаемых сообщений */
message.mt_type = 1L;
/* Создадим очередь сообщений */
if ((msgid = msgget(key, РЕRМ | IPC_CREAT)) < 0) {
printf(«Невозможно создать очередьn»);
exit(1);
}
/* Прочитаем сообщение */
n =
msgrcv(msgid, &message, sizeof(message), message.mtype, 0);
/* Если сообщение поступило, выведем его содержимое
на терминал */
if (n > 0) {
if (write(1, message.buff, n) != n) {
printf(«Ошибка выводаn»);
exit(1);
}
} else {
printf(«Ошибка чтения сообщенияn»);
exit(1);
}
/* Удалить очередь поручим клиенту */
exit(0);
}
Клиент:
#include
#include
#include
#include «mesg.h»
main {
/* Структура нашего сообщения (может отличаться от
структуры msgbuf */
Message message;
key_t key;
int msgid, length;
/* Тип посылаемого сообщения, может использоваться для
мультиплексирования */
message.mtype = 1L;
/* Получим ключ */
if ((key = ftok(«server», 'A')) < 0) {
printf(«Невозможно получить ключn»);
exit(1);
}
/* Получим доступ к очереди сообщений, очередь уже
должна быть создана сервером */
if ((msgid = msgget(key, 0)) < 0) {
printf(«Невозможно получить доступ к очередиn»);
exit(1);
}
/* Поместим строку в сообщение */
if ((length = sprintf(message.buff,
«Здравствуй, Мир!n»)) < 0) {
printf(«Ошибка копирования в буферn»);
exit(1);
} /* Передадим сообщение */
if (msgsnd(msgid, (void*)&message, length, 0) != 0) {
printf(«Ошибка записи сообщения в очередьn»);
exit(1);
}
/* Удалим очередь сообщений */
if (msgctl(msgid, IPC_RMID, 0) < 0) {
printf(«Ошибка удаления очередиn»);
exit(1);
}
exit(0);
}
Для синхронизации процессов, а точнее, для синхронизации доступа нескольких процессов к разделяемым ресурсам, используются семафоры. Являясь одной из форм IPC, семафоры не предназначены для обмена большими объемами данных, как в случае FIFO или очередей сообщений. Вместо этого, они выполняют функцию, полностью соответствующую своему названию – разрешать или запрещать процессу использование того или иного разделяемого ресурса.
Применение семафоров поясним на простом примере. Допустим, имеется некий разделяемый ресурс (например, файл). Необходимо блокировать доступ к ресурсу для других процессов, когда некий процесс производит операцию над ресурсом (например, записывает в файл). Для этого свяжем с данным ресурсом некую целочисленную величину – счетчик, доступный для всех процессов. Примем, что значение 1 счетчика означает доступность ресурса, 0 – его недоступность. Тогда перед началом работы с ресурсом процесс должен проверить значение счетчика. Если оно равно 0 – ресурс занят и операция недопустима – процессу остается ждать. Если значение счетчика равно 1 – можно работать с ресурсом. Для этого, прежде всего, необходимо заблокировать ресурс, т. е. изменить значение счетчика на 0. После выполнения операции для освобождения ресурса значение счетчика необходимо изменить на 1. В приведенном примере счетчик играет роль семафора.
Для нормальной работы необходимо обеспечить выполнение следующих условий:
1. Значение семафора должно быть доступно различным процессам. Поэтому семафор находится не в адресном пространстве процесса, а в адресном пространстве ядра.
2. Операция проверки и изменения значения семафора должна быть реализована в виде одной атомарной по отношению к другим процессам (т. е. непрерываемой другими процессами) операции. В противном случае возможна ситуация, когда после проверки значения семафора выполнение процесса будет прервано другим процессом, который в свою очередь проверит семафор и изменит его значение. Единственным способом гарантировать атомарность критических участков операций является выполнение этих операций в режиме ядра (см. режимы выполнения процесса).
Таким образом семафоры являются системным ресурсом, действия над которым производятся через интерфейс системных вызовов.
Семафоры в System V обладают следующими характеристиками:
□ Семафор представляет собой не один счетчик, а группу, состоящую из нескольких счетчиков, объединенных общими признаками (например, дескриптором объекта, правами доступа и т.д.).
□ Каждое из этих чисел может принимать любое неотрицательное значение в пределах, определенных системой (а не только значения 0 и 1).
Для каждой группы семафоров (в дальнейшем мы будем называть группу просто семафором) ядро поддерживает структуру данных semid_ds
, включающую следующие поля:
struct ipc_perm sem_perm | Описание прав доступа |
struct sem *sem_base | Указатель на первый элемент массива семафоров |
ushort sem_nsems | Число семафоров в группе |
time_t sem_otime | Время последней операции |
time_t sem_ctime | Время последнего изменения |
Значение конкретного семафора из набора хранится во внутренней структуре sem
:
ushort semval | Значение семафора |
pid_t sempid | Идентификатор процесса, выполнившего последнюю операцию над семафором |
ushort semncnt | Число процессов, ожидающих увеличения значения семафора |
ushort semzcnt | Число процессов, ожидающих обнуления семафора |
Помимо собственно значения семафора, в структуре sem хранится идентификатор процесса, вызвавшего последнюю операцию над семафором, число процессов, ожидающих увеличения значения семафора, и число процессов, ожидающих, когда значение семафора станет равным нулю. Эта информация позволяет ядру производить операции над семафорами, которые мы обсудим несколько позже.
Для получения доступа к семафору (и для его создания, если он не существует) используется системный вызов semop(2):
#include
#include
#include
int semget(key_t key, int nsems, int semflag);
В случае успешного завершения операции функция возвращает дескриптор объекта, в случае неудачи – -1. Аргумент nsems
задает число семафоров в группе. В случае, когда мы не создаем, а лишь получаем доступ к существующему семафору, этот аргумент игнорируется. Аргумент semflag
определяет права доступа к семафору и флажки для его создания (IPC_CREAT
, IPC_EXCL
).
После получения дескриптора объекта процесс может производить операции над семафором, подобно тому, как после получения файлового дескриптора процесс может читать и записывать данные в файл. Для этого используется системный вызов semop(2):
#include
#include
#include
int semop(int semid, struct sembuf *semop, size_t nops);
В качестве второго аргумента функции передается указатель на структуру данных, определяющую операции, которые требуется произвести над семафором с дескриптором semid
. Операций может быть несколько, и их число указывается в последнем аргументе nops
. Важно, что ядро обеспечивает атомарность выполнения критических участков операций (например, проверка значения – изменение значения) по отношению к другим процессам.
Каждый элемент набора операций semop
имеет вид:
struct sembuf {
short sem_num; /* номер семафора в группе */
short sem_op; /* операция */
short sem_flg; /* флаги операции */
}
UNIX допускает три возможные операции над семафором, определяемые полем semop
:
1. Если величина semop
положительна, то текущее значение семафора увеличивается на эту величину.
2. Если значение semop
равно нулю, процесс ожидает, пока семафор не обнулится.
3. Если величина semop
отрицательна, процесс ожидает, пока значение семафора не станет большим или равным абсолютной величине semop
. Затем абсолютная величина semop вычитается из значения семафора.
Можно заметить, что первая операция изменяет значение семафора (безусловное выполнение), вторая операция только проверяет его значение (условное выполнение), а третья – проверяет, а затем изменяет значение семафора (условное выполнение).
При работе с семафорами взаимодействующие процессы должны договориться об их использовании и кооперативно проводить операции над семафорами. Операционная система не накладывает ограничений на использование семафоров. В частности, процессы вольны решать, какое значение семафора является разрешающим, на какую величину изменяется значение семафора и т.п.
Таким образом, при работе с семафорами процессы используют различные комбинации из трех операций, определенных системой, по-своему трактуя значения семафоров.
В качестве примера рассмотрим два случая использования бинарного семафора (т.е. значения которого могут принимать только 0 и 1). В первом примере значение 0 является разрешающим, а 1 запирает некоторый разделяемый ресурс (файл, разделяемая память и т.п.), ассоциированный с семафором. Определим операции, запирающие ресурс и освобождающие его:
static struct sembuf sop_lock[2] = {
0, 0, 0, /* ожидать обнуления семафора */
0, 1, 0 /* затем увеличить значение семафора на 1 */
};
static struct sembuf sop_unlock[1] = {
0,-1, 0 /* обнулить значение семафора */
};
Итак, для запирания ресурса процесс производит вызов:
semop(semid, &sop_lock[0], 2);
обеспечивающий атомарное выполнение двух операций:[43]43
Ядро обеспечивает атомарное выполнение не всего набора операций в целом, а лишь критических участков. Так, например, в процессе ожидания освобождения ресурса (ожидание нулевого значения семафора) выполнение процесса будет (и должно быть) прервано процессом, который освободит ресурс (т.е. установит значение семафора равным 1). Ожидание семафора соответствует состоянию «сна» процесса, допускающим выполнение других процессов в системе. В противном случае, процесс, ожидающий ресурс, остался бы заблокированным навсегда.
[Закрыть]
1. Ожидание доступности ресурса. В случае, если ресурс уже занят (значение семафора равно 1), выполнение процесса будет приостановлено до освобождения ресурса (значение семафора равно 0).
2. Запирание ресурса. Значение семафора устанавливается равным 1. Для освобождения ресурса процесс должен произвести вызов:
semop(semid, &sop_unlock[0], 1);
который уменьшит текущее значение семафора (равное 1) на 1, и оно станет равным 0, что соответствует освобождению ресурса. Если какой-либо из процессов ожидает ресурса (т. е. произвел вызов операции sop_lock
), он будет «разбужен» системой, и сможет в свою очередь запереть ресурс и работать с ним.
Во втором примере изменим трактовку значений семафора: значению 1 семафора соответствует доступность некоторого ассоциированного с семафором ресурса, а нулевому значению – его недоступность. В этом случае содержание операций несколько изменится.
static struct sembuf sop_lock[2] = {
0, -1, 0, /* ожидать разрешающего сигнала (1),
затем обнулить семафор */
};
static struct sembuf sop_unlock[1] = {
0, 1, 0 /* увеличить значение семафора на 1 */
};
Процесс запирает ресурс вызовом:
semop(semid, &sop_lock[0], 1);
а освобождает:
semop(semid, &sop_unlock[0], 1);
Во втором случае операции получились проще (по крайней мере их код стал компактнее), однако этот подход имеет потенциальную опасность: при создании семафора, его значения устанавливаются равными 0, и во втором случае он сразу же запирает ресурс. Для преодоления данной ситуации процесс, первым создавший семафор, должен вызвать операцию sop_unlock
, однако в этом случае процесс инициализации семафора перестанет быть атомарным и может быть прерван другим процессом, который, в свою очередь, изменит значение семафора. В итоге, значение семафора станет равным 2, что повредит нормальной работе с разделяемым ресурсом.
Можно предложить следующее решение данной проблемы:
/* Создаем семафор, если он уже существует semget
возвращает ошибку, поскольку указан флаг IPC_EXCL */
if ((semid = semget(key, nsems, perms | IPC_CREAT | IPC_EXCL)) < 0) {
if (errno = EEXIST) {
/* Действительно, ошибка вызвана существованием объекта */
if ((semid = semget(key, nsems, perms)) < 0)
return(-1); /* Возможно, не хватает системных ресурсов */
} else
return(-1); /* Возможно, не хватает системных ресурсов * /
}
/* Если семафор создан нами, проинициализируем его */
else
semop(semid, &sop_unlock[0], 1);
Интенсивный обмен данными между процессами с использованием рассмотренных механизмов межпроцессного взаимодействия (каналы, FIFO, очереди сообщений) может вызвать падение производительности системы. Это, в первую очередь, связано с тем, что данные, передаваемые с помощью этих объектов, копируются из буфера передающего процесса в буфер ядра и затем в буфер принимающего процесса. Механизм разделяемой памяти позволяет избавиться от накладных расходов передачи данных через ядро, предоставляя двум или более процессам возможность непосредственного получения доступа к одной области памяти для обмена данными.
Безусловно, процессы должны предварительно "договориться" о правилах использования разделяемой памяти. Например, пока один из процессов производит запись данных в разделяемую память, другие процессы должны воздержаться от работы с ней. К счастью, задача кооперативного использования разделяемой памяти, заключающаяся в синхронизации выполнения процессов, легко решается с помощью семафоров.
Примерный сценарий работы с разделяемой памятью выглядит следующим образом:
1. Сервер получает доступ к разделяемой памяти, используя семафор.
2. Сервер производит запись данных в разделяемую память.
3. После завершения записи сервер освобождает разделяемую память с помощью семафора.
4. Клиент получает доступ к разделяемой памяти, запирая ресурс с помощью семафора.
5. Клиент производит чтение данных из разделяемой памяти и освобождает ее, используя семафор.
Для каждой области разделяемой памяти, ядро поддерживает структуру данных shmid_ds
, основными полями которой являются:
struct ipc_perm shm_perm | Права доступа, владельца и создателя области (см. описание ipc_perm выше) |
int shm_segsz | Размер выделяемой памяти |
ushort shm_nattch | Число процессов, использующих разделяемую память |
time_t shm_atime | Время последнего присоединения к разделяемой памяти |
time_t shm_dtime | Время последнего отключения от разделяемой памяти |
time_t shm_ctime | Время последнего изменения |
Для создания или для доступа к уже существующей разделяемой памяти используется системный вызов shmget(2):
#include
#include
#include
int shmget(key_t key, int size, int shmflag);
Функция возвращает дескриптор разделяемой памяти в случае успеха, и -1 в случае неудачи. Аргумент size
определяет размер создаваемой области памяти в байтах. Значения аргумента shmflag
задают права доступа к объекту и специальные флаги IPC_CREAT
и IPC_EXCL
. Заметим, что вызов shmget(2) лишь создает или обеспечивает доступ к разделяемой памяти, но не позволяет работать с ней. Для работы с разделяемой памятью (чтение и запись) необходимо сначала присоединить (attach) область вызовом shmat(2):
#include
#include
#include
char *shmat(int shmid, char *shmaddr, int shmflag);
Вызов shmat(2) возвращает адрес начала области в адресном пространстве процесса размером size
, заданным предшествующем вызовом shmget(2). В этом адресном пространстве взаимодействующие процессы могут размещать требуемые структуры данных для обмена информацией. Правила получения этого адреса следующие:
1. Если аргумент shmaddr
нулевой, то система самостоятельно выбирает адрес.
2. Если аргумент shmaddr
отличен от нуля, значение возвращаемого адреса зависит от наличия флажка SHM_RND
в аргументе shmflag
:
• Если флажок SHM_RND
не установлен, система присоединяет разделяемую память к указанному shmaddr
адресу.
• Если флажок SHM_RND
установлен, система присоединяет разделяемую память к адресу, полученному округлением в меньшую сторону shmaddr
до некоторой определенной величины SHMLBA
.
По умолчанию разделяемая память присоединяется с правами на чтение и запись. Эти права можно изменить, указав флажок SHM_RDONLY
в аргументе shmflag
.
Таким образом, несколько процессов могут отображать область разделяемой памяти в различные участки собственного виртуального адресного пространства, как это показано на рис. 3.20.
Рис. 3.20. Совместное использование разделяемой памяти
Окончив работу с разделяемой памятью, процесс отключает (detach) область вызовом shmdt(2):
#include
#include
#include
int shmdt(char *shmaddr);
При работе с разделяемой памятью необходимо синхронизировать выполнение взаимодействующих процессов: когда один из процессов записывает данные в разделяемую память, остальные процессы ожидают завершения операции. Обычно синхронизация обеспечивается с помощью семафоров, назначение и число которых определяется конкретным использованием разделяемой памяти.
Можно привести примерную схему обмена данными между двумя процессами (клиентом и сервером) с использованием разделяемой памяти. Для синхронизации процессов использована группа из двух семафоров. Первый семафор служит для блокирования доступа к разделяемой памяти, его разрешающий сигнал – 0, а 1 является запрещающим сигналом. Второй семафор служит для сигнализации серверу о том, что клиент начал работу. Необходимость применения второго семафора обусловлена следующими обстоятельствами: начальное состояние семафора, синхронизирующего работу с памятью, является открытым (0), и вызов сервером операции заблокирует обращение к памяти для клиента. Таким образом, сервер должен вызвать операцию mem_lock
только после того, как разделяемую память заблокирует клиент. Назначение второго семафора заключается в уведомлении сервера, что клиент начал работу, заблокировал разделяемую память и начал записывать данные в эту область. Теперь, при вызове сервером операции mem_lock его выполнение будет приостановлено до освобождения памяти клиентом, который делает это после окончания записи строки «Здравствуй, Мир!».
shmem.h:
#define MAXBUFF 80
#define PERM 0666
/* Структура данных в разделяемой памяти */
typedef struct mem_msg {
int segment;
char buff[MAXBUFF];
} Message;
/* Ожидание начала выполнения клиента */
static struct sembuf proc_wait[1] = { 1, -1, 0 };
/* Уведомление сервера о том, что клиент начал работу */
static struct sembuf proc_start[1] = {
1, 1, 0
};
/* Блокирование разделяемой памяти */
static struct sembuf mem_lock[2] = {
0, 0, 0,
0, 1, 0
};
/* Освобождение ресурса */
static struct sembuf mem_unlock[1] = {
0, -1, 0
};
Сервер:
#include
#include
#include
#include
#include «shmem.h»
main() {
Message* msgptr;
key_t key;
int shmid, semid;
/* Получим ключ, Один и тот же ключ можно использовать как
для семафора, так и для разделяемой памяти */
if ((key = ftok(«server», 'A')) < 0) {
printf(«Невозможно получить ключn»);
exit(1);
}
/* Создадим область разделяемой памяти */
if ((shmid = shmget(key, sizeof(Message),
PERM | IPC_CREAT)) < 0) {
printf(«Невозможно создать областьn»);
exit(1);
}
/* Присоединим ее */
if ((msgptr = (Message*)shmat(shmid, 0, 0)) < 0) {
printf(«Ошибка присоединенияn»);
exit(1);
}
/* Создадим группу из двух семафоров:
Первый семафор – для синхронизации работы
с разделяемой памятью. Второй семафор -
для синхронизации выполнения процессов */
if ((semid = semget(key, 2, PERM | IPC_CREAT)) < 0) {
printf(«Невозможно создать семафорn»);
exit(1);
}
/* Ждем, пока клиент начнет работу и заблокирует разделяемую память */
if (semop(semid, &proc_wait[0], 1) < 0) {
printf(«Невозможно выполнить операцииn»);
exit(1);
}
/* Ждем, пока клиент закончит запись в разделяемую память
и освободит ее. После этого заблокируем ее */
if (semop(semid, &mem_lock[0], 2) < 0) {
printf(«Невозможно выполнить операциюn»);
exit(1);
}
/* Выведем сообщение на терминал */
printf(%s, msgptr->buff);
/* Освободим разделяемую память */
if (semop(semid, &mem_unlock[0], 1) < 0 {
printf(«Невозможно выполнить операциюn»);
exit(1);
}
/* Отключимся от области */
if (shmdt(msgptr) < 0) {
printf(«Ошибка отключенияn»);
exit(1);
}
/* Всю остальную работу по удалению объектов сделает клиент */
exit(0);
}
Клиент:
#include
#include
#include
#include
#include «shmem.h»
main() {
Message *msgptr;
key_t key;
int shmid, semid;
/* Получим ключ. Один и тот же ключ можно использовать как
для семафора, так и для разделяемой памяти */
if ((key = ftok(«server», 'A')) < 0) {
printf(«Невозможно получить ключn»);
exit(1);
}
/* Получим доступ к разделяемой памяти */
if ((shmid = shmget(key, sizeof(Message), 0)) < 0) {
printf(«Ошибка доступаn»);
exit(1);
}
/* Присоединим ее */
if ((msgptr = (Message*)shmat(shmid, 0, 0)) < 0) {
prinf("Ошибка присоединенияn);
exit(1);
}
/* Получим доступ к семафору */
if ((semid = semget(key, 2, PERM)) < 0) {
printf(«Ошибка доступаn»);
exit(1);
}
/* Заблокируем разделяемую память */
if (semop(semid, &mem_lock[0], 2) < 0) {
printf(«Невозможно выполнить операциюn»);
exit(1);
}
/* Уведомим сервер о начале работы */
if (semop(semid, &proc_start[0], 1) < 0) {
printf(«Невозможно выполнить операциюn»);
exit(1);
}
/* Запишем в разделяемую память сообщение */
sprintf(msgptr->buff, «Здравствуй, Мир!n»);
/* Освободим разделяемую память */
if (semop(semid, &mem_unlock[0], 1) < 0) {
printf(«Невозможно выполнить операциюn»);
exit(1);
}
/* Ждем, пока сервер в свою очередь не освободит
разделяемую память */
if (semop(semid, &mem_lock[0], 2) < 0) {
printf(Невозможно выполнить операциюn");
exit(1);
}
/* Отключимся от области */
if (shmdt(msgptr) < 0) {
printf(«Ошибка отключенияn»);
exit(1);
}
/* Удалим созданные объекты IPC */
if (shmctl(shmid, IPC_RMID, 0) < 0) {
printf(«Невозможно удалить областьn»);
exit(1);
}
if (semctl(semid, 0, IPC_RMID) < 0) {
printf(«Невозможно удалить семафорn»);
exit(1);
}
exit(0);
}