UNIX: взаимодействие процессов — страница 17 из 35

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

14.1. Введение

Основные принципы разделяемой памяти System V совпадают с концепцией разделяемой памяти Posix. Вместо вызовов shm_open и mmap в этой системе используются вызовы shmget и shmat.

Для каждого сегмента разделяемой памяти ядро хранит нижеследующую структуру, определенную в заголовочном файле :

struct shmid_ds {

 struct ipc_perm shm_perm; /* структура разрешений */

 size_t shm_segsz; /* размер сегмента */

 pid_t shm_lpid; /* идентификатор процесса, выполнившего последнюю операцию */

 pid_t shm_cpid; /* идентификатор процесса-создателя */

 shmatt_t shm_nattch; /* текущее количество подключений */

 shmat_t shm_cnattch; /* количество подключений in-core */

 time_t shm_atime; /* время последнего подключения */

 time_t shm_dtime; /* время последнего отключения */

 time_t shm_ctime; /* время последнего изменения данной структуры */

};

Структура ipc_perm была описана в разделе 3.3; она содержит разрешения доступа к сегменту разделяемой памяти.

14.2. Функция shmget

С помощью функции shmget можно создать новый сегмент разделяемой памяти или подключиться к существующему:

#include 

int shmget(key_t key, size_t size, int oflag);

/* Возвращает идентификатор разделяемой памяти в случае успешного завершения. –1 –в случае ошибки */

Возвращаемое целочисленное значение называется идентификатором разделяемой памяти. Он используется с тремя другими функциями shmXXX.

Аргумент key может содержать значение, возвращаемое функцией ftok, или константу IPC_PRIVATE, как обсуждалось в разделе 3.2.

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

Флаг oflag представляет собой комбинацию флагов доступа на чтение и запись из табл. 3.3. К ним могут быть добавлены с помощью логического сложения флаги IPC_CREAT или IPC_CREAT | IPC_EXCL, как уже говорилось в связи с рис. 3.2.

Новый сегмент инициализируется нулями.

Обратите внимание, что функция shmget создает или открывает сегмент разделяемой памяти, но не дает вызвавшему процессу доступа к нему. Для подключения сегмента памяти предназначена функция shmat, описанная в следующем разделе.

14.3. Функция shmat

После создания или открытия сегмента разделяемой памяти вызовом shmget его нужно подключить к адресному пространству процесса вызовом shmat:

#include 

void *shmat(int shmid, const void *shmaddr, int flag);

/* Возвращает начальный адрес полученной области в случае успешного завершения. –1 –в случае ошибки */

Аргумент shmid — это идентификатор разделяемой памяти, возвращенный shmget. Функция shmat возвращает адрес начала области разделяемой памяти в адресном пространстве вызвавшего процесса. Правила, по которым формируется этот адрес, таковы:

■ если аргумент shmaddr представляет собой нулевой указатель, система сама выбирает начальный адрес для вызвавшего процесса. Это рекомендуемый (и обеспечивающий наилучшую совместимость) метод;

■ если shmaddr отличен от нуля, возвращаемый адрес зависит от того, был ли указан флаг SHM_RND (в аргументе flag ):

□ если флаг SHM_RND не указан, разделяемая память подключается непосредственно с адреса, указанного аргументом shmaddr,

□ если флаг SHM_RND указан, сегмент разделяемой памяти подключается с адреса, указанного аргументом shmaddr, округленного вниз до кратного константе SHMLBA. Аббревиатура LBA означает lower boundary address — нижний граничный адрес.

По умолчанию сегмент подключается для чтения и записи, если процесс обладает соответствующими разрешениями. В аргументе flag можно указать константу SHM_RDONLY, которая позволит установить доступ только на чтение.

14.4. Функция shmdt

После завершения работы с сегментом разделяемой памяти его следует отключить вызовом shmdt:

#include 

int shmdt(const void *shmaddr);

/* Возвращает 0 в случае успешного завершения, –1 – в случае ошибки */

При завершении работы процесса все сегменты, которые не были отключены им явно, отключаются автоматически.

Обратите внимание, что эта функция не удаляет сегмент разделяемой памяти. Удаление осуществляется функцией shmctl с командой IPC_RMIO.

14.5. Функция shmctl

