Обмен сообщениями с помощью разделяемой памяти и семафоров



ФЕДЕРАЛЬНОЕ АГЕНТСТВО ПО ОБРАЗОВАНИЮ

Государственное образовательное учреждение высшего профессионального образования

Московский государственный институт электроники и математики

(Технический университет)

Кафедра математического обеспечения систем обработки информации и управления

Межпроцессное взаимодействие на уровне «клиент-сервер»

Методические указания к лабораторным и домашним работам по дисциплине «Операционные системы»

 

 

Специальности:                          230401 – Прикладная математика

 220101 – Вычислительные машины, комплексы, системы и сети

                   071900 – Информационные системы и технологии

                                            090102 – Компьютерная безопасность

 

 

Москва 2009

 

Составитель канд. техн. наук, проф. А.Ю. Истратов

 

УДК 681.1.06

 

Межпроцессное взаимодействие на уровне «клиент-сервер»/ Моск. гос. ин-т электроники и математики; Сост. –А.Ю. Истратов, 2005г. – с. 36

 

Библиогр.: 3 назв.

 

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

Для студентов специальностей «Прикладная математика», «Компьютерная безопасность», «Информационные системы и технологии», «Вычислительные машины, комплексы, системы и сети»

 

???? ISBN 5–94506–100–X

 

  


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

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

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

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

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

Обмен сообщениями

 

Реализация очередей сообщений аналогична реализации UNIX-файлов. В адресном пространстве ядра имеется таблица очередей сообщений, в которой отслеживаются все очереди сообщений, создаваемые в системе.

 

 

В каждой записи хранится одно сообщение и присвоенный ему тип.

В каждой записи таблицы сообщений можно найти следующие данные, относящиеся к одной из очередей:

– имя, представляющее собой целочисленный идентификационный ключ, присвоенный очереди процессом, который ее создал. Другие процессы могут, указывая этот ключ, «открывать» очередь и получать дескриптор для доступа к ней.

– идентификаторы владельца и группы создателя очереди. Знание их позволяет удалять очередь и изменять параметры управления ею.

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

– права доступа к очереди.

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

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

– указатель на список сообщений в очереди.

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

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

Процесс может выбрать сообщение из очереди следующими способами:

– выбрать самое старое сообщение, независимо от типа.

– выбрать сообщение, идентификатор которого совпадает с идентификатором, указанным процессом. Если несколько — самое старое.

– выбрать сообщение, числовое значение типа которого — наименьшее или равное значению типа, указанного процессом. Если несколько — самое старое.

На процедуру манипулирования сообщениями система устанавливает ряд ограничений, которые определяются в <sys/msg.h>.

В заголовке <sys/ipc.h> (IPC — Interprocess Communication) объявляется тип данных struct ipc_perm, который используется для хранения идентификаторов создателя, владельца, их групп, имени (ключа) очереди и прав на чтение и запись для той или иной очереди сообщений.

Запись таблицы сообщений имеет тип данных struct msqid_ds, определяемый в <sys/msg.h>:

 

Поле Данные
msg_perm Данные, хранящиеся в записи типа struct ipc_perm;
msg_first Указатель на первое (самое старое) сообщение в очереди;
msg_last Указатель на последнее (самое новое) сообщение в очереди;
msg_cbyte Общее число байтов во всех сообщениях очереди;
msg_qnum Общее число сообщений очереди на данный момент;
msg_qbytes Максимальное число байтов всех сообщений очереди;
msg_lspid Идентификатор процесса, который последним передал в очередь сообщение;
msg_lrpid Идентификатор процесса, который последним прочитал из очереди сообщение;
msg_stime Время, когда в очередь было передано самое последнее сообщение;
msg_rtime Время, когда из очереди было прочитано самое последнее сообщение;
msg_ctime Время последнего изменения управляющих параметров очереди (права доступа, идентификатор владельца, группы).

 

Структура struct msg, определенная в <sys/msg.h>, — это тип данных для записи сообщения.

 

