Книга: Операционная система UNIX

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

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

Интенсивный обмен данными между процессами с использованием рассмотренных механизмов межпроцессного взаимодействия (каналы, 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 <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(2) лишь создает или обеспечивает доступ к разделяемой памяти, но не позволяет работать с ней. Для работы с разделяемой памятью (чтение и запись) необходимо сначала присоединить (attach) область вызовом shmat(2):

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
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 <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
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 <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <sys/shm.h>
#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 <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <sys/shm.h>
#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);
}

Оглавление книги


Генерация: 0.066. Запросов К БД/Cache: 0 / 0
поделиться
Вверх Вниз