Функция shmctl позволяет выполнять различные операции с сегментом разделяемой памяти:

#include 

int shmctl(int shmid, int and, struct shmid_ds *buff);

/* Возвращает 0 в случае успешного завершения, –1 в случае ошибки */

Команд (значений аргумента cmd) может быть три:

■ IPC_RMID — удаление сегмента разделяемой памяти с идентификатором shmid из системы;

■ IPC_SET — установка значений полей структуры shmid_ds для сегмента разделяемой памяти равными значениям соответствующих полей структуры, на которую указывает аргумент buff: shm_perm.uid, shm_perm.gid, shm_perm.mode. Значение поля shm_ctime устанавливается равным текущему системному времени;

■ IPC_STAT — возвращает вызывающему процессу (через аргумент buff) текущее значение структуры shmid_ds для указанного сегмента разделяемой памяти.

14.6. Простые программы

В этом разделе приведено несколько примеров простых программ, иллюстрирующих работу с разделяемой памятью System V.

Программа shmget

Программа shmget, текст которой приведен в листинге 14.1,[1] создает сегмент разделяемой памяти, принимая из командной строки полное имя и длину сегмента.

Листинг 14.1. Создание сегмента разделяемой памяти System V указанного размера

//svshm/shmget.c

1  #include "unpipc.h"


2  int

3  main(int argc, char **argv)

4  {

5   int c, id, oflag;

6   char *ptr;

7   size_t length;

8   oflag = SVSHM_MODE | IPC_CREAT;

9   while ((c = Getopt(argc, argv, "e")) != –1) {

10   switch (c) {

11   case 'e':

12    oflag |= IPC_EXCL;

13    break;

14   }

15  }

16  if (optind != argc – 2)

17   err_quit("usage: shmget [ –e ] ");

18  length = atoi(argv[optind + 1]);

19  id = Shmget(Ftok(argv[optind], 0), length, oflag);

20  ptr = Shmat(id, NULL, 0);

21  exit(0);

22 }

19 Вызов shmget создает сегмент разделяемой памяти указанного размера. Полное имя, передаваемое в качестве аргумента командной строки, преобразуется в ключ IPC System V вызовом ftok. Если указан параметр –е, наличие существующего сегмента с тем же именем приведет к возвращению ошибки. Если мы знаем, что сегмент уже существует, в командной строке должна быть указана нулевая длина.

20 Вызов shmat подключает сегмент к адресному пространству процесса. После этого программа завершает работу. Разделяемая память System V обладает поменьшей мере живучестью ядра, поэтому сегмент разделяемой памяти при этом не удаляется.

Программа shmrmid

В листинге 14.2 приведен текст тривиальной программы shmrmid, которая вызывает shmctl с командой IPC_RMID для удаления сегмента разделяемой памяти из системы.

Листинг 14.2. Удаление сегмента разделяемой памяти system V из системы

//svshm/shmrmid.c

1  #include "unpipc.h"


2  int

3  main(int argc, char **argv)

4  {

5   int id;

6   if (argc != 2)

7    err_quit("usage: shmrmid ");

8   id = Shmget(Ftok(argv[1], 0), 0, SVSHM_MODE);

9   Shmctl(id, IPC_RMID, NULL);

10  exit(0);

11 }

Программа shmwrite

В листинге 14.3 приведен текст программы shmwrite, которая заполняет сегмент разделяемой памяти последовательностью значений 0, 1, 2, …, 254, 255, 0, 1 и т. д.

Листинг 14.3. Заполнение сегмента разделяемой памяти последовательностью чисел

//svshm/shmwrite.c

1  #include "unpipc.h"


2  int

3  main(int argc, char **argv)

4  {

5   int i, id;

6   struct shmid_ds buff;

7   unsigned char *ptr;

8   if (argc != 2)

9    err_quit("usage: shmwrite ");

10  id = Shmget(Ftok(argv[1], 0), 0, SVSHM_MODE);

11  ptr = Shmat(id, NULL, 0);

12  Shmctl(id, IPC_STAT, &buff);

13  /* присваиваем: ptr[0] = 0, ptr[1] = 1 и т. д. */

14  for (i = 0; i < buff.shm_segsz; i++)

15   *ptr++ = i % 256;

16  exit(0);

17 }