Поле Данные
msg_type Целочисленный тип, присвоенный сообщению;
msg_ts Количество байтов в тексте сообщения;
msg_spot Указатель на текст сообщения, который хранится в другой области данных ядра;
msg_next Указатель на следующую запись сообщения или NULL, если это последняя запись в очереди сообщений.

 

Все эти структуры используются в таблице сообщений и записях сообщений следующим образом:

 

 

Для манипулирования сообщениями используется 4 системных вызова:

– msgget (open) — открытие для доступа и создание (при необходимости) очереди сообщений.

– msgsnd (write) — передача сообщения из очереди.

– msgrcv (read) — прием сообщения из очереди.

– msgctl (stat, unlink, chmod, chown) — манипулирование управляющими параметрами очереди сообщений.

Для этих системных вызовов необходимы следующие файлы заголовков:

#include <sys/types.h>

#include <sys/ipc.h>

#include <sys/msg.h>

int msgget(key_t key, int flag);

 

Этот системный вызов открывает очередь сообщений, идентификатор которой совпадает с key и возвращает положительный целочисленный дескриптор.

Если key — положительное число, этот системный вызов требует открыть очередь сообщений, идентификатор который совпадает с данным значением. Если же значением key является IPC_PRIVATE, системный вызов создает новую очередь сообщений.

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

 

 

int fd;

fd = msgget (15, IPC _ CREAT | 0664);

 

В случае неудачи системный вызов msgget возвращает -1.

 

int msgget(int msgfd, const void *obbuf, int len, int flag);

Системный вызов передает сообщение, на которое указывает obbuf, в очередь, обозначенную дескриптором msgfd.

obbuf — указатель на объект, который содержит реальный текст и тип сообщения, подлежащего передаче, например:

 

struct msgbuf

{

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

сhar text [MSGMAX]; // буфер для текста сообщения

};

 

Значение len — размер в байтах поля text объекта. flag может иметь значение 0. Это означает, что при необходимости процесс можно блокировать до тех пор, пока системный вызов не будет успешно выполнен. Если flag = IPC_NOWAIT, то при блокировании процесса выполнение системного вызова прерывается.

В случае успешного выполнения msgsnd возвращает 0, в случае передачи -1.

 

int msgrcv (int msgfd, const void* obbuf, int len, int mtype, int flag);

 

Этот системный вызов принимает сообщение типа mtype из очереди сообщений, обозначенной дескриптором msgfd. Полученное сообщение хранится в объекте, на который указывает аргумент obbuf. Аргумент len — максимальный размер (в байтах) текста сообщения. Аргумент mtype — это тип сообщения, подлежащего приему:

– 0 — принять из очереди самое старое сообщение любого типа;

– полож. целое > 0 — принять самое старое сообщение указанного типа;

– полож. целое < 0 — принять сообщение, тип которого меньше абсолютного значения mtype или равен ему. Если таких сообщений в очереди несколько, принять то, которое является самым старым и имеет наименьшее значение типа.

Аргумент flag может иметь значение 0. Это означает, что процесс можно блокировать. Если в очереди есть сообщение, превышающее len, системный вызов возвращает -1.

Если flag == IPC_NOWAIT, то вызов будет неблокирующим. Если к IPC_NOWAIT установлен и флаг MSG_NOERROR, то сообщение, находящееся в очереди, можно читать, даже если его размер превышает len байтов. Системный вызов возвращает вызывающему процессу первые len байтов текста сообщения, а остальные данные отбрасывает.

Системный вызов msgrcv возвращает количество байтов, записанных в буфер text объекта, на который указывает аргумент obbuf или -1.

 

Пример.

 

#include <stdio.h>

#include <string.h>

#include <sys/stat.h>

#include <sys/ipc.h>

#include <sys/msg.h>

#ifndef MSGMAX

#define MSGMAX 1024

#endif

struct mbuf

{

long mtype;

char text[MSGMAX];

} buf = { 8, "Happy birthday to you" };

int main()

