Уильям Стивенс - UNIX: взаимодействие процессов
Если теперь мы запустим программу shmcreate в Solaris 2.6, то увидим, что файл указанного размера создается в каталоге /tmp:
solaris % shmcreate –e /testshm 123
solaris % ls-l/tmp/.*testshm*
-rw-r--r-- 1 rstevens other1 123 Dec 10 14:40 /tmp/.SHMtestshm
Пример
Приведем теперь пример (листинг 13.5), иллюстрирующий тот факт, что объект разделяемой памяти может отображаться в области, начинающиеся с разных адресов в разных процессах.
Листинг 13.5. Разделяемая память может начинаться с разных адресов в разных процессах//pxshm/test3.c
1 #include "unpipc.h"
2 int
3 main(int argc, char **argv)
4 {
5 int fd1, fd2, *ptr1, *ptr2;
6 pid_t childpid;
7 struct stat stat;
8 if (argc != 2)
9 err_quit("usage: test3 <name>");
10 shm_unlink(Px_ipc_name(argv[1]));
11 fd1 = Shm_open(Px_ipc_name(argv[1]), O_RDWR | O_CREAT | O_EXCL, FILE_MODE);
12 Ftruncate(fd1, sizeof(int));
13 fd2 = Open("/etc/motd", O_RDONLY);
14 Fstat(fd2, &stat);
15 if ((childpid = Fork()) == 0) {
16 /* дочерний процесс */
17 ptr2 = Mmap(NULL, stat.st_size, PROT_READ, MAP_SHARED, fd2, 0);
18 ptr1 = Mmap(NULL, sizeof(int), PROT_READ | PROT_WRITE,
19 MAP_SHARED, fd1, 0);
20 printf("child: shm ptr = %p, motd ptr = %pn", ptr1, ptr2);
21 sleep(5);
22 printf("shared memory integer = %dn", *ptr1);
23 exit(0);
24 }
25 /* родительский процесс: вызовы map следуют в обратном порядке */
26 ptr1 = Mmap(NULL, sizeof(int), PROT_READ | PROT_WRITE, MAP_SHARED, fd1, 0);
27 ptr2 = Mmap(NULL, stat.st_size, PROT_READ, MAP_SHARED, fd2, 0);
28 printf("parent: shm ptr = %p, motd ptr = %pn", ptr1, ptr2);
29 *ptr1 = 777;
30 Waitpid(childpid, NULL, 0);
31 exit(0);
32 }
10-14 Создаем сегмент разделяемой памяти с именем, принимаемым в качестве аргумента командной строки. Его размер устанавливается равным размеру целого. Затем открываем файл /etc/motd.
15-30 После вызова fork и родительский, и дочерний процессы вызывают mmap дважды, но в разном порядке. Каждый процесс выводит начальный адрес каждой из областей памяти. Затем дочерний процесс ждет 5 секунд, родительский процесс помещает значение 777 в область разделяемой памяти, после чего дочерний процесс считывает и выводит это значение.
Запустив эту программу, мы убедимся, что объект разделяемой памяти начинается с разных адресов в пространствах дочернего и родительского процессов:
solaris % test3 test3.data
parent: shm ptr = eee30000, motd ptr = eee20000
child: shm ptr = eee20000, motd ptr = eee30000
shared memory integer = 777
Несмотря на разницу в начальных адресах, родительский процесс успешно помещает значение 777 по адресу 0xeee30000, а дочерний процесс благополучно считывает его по адресу 0хеее20000. Указатели ptr1 в родительском и дочернем процессах указывают на одну и ту же область разделяемой памяти, хотя их значения в этих процессах различны.
13.5. Увеличение общего счетчика
Разработаем программу, аналогичную приведенной в разделе 12.3, — несколько процессов увеличивают счетчик, хранящийся в разделяемой памяти. Итак, мы помещаем счетчик в разделяемую память, а для синхронизации доступа к нему используем именованный семафор. Отличие программы из этого раздела от предыдущей состоит в том, что процессы более не являются родственными. Поскольку обращение к объектам разделяемой памяти Posix и именованным семафорам Posix осуществляется по именам, процессы, увеличивающие общий счетчик, могут не состоять в родстве. Достаточно лишь, чтобы каждый из них знал имя IPC счетчика и чтобы у каждого были соответствующие разрешения на доступ к объектам IPC (области разделяемой памяти и семафору).
В листинге 13.6 приведен текст программы-сервера, которая создает объект разделяемой памяти, затем создает и инициализирует семафор, после чего завершает работу.
Листинг 13.6. Программа, создающая и инициализирующая объект разделяемой памяти и семафор//pxshm/server1.c
1 #include "unpipc.h"
2 struct shmstruct { /* структура, помещаемая в разделяемую память */
3 int count;
4 };
5 sem_t *mutex; /* указатель на именованный семафор */
6 int
7 main(int argc, char **argv)
8 {
9 int fd;
10 struct shmstruct *ptr;
11 if (argc != 3)
12 err_quit("usage: server1 <shmname> <semname>");
13 shm_unlink(Px_ipc_name(argv[1])); /* ошибки игнорируются */
14 /* создание shm. установка размера, отображение, закрытие дескриптора */
15 fd = Shm_open(Px_ipc_name(argv[1]), O_RDWR | O_CREAT | O_EXCL, FILE_MODE);
16 Ftruncate(fd, sizeof(struct shmstruct));
17 ptr = Mmap(NULL, sizeof(struct shmstruct), PROT_READ | PROT_WRITE,
18 MAP_SHARED, fd, 0);
19 Close(fd);
20 sem_unlink(Px_ipc_name(argv[2])); /* игнорируем ошибку */
21 mutex = Sem_open(Px_ipc_name(argv[2]), O_CREAT | O_EXCL, FILE_MODE, 1);
22 Sem_close(mutex);
23 exit(0);
24 }
Создание объекта разделяемой памяти13-19 Программа начинает работу с вызова shm_unlink, на тот случай, если объект разделяемой памяти еще существует, а затем делается вызов shm_open, создающий этот объект. Его размер устанавливается равным размеру структуры sbmstruct вызовом ftruncate, а затем mmap отображает объект в наше адресное пространство. После этого дескриптор объекта закрывается.
Создание и инициализация семафора20-22 Сначала мы вызываем sem_unlink, на тот случай, если семафор еще существует. Затем делается вызов sem_open для создания именованного семафора и инициализации его единицей. Этот семафор будет использоваться в качестве взаимного исключения всеми процессами, которые будут обращаться к объекту разделяемой памяти. После выполнения этих операций семафор закрывается.
Завершение работы процесса23 Процесс завершает работу. Поскольку разделяемая память Posix обладает по крайней мере живучестью ядра, объект не прекращает существования до тех пор, пока он не будет закрыт всеми открывавшими его процессами и явно удален.
Нам приходится использовать разные имена для семафора и объекта разделяемой памяти. Нет никаких гарантий, что в данной реализации к именам Posix IPC будут добавляться какие-либо суффиксы или префиксы, указывающие тип объекта (очередь сообщений, семафор, разделяемая память). Мы видели, что в Solaris эти типы имен имеют префиксы .MQ, .SEM и .SHM, но в Digital Unix они префиксов не имеют.
В листинге 13.7 приведен текст программы-клиента, которая увеличивает счетчик, хранящийся в разделяемой памяти, определенное число раз, блокируя семафор для каждой операции увеличения.
Листинг 13.7. Программа, увеличивающая значение счетчика в разделяемой памяти//pxshm/client1.c
1 #include "unpipc.h"
2 struct shmstruct { /* структура, помещаемая в разделяемую память */
3 int count;
4 };
5 sem_t *mutex; /* указатель на именованный семафор */
6 int
7 main(int argc, char **argv)
8 {
9 int fd, i, nloop;
10 pid_t pid;
11 struct shmstruct *ptr;
12 if (argc != 4)
13 err_quit("usage: client1 <shmname> <semname> <#loops>");
14 nloop = atoi(argv[3]);
15 fd = Shm_open(Px_ipc_name(argv[1]), O_RDWR, FILE_MODE);
16 ptr = Mmap(NULL, sizeof(struct shmstruct), PROT_READ | PROT_WRITE,
17 MAP_SHARED, fd, 0);
18 Close(fd);
19 mutex = Sem_open(Px_ipc_name(argv[2]), 0);
20 pid = getpid();
21 for (i = 0; i < nloop; i++) {
22 Sem_wait(mutex);
23 printf("pid %ld: %dn", (long) pid, ptr->count++);
24 Sem_post(mutex);
25 }
26 exit(0);
27 }
Открытие области разделяемойпамяти15-18 Вызов shm_open открывает объект разделяемой памяти, который должен уже существовать (поскольку не указан флаг O_CREAT). Память отображается в адресное пространство процесса вызовом mmap, после чего дескриптор закрывается.
Открытие семафора19 Открываем именованный семафор.
Блокирование семафора и увеличение счетчика20-26 Параметр командной строки позволяет указать количество увеличений счетчика. Каждый раз мы выводим предыдущее значение счетчика вместе с идентификатором процесса, поскольку одновременно работают несколько экземпляров программы.
Запустим сначала сервер, а затем три экземпляра программы-клиента в фоновом режиме.
solaris % server shm1 sem1
solaris % client1 shm1 sem110000 &client1 shm1 sem110000 &client1 shm1 sem1 10000&
[2] 17976 интерпретатор выводит идентификаторы процессов
[3] 17977
[4] 17978
pid 17977: 0 и этот процесс запускается первым
pid 17977: 1
. . . процесс 17977 продолжает работу