10-12 Сегмент разделяемой памяти открывается вызовом shmget и подключается вызовом shmat. Его размер может быть получен вызовом shmctl с командой IPC_STAT.

13-15 В разделяемую память записывается последовательность значений.

Программа shmread

Программа shmread, текст которой приведен в листинге 14.4, проверяет последовательность значений, записанную в разделяемую память программой shmwrite.

Листинг 14.4. Проверка значений в сегменте разделяемой памяти

//svshm/shmread.c

1  #include "unpipc.h"


2  int

3  main(int argc, char **argv)

4  {

5   int i, id;

6   struct shmid_ds buff;

7   unsigned char c, *ptr;

8   if (argc != 2)

9    err_quit("usage: shmread ");

10  id = Shmget(Ftok(argv[1], 0), 0, SVSHM_MODE);

11  ptr = Shmat(id, NULL, 0);

12  Shmctl(id, IPC_STAT, &buff);

13  /* проверка значений ptr[0] = 0, ptr[1] = 1 и т. д. */

14  for (i = 0; i < buff.shm_segsz; i++)

15   if ((c = *ptr++) != (i % 256))

16    err_ret("ptr[%d] = %d", i.e);

17  exit(0);

18 }

10-12 Открываем и подключаем сегмент разделяемой памяти. Его размер может быть получен вызовом shmctl с командой IPC_STAT. 13-16 Проверяется последовательность, записанная программой shmwrite.

Примеры

Создадим сегмент разделяемой памяти длиной 1234 байта в системе Solaris 2.6. Для идентификации сегмента используем полное имя нашего исполняемого файла shmget. Это имя будет передано функции ftok. Имя исполняемого файла сервера часто используется в качестве уникального идентификатора для данного приложения:

solaris % shmget shmget 1234

solaris % ipcs –bmo

IPC status from  as of Thu Jan 8 13:17:06 1998

T ID KEY        MODE       OWNER    GROUP  NATTCH SEGSZ

Shared Memory:

m 1  0x0000f12a –rw-r--r-- rstevens other1 0      1234

Программу ipcs мы запускаем для того, чтобы убедиться, что сегмент разделяемой памяти действительно был создан и не был удален по завершении программы shmcreate. Количество подключений (хранящееся в поле shm_nattch структуры shmid_ds) равно нулю, как мы и предполагали.

Теперь запустим пpoгрaммy shmwrite, чтобы заполнить содержимое разделяемой памяти последовательностью значений. Затем проверим содержимое сегмента разделяемой памяти программой shmread и удалим этот сегмент:

solaris % shmwrite shmget

solaris % shmread shmget

solaris % shmrmid shmget

solaris % ipes –bmo

IPC status from  as of Thu Jan 8 13:17:06 1998

T ID KEY        MODE       OWNER    GROUP  NATTCH SEGSZ

Shared Memory:

Мы используем программу ipcs, чтобы убедиться, что сегмент разделяемой памяти действительно был удален.

ПРИМЕЧАНИЕ

При использовании имени исполняемого файла сервера в качестве аргумента ftok для идентификации какого-либо вида IPC System V обычно передается полное имя этого файла (например, /usr/bin/myserverd), а не часть имени, как сделано у нас (shmget). У нас не возникло проблем в этом примере, потому что все программы запускались из того же каталога, в котором был расположен исполняемый файл сервера. Вы помните, что функция ftok использует номер i-node файла для формирования ключа IPC и ей безразлично, определяется файл своим полным именем или его частью (относительным именем). 

14.7. Ограничения, накладываемые на разделяемую память

На разделяемую память System V накладываются определенные ограничения точно так же, как и на семафоры и очереди сообщений System V (раздел 3.8). В табл. 14.1 приведены значения этих ограничений для разных реализаций. В первом столбце приведены традиционные для System V имена переменных ядра, в которых хранятся эти ограничения.


Таблица 14.1. Типичные значения ограничений, накладываемых на разделяемую память System V

ИмяОписаниеDUnix 4.0BSolaris 2.6
shmmaxМаксимальный размер сегмента в байтах41943041048576
shmmnbМинимальный размер сегмента разделяемой памяти в байтах11
shmmniМаксимальное количество идентификаторов разделяемой памяти в системе128100
shmsegМаксимальное количество сегментов, подключенных к процессу326