{

int perm, fd;

perm = S_IRUSR | S_IWUSR | S_IRGRP | S_IWOTH;

fd = msgget(15, IPC_CREAT | perm);

if (fd == -1 || msgsnd(fd, &buf, strlen(buf.text) + 1, IPC_NOWAIT))

   perror(" Ошибка в посылке сообщения ");

else if (msgrcv(fd, &buf, MSGMAX, 8, IPC_NOWAIT | MSG_NOERROR) != -1)

   printf("%s\n", buf.text);

else

   printf(" Ошибка msgrcv\n");

}

 

После создания очереди сообщений с идентификатором 15, процесс передает в эту очередь сообщение “Happy birthday to you”, тип которого 8 и указывает, что данный вызов неблокирующий. Далее процесс вызывает системный вызов msgrcv, который ждет и выбирает из очереди сообщение типа 8. Если вызов завершается успешно, процесс направляет выбранное сообщение на стандартный вывод, в противном случае сообщает об ошибке.

 

int msgctl(int msgfd, int cmd, struct msqid_ds *obbuf);

 

С помощью этого системного вызова можно запрашивать управляющие параметры очереди сообщений, обозначенной msgfd, изменять информацию в управляющих параметрах очереди, удалять очередь из системы. Аргумент msgfd берется из вызова msgget. Возвращает 0 в случае успешного завершения, -1 — ошибки.

Значения аргумента cmd следующие:

– IPC_STAT — копировать управляющие параметры очереди в объект, указанный obbuf.

– IPC_SET — заменить управляющие параметры очереди параметрами, содержащимися в объекте, на который указывает obbuf.

– IPC_RMID — удалить очередь из системы.

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

 

Пример.

 

# include <stdio.h>

# include <sys/ipc.h>

# include <sys/msg.h>

void main()

{

struct msqid_ds buf;

int fd;

fd = msgget(15, 0);

if (fd > 0 && msgctl(fd, IPC_STAT, &buf) == 0)

{

   printf ("Количество сообщений: % d \ n ", buf . msg _ qnum );

   buf . msg _ perm . uid = getuid (); // изменить UID владельца

   if (msgctl(fd, IPC_SET, &buf) == -1)

       printf ("Ошибка во 2-м msgctl \ n ");

}

else

   printf(" Ошибка в 1- м msgctl\n");

if (msgctl(fd, IPC_RMID, 0) == -1) perror(" Ошибка в 3- м msgctl");

}

 

Здесь процесс открывает очередь сообщений с ключевым идентификатором 15 и вызывает msgctl для считывания управляющих параметров очереди. Если msgget и msgctl выполняются успешно, процесс выводит на экран количество сообщений, находящихся в очереди, и устанавливает идентификатор владельца очереди равным своему идентификатору. Наконец, вызвав msgctl в 3-й раз, процесс удаляет очередь.

Поддержка семафоров

 

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

– имя — целочисленный идентификатор, присвоенный процессом, который его создал;

– идентификаторы создателя и группы;

– идентификаторы назначенного владельца и его группы;

– права доступа («запись-чтение» по категориям «владелец-группа-прочие»);

– количество семафоров в наборе;

– время изменения одного или нескольких значений семафоров последним процессом;

– время последнего изменения управляющих параметров набора каким-либо процессом;

– указатель на массив семафоров.

 

Семафоры в наборе обозначаются индексами массива: 1-й семафор имеет индекс 0, 2-й — единицу, и т. д. В каждом семафоре содержатся следующие данные:

– значение семафора;

– идентификатор процесса, который оперировал семафором в последний раз;

– число процессов, заблокированных в текущий момент и ожидающих увеличения значения семафора;

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

 

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

Ограничения на манипулирование семафорами определяются в заголовке <sys/sem.h>.

В заголовке <sys/ipc.h> объявляется тип данных struct ipc_perm, который используется в данном наборе семафоров для хранения идентификаторов создателя и его группы, прав доступа на чтение и запись.

Элементы таблицы семафоров имеют тип данных struct semid_ds, который определяется в заголовке <sys/sem.h>:

 

Поле Данные
sem_perm Данные, хранящиеся в записи struct ipc_perm;
sem_nsems Число семафоров в наборе;
sem_base Указатель на массив семафоров;
sem_otime Время, когда какой-либо процесс в последний раз выполнял операции под семафорами;
sem_ctime Время, когда тот или иной процесс в последний раз изменял управляющие параметры набора.

 

