Листинг 20. Работа с семафорами
10.semop(semid,&sop_unlock[0], 1);
6.3.6. Разделяемая память
Интенсивный обмен данными между процессами с использованием рассмотренных механизмов межпроцессного взаимодействия (каналы, FIFO, очереди сообщений) может вызвать падение производительности системы. Это, в первую очередь, связано с тем, что данные, передаваемые с помощью этих объектов, копируются из буфера передающего процесса в буфер ядра и затем в буфер принимающего процесса.
Механизм разделяемой памяти позволяет избавиться от накладных расходов передачи данных через ядро, предоставляя двум или более процессам возможность непосредственного получения доступа к одной области памяти для обмена данными.
Безусловно, процессы должны предварительно «договориться» о правилах использования разделяемой памяти. Например, пока один из процессов производит запись данных в разделяемую память, другие процессы должны воздержаться от работы с ней. К счастью, задача кооперативного использования разделяемой памяти, заключающаяся в синхронизации выполнения процессов, легко решается с помощью семафоров.
Примерный сценарий работы с разделяемой памятью выглядит следующим образом:
1)Сервер получает доступ к разделяемой памяти, используя семафор.
2)Сервер производит запись данных в разделяемую память.
3)После завершения записи сервер освобождает разделяемую память с помощью семафора.
4)Клиент получает доступ к разделяемой памяти, «запирая» ресурс с помощью семафора.
5)Клиент производит чтение данных из разделяемой памяти и освобождает её, используя семафор.
Для создания или для доступа к уже существующей разделяемой памяти используется системный вызов shmget:
#include <sys/types.h> #include <sys/ipc.h> #include <sys/shm.h>
int shmget(key_t key, int size, int shmflag);
Функция возвращает дескриптор разделяемой памяти в случае успеха, и –1 в случае неудачи. Аргумент size определяет размер создаваемой области памяти в байтах. Значения аргумента shmflag задают права доступа к объекту и специальные флаги IPC_CREAT и IPC_EXCL. Заметим, что вызов shmget лишь создаёт или обеспечивает доступ к разделяемой памяти, но не позволяет
106
работать с ней. Для работы с разделяемой памятью (чтение и запись) необходимо сначала присоединить (англ. attach) область вызовом shmat:
#include <sys/types.h> #include <sys/ipc.h> #include <sys/shm.h>
char *shmat(int shmid, char *shmaddr, int shmflag);
Вызов shmat возвращает адрес начала области в адресном пространстве процесса размером size, заданным предшествующим вызовом shmget. В этом адресном пространстве взаимодействующие процессы могут размещать требуемые структуры данных для обмена информацией. Правила получения этого адреса следующие:
1)Если аргумент shmaddr нулевой, то система самостоятельно выбирает адрес.
2)Если аргумент shmaddr отличен от нуля, значение возвращаемого адреса зависит от наличия флажка SHM_RND в аргументе shmflag:
Если флажок SHM_RND не установлен, система присоединяет разделяемую память к указанному shmaddr адресу.
Если флажок SHM_RND установлен, система присоединяет разделяемую память к адресу, полученному округлением в меньшую сторону shmaddr до некоторой определённой величины SHMLBA.
По умолчанию разделяемая память присоединяется с правами на чтение и запись. Эти права можно изменить, указав флажок SHM_RDONLY в аргументе shmflag.Таким образом, несколько процессов могут отображать область разделяемой памяти в различные участки собственного виртуального адресного пространства. Окончив работу с разделяемой памятью, процесс отключает (англ. detach) область вызовом shmdt:
#incluude <sys/types.h> #include <sys/ipc.h> #include <sys/shm.h>
int shmdt(char *shmaddr);
При работе с разделяемой памятью необходимо синхронизировать выполнение взаимодействующих процессов: когда один из процессов записывает данные в разделяемую память, остальные процессы ожидают завершения операции. Обычно синхронизация обеспечивается с помощью семафоров, назначение и число которых определяется конкретным использованием разделяемой памяти. Можно привести примерную схему обмена данными между двумя процессами (клиентом и сервером) с использованием разделяемой памяти.
107
Для синхронизации процессов использована группа из двух семафоров. Первый семафор служит для блокирования доступа к разделяемой памяти, его разрешающий сигнал – 0, а 1 является запрещающим сигналом. Второй семафор служит для сигнализации серверу о том, что клиент начал работу. Необходимость применения второго семафора обусловлена следующими обстоятельствами: начальное состояние семафора, синхронизирующего работу с памятью, является открытым (0), и вызов сервером операции mem_lock заблокирует обращение к памяти для клиента. Таким образом, сервер должен вызвать операцию mem_lock только после того, как разделяемую память заблокирует клиент. Назначение второго семафора заключается в уведомлении сервера, что клиент начал работу, заблокировал разделяемую память и начал записывать данные в эту область. Теперь, при вызове сервером операции mem_lock его выполнение будет приостановлено до освобождения памяти клиентом, который делает это после окончания записи строки «Здравствуй, Мир!».
|
|
Листинг |
21. Файл заголовков shmem.h |
|
|
1. |
|
#define |
MAXBUFF |
80 |
|
2. |
|
#define |
PERM |
0666 |
|
3. |
|
/*Структура данных разделяемой памяти*/ |
|||
4. |
|
typedef |
struct mem_msg{ |
||
5. |
|
int |
segment; |
|
|
6. |
|
char |
buff[MAXBUFF]; |
||
7. |
|
} Message; |
|
|
|
8. |
|
/*0жидание начала выполнения клиента*/ |
|||
9. |
|
static struct sembuf proc_wait[1] = { 1, -1, 0 }; |
|||
10. |
|
/*Уведомление сервера о том, что клиент начал работу*/ |
|||
11. |
|
static struct sembuf proc_start[1] = { 1, 1, 0 }; |
|||
12. |
|
/*Блокирование разделяемой памяти*/ |
|||
13. |
|
static struct sembuf mem_lock[2] = { 0, 0, 0, |
|||
14. |
|
|
|
0, 1, 0 }; |
|
15. |
|
/*Освобождение ресурса*/ |
|||
16. |
|
static struct sembuf mem_unlock[1] = { 0, -1, 0 }; |
|||
Листинг 22. Программа, реализующая сервер
1.#include <sys/types.h>
2.#include <sys/ipc.h>
3.#include <sys/sem.h>
4.#include <sys/shm.h>
5.#include “shmem.h”
6.
7.int main(void){
8.Message * msgptr;
9. |
key_t |
key; |
10. |
int |
shmid, semid; |
108
Листинг 22. Программа, реализующая сервер
11.
12.if ((key = ftok(“server”, ‘A’)) < 0){
13.printf(“Невозможно получить ключ\n”); exit(1);
14.}
15.
16./*Создадим область разделяемой памяти*/
17.if ((shmid = shmget(key, sizeof(Message), PERM | IPC_CREAT)) < 0){
18.printf(“Невозможно создать область\n”); exit(1);
19.}
20.
21.if ((msgptr = (Message *)shmat(shmid, 0, 0)) < 0){
22.printf (“Ошибка присоединения\n”); exit(1);
23.}
24.
25./*Создадим группу из двух семафоров */
26.
27.if ((semid = semget(key, 2, PERM | IPC_CREAT)) < 0){
28.printf(“Невозможно создать семафор\n”); exit(1);
29.}
30.
31./* Ждм, пока клиент начнет работу и *
* заблокирует разделяемую память |
*/ |
32.if (semop(semid, &proc_wait[0], 1) < 0){
33.printf(“Невозможно выполнить операцию\n”); exit(1);
34.}
35.
36./* Ждём, пока клиент закончит запись в *
*разделяемую память и освободит её. *
* После этого заблокируем её |
*/ |
37.if (semop(semid, &mem_lock[0], 2) < 0){
38.printf(“Невозможно выполнить операцию\n”); exit(1);
39.}
40.
41./* Выведем сообщение на терминал */
42.printf(“%s”, msgptr->buff);
43.
44./* Oсвободим разделяемую память */
45.if (semop(semid, &mem_unlock[0], 1) < 0){
46.printf(“Невозможно выполнить операцию\n”); exit(1);
47.}
109
Листинг 22. Программа, реализующая сервер
48.
49./*Oтключимся от области*/
50.if (shmdt(msgptr) < 0){
51.printf(“Ошибка отключения\n”); exit(1);
52.}
53.
54./* Всю остальную работу по удалению объектов *
* сделает клиент |
*/ |
55. exit(1); |
|
56. } |
|
Листинг 23. Программа, реализующая клиент
1.#include <sys/types.h>
2.#include <sys/ipc.h>
3.#include <sys/sem.h>
4.#include <sys/shm.h>
5.#include “shmem.h”
6.int main (void){
7.Message * msgptr;
8. |
key_t |
key; |
9. |
int |
shmid, semid; |
10.if ((key = ftok(“server”, ‘A’)) < 0){
11.printf(“Невозможно получить ключ\n”); exit(1);
12.}
13.if ((shmid = shmget(key, sizeof(Message), 0)) < 0){
14.printf(“Ошибка доступа\n”); exit(1);
15.}
16.if ((msgptr = (Message *) shmat(shmid, 0, 0)) < 0){
17.printf(“Ошибка присоединения\n”);
18.exit(1);
19.}
20./* Получим доступ к семафору*/
21.if ((semid = semget(key, 2, PERM)) < 0){
22.printf(“Ошибка доступа\n”); exit(1);
23.} /*3аблокируем разделяемую память*/
24.if (semop(semid, &mem_lock[0], 2) < 0){
25.printf(“Невозможно выполнить операцию\n”); exit(1);
26.} /*Уведомим сервер о начале работы*/
27.if (semop(semid, &proc_start[0], 1) < 0){
28.printf(“Невозможно выполнить операцию\n”); exit(1);
29.}
30./*3апишем в разделяемую память сообщение*/
110