Пример

Программа в листинге 14.5 определяет значения четырех ограничений, приведенных в табл. 14.1.

Листинг 14.5. Определение системных ограничений на разделяемую память

//svshm/limits.c

1  #include "unpipc.h"

2  #define MAX_NIDS 4096


3  int

4  main(int argc, char **argv)

5  {

6   int i, j, shmid[MAX_NIDS];

7   void *addr[MAX_NIDS];

8   unsigned long size;

9   /* проверка максимального количества открываемых идентификаторов */

10  for (i = 0; i <= MAX_NIDS; i++) {

11   shmid[i] = shmget(IPC_PRIVATE, 1024, SVSHM_MODE | IPC_CREAT);

12   if (shmid[i]== –1) {

13    printf("%d identifiers open at once\n", i);

14    break;

15   }

16  }

17  for (j = 0; j < i; j++)

18   Shmctl(shmid[j], IPC_RMID, NULL);

19   /* определяем максимальное количество подключаемых сегментов */

20   for (i=0;i <= MAX_NIDS; i++) {

21    shmid[i] = Shmget(IPC_PRIVATE, 1024, SVSHM_MODE | IPC_CREAT);

22    addr[i] = shmat(shmid[i], NULL, 0);

23    if (addr[i] == (void *) –1) {

24     printf("%d shared memory segments attached at once\n", i);

25     Shmctl(shmid[i], IPC_RMID, NULL); /* удаляем неудачно подключенный сегмент */

26     break;

27    }

28   }

29   for (j = 0; j < i; j++) {

30    Shmdt(addr[j]);

31    Shmcfl(shmid[j], IPC_RMID, NULL);

32   }

33   /* проверка минимального размера сегмента */

34   for (size = 1; ; size++) {

35   shmid[0] = shmget(IPC_PRIVATE, size, SVSHM_MODE | IPC_CREAT);

36   if (shmid[0] != –1) { /* выход при успешном создании */

37    printf("minimum size of shared memory segment = %lu\n", size);

38    Shmctl(shmid[0], IPC_RMID, NULL);

39    break;

40   }

41  }

42  /* определение максимального размера сегмента */

43  for (size = 65536; ; size += 4096) {

44   shmid[0] = shmget(IPC_PRIVATE, size, SVSHM_MODE | IPC_CREAT);

45   if (shmid[0] == –1) { /* выход при ошибке */

46    printf("maximum size of shared memory segment = %lu\n", size-4096);

47    break;

48   }

49   Shmctl(shmid[0], IPC_RMID, NULL);

50  }

51  exit(0);

52 }

Запустив эту программу в Digital Unix 4.0B, увидим:

alpha % limits

127 identifiers open at once

32 shared memory segments attached at once

minimum size of shared memory segment = 1

maximum size of shared memory segment = 4194304

Причина, по которой в табл. 14.1 приведено значение 128 для числа идентификаторов, а наша программа выводит значение 127, заключается в том, что один сегмент разделяемой памяти всегда используется системным демоном.

14.8. Резюме

Разделяемая память System V похожа на разделяемую память Posix. Наиболее схожи функции:

■ shmget для получения идентификатора; 

■ shmat для подключения сегмента разделяемой памяти к адресному пространству процесса;

■ shmctl с командой IPC_STAT для получения размера существующего сегмента разделяемой памяти;

■ shmctl с командой IPC_RMID для удаления объекта разделяемой памяти.

Одно из отличий состоит в том, что размер объекта разделяемой памяти Posix может быть изменен в любой момент вызовом ftruncate (как мы продемонстрировали в упражнении 13.1), тогда как размер объекта разделяемой памяти System V устанавливается изначально вызовом shmget и не может быть изменен.

Упражнение

Листинг 6.6 содержал измененную версию программы из листинга 6.4. Новая программа использовала для обращения к объекту IPC System V идентификатор вместо полного имени. Таким образом мы показали, что для доступа к очереди сообщений System V достаточно знать только ее идентификатор (если у нас имеются соответствующие разрешения). Сделайте аналогичные изменения в программе из листинга 14.4, продемонстрировав, что это верно и для разделяемой памяти System V. 

ЧАСТЬ 5