Тип данных struct sem из заголовка <sys/sem.h> используется для представления данных, хранящихся в семафоре:

 

 

Поле Данные
semval Целочисленные значения текущего семафора;
sempid Идентификатор процесса, который выполнял операции над данным семафором в последний раз;
semncnt Число процессов, которые заблокированы и ожидают увеличения значения семафора;
semzcnt Число процессов, которые заблокированы и ожидают обращения значения семафора в ноль.

 

В ОС UNIX имеются 3 системных вызова, предназначенные для манипулирования семафорами: semget, semop, semctl.

Прототип системного вызова semget имеет следующий вид:

 

int semget(key_t key, int num_sem, int flag);

 

Этот системный вызов открывает набор семафоров, идентификатор которого задан значением аргумента key, и возвращает неотрицательный целочисленный дескриптор.

– если key >= 0 (целое), системный вызов открывает такой набор семафоров;

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

Если flag = 0, системный вызов прерывает свою работу, если нет набора семафоров с идентификатором key; в противном случае возвращает дескриптор этого набора семафоров.

Если процессу необходимо создать новый набор с идентификатором key, то значение flag должно представлять собой результат побитового сложения константы IPC_CREAT и числовых значений прав доступа для чтения и записи.

Значение num_sem может быть равно нулю, если IPC_CREAT в flag не указан, или числу семафоров в создаваемом наборе.

Например:

 

int perms = S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH;

int semd = semget(15, 2, IPC_CREAT | perms);

 

Создается набор из 2-х семафоров с идентификатором 15 и разрешением на чтение-запись для владельца, чтение — для группы и прочих.

В случае неудачи semget возвращает -1.

 

Прототип системного вызова semop имеет следующий вид:

 

int semop(int semfd, struct sembuf *op, int len);

 

С помощью этого системного вызова можно изменять значение одного или нескольких семафоров в наборе с дескриптором semfd и/или проверять равенство их значений нулю.

Аргумент op — это указатель на массив объектов типа struct sembuf, описанной в заголовке <sys/sem.h>, каждый из которых задает одну операцию (запрос или изменение значения).

len — показывает, сколько элементов имеется в массиве, указанном op.

 

struct sembuf

{

short sem _ num ; // индекс семафора

short sem _ op ; // операция над семафором

short sem _ flg ; // флаг(и) операции

};

 

Переменная sem_op может иметь следующие значения:

– полож. число — увеличить значение указ. семафора на эту величину;

– отриц. число — уменьшить значение указ. семафора на эту величину;

– 0 — проверить равенство значения семафора нулю.

Если системный вызов semop попытается уменьшить значение семафора до отрицательного числа или посчитает, что значение семафора равно 0, когда на самом деле это не так, то ядро заблокирует вызывающий процесс. Этого не произойдет, если в sem_flg указан макрос IPC_NOWAIT. Если sem_flg = SEM_UNDO, то при завершении вызывающего процесса ядро ликвидирует сделанные изменения, чтобы процессы, ожидающие изменения семафоров, не были заблокированы навечно.

В случае успешного выполнения semop возвращает 0, неудачи — -1.

 

Пример.

 

#include <stdio.h>

#include <sys/types.h>

#include <sys/ipc.h>

#include <sys/sem.h>

struct sembuf buf[2] = {0, -1, SEM_UNDO | IPC_NOWAIT}, {1, 0, 1};

// SEM _ UNDO – уменьшить знач. 1-го семафора на 1

// 0 в {1, 0, 1} – проверить знач. 2-го семафора на рав. 0

main ()

{

int perms = S_IRWXU | S_IRWXG | S_IRWXO;

int fd = semget (100, 2, IPC _ CREAT | perms );

if ( fd == -1)

{

   printf ("Ошибка в semget \ n ");

   exit (1);

}

if (semop(fd, buf, 2) == -1) perror("semop");

}

 

В примере открывается набор из 2-х семафоров с идентификатором 100. Этот набор создается, если его не было, с правами 777. Если системный вызов semget проходит успешно, вызывается semop, который уменьшает значение 1-го семафора на 1 и проверяет значение 2-го семафора на равенство 0.

 

Прототип системного вызова semctl имеет следующий вид:

 

int semctl(int semfd, int num, int cmd, union semun arg);

 

C помощью этого системного вызова можно запрашивать и изменять управляющие параметры набора семафоров с дескриптором semfd, а также удалять семафор.

Значение num — это индекс семафора, cmd — задает операцию, которая должна быть выполнена над конкретным семафором данного набора.

arg — это объект типа union semun, который может использоваться для задания или выборки управляющих параметров набора семафоров в соответствии с аргументом cmd.

Тип данных union semun определяется в заголовке <sys/sem.h>:

 

union semun

{

int val ;         // значение семафора

struct semid _ ds * buf ; // управляющие параметры набора

ushort * array ;   // массив значений семафоров

};

 

cmd может принимать следующие значения:

– IPC_STAT — копировать управляющие параметры набора семафоров в объект, указанный аргументом arg.buf.

– IPC_SET — заменить управляющие параметры набора семафоров данными, определенными в arg.buf.

– IPC_RMID — удалить семафор из системы.

– GETALL — скопировать все значения семафоров в arg.array.

– SETALL — установить все значения семафоров равными значениям, содержащимся в массиве, на который указывает arg.array.

– GETVAL — возвратить значение семафора с номером num. arg не используется.

– SETVAL — установить значение семафора с номером num, равному значению, указанному в arg.val.

– GETPID — возвратить идентификатор процесса, который выполнял операции над семафором с номером num последним. arg не используется.

– GETNCNT — возвратить количество процессов, которые в текущий момент заблокированы и ожидают увеличения семафора с номером num (arg не используется).

– GETZCNT — возвратить количество процессов, которые в текущий момент заблокированы и ожидают обращение значения семафора с номером num в нуль (arg не используется).

Системный вызов semctl возвращает значение, соответствующее конкретному cmd или -1.

 

Пример.

#include <stdio.h>

#include <sys/ipc.h>

#include <sys/sem.h>

union semun

{

int val;

struct semid_ds *buf;

ushort *array;

} arg;

main()

{

struct semid_ds buf;

arg.buf = &buf;

int fd = semget(100, 0, 0);

if (fd > 0 && semctl(fd, 0, IPC_STAT, arg))

{

   printf(" Кол - во семафоров в наборе : %d\n", arg.buf->sem_nsems);

   arg.buf->sem_perm.uid = getuid();

   if (semctl(fd, 0, IPC_SET, arg) == -1) perror("semctl2");

}

else

   perror("semctl1");

if (semctl(fd, 0, IPC_RMID, 0) == -1) perror("semctl3");

}

 

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

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

 

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

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

В любой момент времени в системе может существовать множество разделяемых областей памяти.

В ОС UNIX в адресном пространстве ядра имеется таблица разделяемой памяти, в которой отслеживаются все разделяемые области памяти, создаваемые в системе. В каждом элементе таблицы находятся следующие данные:

– идентификатор разделяемой области памяти;

– идентификаторы владельца и группы;

– идентификаторы назначенного владельца и его группы;

– права доступа;

– размер разделяемой области памяти в байтах;

– время, когда какой-либо процесс в последний раз подсоединялся к разделяемой области памяти;

– время, когда какой-либо процесс в последний раз отсоединялся от разделяемой области памяти;

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

Разделяемые области памяти не освобождаются, даже если создавшие их процессы завершаются. Для манипулирования разделяемой областью памяти система устанавливает ряд ограничений, которые определяются в <sys/shm.h>.

В заголовке <sys/ipc.h> объявляется тип данных struct ipc_perm, используемый для хранения идентификаторов создателя, владельца и их групп, идентификатора разделяемой области памяти, прав доступа.

Элементы таблицы разделяемой области памяти относятся к типу данных struct shmid_ds, который определяется в <sys/shm.h>:

 

Поле Данные
shm_perm Данные, хранящиеся в записи типа struct ipc_perm;
shm_segsz Размер разделяемой области памяти в байтах;
shm_lpid Идентификатор процесса, который последний раз подсоединялся к разделяемой области памяти;
shm_cpid Идентификатор процесса-создателя;
shm_nattch Число процессов, подсоединенных к области в данный момент;
shm_atime Время, когда процесс в последний раз подсоединялся к разделяемой области памяти;
shm_dtime Время, когда процесс в последний раз отсоединялся от разделяемой области памяти;
shm_ctime Время, когда процесс в последний раз изменил управляющие параметры разделяемой области памяти.

 

Для манипулирования разделяемой областью памяти в ОС UNIX имеются 4 системных вызова: shmget, shmat, shmdt, shmctl.

 

Прототип системного вызова shmget имеет следующий вид:

 

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

Этот системный вызов открывает разделяемую область памяти, идентификатор которой совпадает с key, и возвращает неотрицательный целочисленный дескриптор. Если значение key = IPC_PRIVATE, системный вызов выделяет новую разделяемую область памяти, которая будет использоваться исключительно вызывающим процессом (родитель и сыновья).

Аргумент size задает размер разделяемой области памяти, которая может быть подсоединена к вызывающему процессу с помощью системного вызова shmat.

Если flag = 0 и нет разделяемой области памяти с идентификатором key, то этот системный вызов завершается неудачно, в противном случае он возвращает дескриптор этой области.

Если процессу необходимо создать разделяемую область памяти с ключом key, то значение flag должно представлять результат побитового сложения IPC_CREAT и прав доступа.

В случае неудачи возвращает -1.

 

Прототип системного вызова shmat выглядит следующим образом:

 

void* shmat(int shmid, void *addr, int flag);

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

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

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

Если значение addr = 0, flag может содержать SHM_RND. Этот флаг указывает ядру на то, что виртуальный адрес addr можно округлить до границы страницы памяти. В противном случае при addr ≠ 0, системный вызов завершается неудачно (-1).

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

Системный вызов shmat возвращает виртуальный адрес области отображения разделяемой памяти, а в случае неудачи -1.

 

Прототип системного вызова shmdt имеет следующий вид:

 

int shmdt( void * addr );

 

Этот системный вызов отсоединяет разделяемую область памяти от заданного аргументом addr виртуального адреса вызывающего процесса.

Приведем пример программы, открывающей разделяемую область памяти размером 1024 байта с ключевым идентификатором 100. Если такая область памяти не существует, то программа создает ее с помощью системного вызова shmget и устанавливает для нее разрешение на чтение-запись для всех пользователей.

После открытия разделяемая область памяти подсоединяется к виртуальному адресу процесса посредством системного вызова shmat. Затем программа записывает сообщение “Happy birthday to you” в разделяемую область памяти и отсоединяет от нее процесс. После этого любой процесс может подсоединиться к разделяемой области памяти и читать записанное в ней сообщение.

 

#include <stdio.h>

#include <string.h>

#include <sys/ipc.h>

#include <sys/shm.h>

main()

{

int perms = S_IRWXU | S_IRWXG | S_IRWXO;

int fd = shmget(100, 1024, IPC_CREAT | perms);

if (fd == -1)

{

   perror("shmget");

   exit(1);

}

char *addr = (char*) shmat(fd, 0, 0);

if (addr == (char*) -1)

{

   perror("shmat");

   exit(1);

}

strcpy(addr, "Happy birthday to you");

if (shmdt(addr) == -1) perror("shmdt");

}

 

Прототип системного вызова shmctl имеет следующий вид:

 

int shmctl( int shmid , int cmd , struct shmid _ ds * buf );

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

shmid — дескриптор разделяемой области памяти, полученный с помощью shmget. Аргумент buf — это адрес объекта типа shmid_ds, который можно использовать для задания и выборки управляющих параметров разделяемой области памяти, указанных аргументом cmd:

– IPC_STAT — копировать управляющие параметры разделяемой области памяти в объект, указанный в buf.

– IPC_SET — заменить управляющие параметры разделяемой области памяти параметрами, определенными в объекте, на который указывает buf.

– IPC_RMID — удалить разделяемую область памяти из системы. Если к разделяемой области памяти подсоединены один или несколько процессов, то операция удаления будет отложена до тех пор, пока эти процессы не отсоединятся от нее.

– SHM_LOCK — блокировать разделяемую область памяти.

– SHM_UNLOCK — разблокировать разделяемую область памяти.

В случае успешного выполнения системный вызов возвращает 0, а в случае неудачи — -1.

 

Пример.

 

#include <stdio.h>

#include <sys/ipc.h>

#include <sys/shm.h>

main()

{

struct shmid_ds sbuf;

int fd = shmget(100, 1024, 0);

if (fd > 0 && shmctl(fd, IPC_STAT, &buf) == 0)

{

   printf(" Размер РОП : %d\n", sbuf.shm_segsz);

   sbuf . shm _ perm . uid = getuid (); // заменить идент. владельца

   if (shmctl(fd, IPC_SET, &buf) == -1) perror("shmctl1");

}

else

   perror("shmctl2");

if (shmctl(fd, IPC_RMID, 0)) perror("shmctl3");

}

Программа открывает разделяемую область памяти с идентификатором 100 и вызывает системный вызов shmctl для выборки управляющих параметров этой области. Если shmget и shmctl выполнились успешно, процесс выводит на экран размер разделяемой области. Посредством еще одного системного вызова shmctl процесс устанавливает идентификатор владельца разделяемой области памяти равным своему. Наконец, в третий раз процесс удаляет разделяемую область памяти.

Обмен сообщениями с помощью разделяемой памяти и семафоров

 

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

 

Семафор 0 Семафор 1  
0 1 Сервер ожидает передачи данных клиентом
1 0 Сообщение клиента готово для прочтения сервером
1 1 Данные ответа сервера клиенту готовы

 

Взаимодействие клиенты/сервер осуществляется следующим образом:

1. Сервер создает набор семафоров и разделяемую область памяти. Он инициирует созданный набор значениями 0, 1.

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

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

4. Клиентский процесс, изменивший значения набора семафоров, может теперь записать в разделяемую область памяти команду запроса на обслуживание и свой идентификатор. После этого он выполняет системный вызов semop со значениями 1, 0. В результате серверный процесс разблокируется и может вызвать системный вызов semop, но новые значения семафоров будут по-прежнему блокировать остальные клиентские процессы, которые выполняют системный вызов semop со значениями 0, -1. Если команда запроса на обслуживание не QUIT_CMD, этот клиентский процесс выполнит semop со значениями -1, -1 и заблокируется.

5. Разблокированный сервер прочитает из разделяемой области памяти запрос на обслуживание, записанный клиентом. Если была записана команда QUIT_CMD, сервер освободит разделяемую область памяти и набор семафоров, а затем завершит свою работу. Если записанная команда другая, сервер запишет данные ответа в разделяемую область памяти, а затем выполнит системный вызов semop со значением 1, 1. Это разблокирует клиентский процесс, который выполняет системный вызов semop со значениями -1, -1. Остальные клиентские процессы, которые выполняют semop со значениями 0, -1, остаются заблокированными новыми значениями семафоров. После вызова semop сервер возвращается в состояние ожидания запроса на обслуживание от нового клиента.

6. Клиент, который разблокирован сервером, устанавливает значения набора семафоров в 0, 0 и читает данные ответа сервера. Он обрабатывает данные (например, выводит на стандартное устройство вывода), а затем устанавливает значения семафоров в 0, 1, после чего завершается. Последний системный вызов semop возвращает систему в состояние, в котором один из клиентов будет разблокирован и начнет взаимодействовать с сервером через разделяемую область памяти и набор семафоров.

Это взаимодействие можно отобразить на следующей диаграмме:

 


Дата добавления: 2019-02-13; просмотров: 771; Мы поможем в написании вашей работы!

Поделиться с друзьями:






Мы поможем в написании ваших работ!