UNIX: взаимодействие процессов - Уильям Стивенс
Шрифт:
Интервал:
Закладка:
Аргумент flags может принимать значения из табл. 12.2. Можно указать только один из флагов — MAP_SHARED или MAP_PRIVATE, прибавив к нему при необходимости MAP_FIXED. Если указан флаг MAP_PRIVATE, все изменения будут производиться только с образом объекта в адресном пространстве процесса; другим процессам они доступны не будут. Если же указан флаг MAP_SHARED, изменения отображаемых данных видны всем процессам, совместно использующим объект.
Для обеспечения переносимости пpoгрaмм флаг MAP_FIXED указывать не следует. Если он не указан, но аргумент addr представляет собой ненулевой указатель, интерпретация этого аргумента зависит от реализации. Ненулевое значение addr обычно трактуется как указатель на желаемую область памяти, в которую нужно произвести отображение. В переносимой программе значение addr должно быть нулевым и флаг MAP_FIXED не должен быть указан.
Одним из способов добиться совместного использования памяти родительским и дочерним процессами является вызов mmap с флагом MAP_SHARED перед вызовом fork. Стандарт Posix.1 гарантирует в этом случае, что все отображения памяти, установленные родительским процессом, будут унаследованы дочерним. Более того, изменения в содержимом объекта, вносимые родительским процессом, будут видны дочернему, и наоборот. Эту схему мы вскоре продемонстрируем в действии.
Для отключения отображения объекта в адресное пространство процесса используется вызов munmap:
#include <sys/mman.h>
int munmap(void *addr, size_t len);
/* Возвращает 0 в случае успешного завершения, –1 – в случае ошибки */
Аргумент addr должен содержать адрес, возвращенный mmap, a len — длину области отображения. После вызова munmap любые попытки обратиться к этой области памяти приведут к отправке процессу сигнала SIGSEGV (предполагается, что эта область памяти не будет снова отображена вызовом mmap).
Если область была отображена с флагом MAP_PRIVATE, все внесенные за время работы процесса изменения сбрасываются.
В изображенной на рис. 12.5 схеме ядро обеспечивает синхронизацию содержимого файла, отображенного в память, с самой памятью при помощи алгоритма работы с виртуальной памятью (если сегмент был отображен с флагом MAP_SHARED). Если мы изменяем содержимое ячейки памяти, в которую отображен файл, через некоторое время содержимое файла будет соответствующим образом изменено ядром. Однако в некоторых случаях нам нужно, чтобы содержимое файла всегда было в соответствии с содержимым памяти. Тогда для осуществления моментальной синхронизации мы вызываем msync:
#include <sys/mman.h>
int msync(void *addr, size_t len, int flags);
/* Возвращает 0 в случае успешного завершения, –1 – в случае ошибки */
Аргумент flags представляет собой комбинацию констант из табл. 12.3.
Таблица 12.3. Значения аргумента flags для функции msync
Константа Описание MS_ASYNC Осуществлять асинхронную запись MS_SYNC Осуществлять синхронную запись MS_INVALIDATE Сбросить кэшИз двух констант MS_ASYNC и MS_SYNC указать нужно одну и только одну. Отличие между ними в том, что возврат из функции при указании флага MS_ASYNC происходит сразу же, как только данные для записи будут помещены в очередь ядром, а при указании флага MS_SYNC возврат происходит только после завершения операций записи. Если указан и флаг MS_INVALIDATE, все копии файла, содержимое которых не совпадает с его текущим содержимым, считаются устаревшими. Последующие обращения к этим копиям приведут к считыванию данных из файла.
Почему вообще используется отображение в память?
До сих пор мы всегда говорили об отображении в память содержимого файла, который сначала открывается вызовом open, а затем отображается вызовом mmap. Удобство состоит в том, что все операции ввода-вывода осуществляются ядром и скрыты от программиста, а он просто пишет код, считывающий и записывающий данные в некоторую область памяти. Ему не приходится вызывать read, write или lseek. Часто это заметно упрощает код.
ПРИМЕЧАНИЕ
Вспомните нашу реализацию очередей сообщений Posix с использованием mmap, где значения сохранялись в структуре msg_hdr и считывались из нее же (листинги 5.26 и 5.28).
Следует, однако, иметь в виду, что не все файлы могут быть отображены в память. Попытка отобразить дескриптор, указывающий на терминал или сокет, приведет к возвращению ошибки при вызове mmap. К дескрипторам этих типов доступ осуществляется только с помощью read и write (и аналогичных вызовов).
Другой целью использования mmap может являться разделение памяти между неродственными процессами. В этом случае содержимое файла становится начальным содержимым разделяемой памяти и любые изменения, вносимые в нее процессами, копируются обратно в файл (что дает этому виду IPC живучесть файловой системы). Предполагается, что при вызове mmap указывается флаг MAP_SHARED, необходимый для разделения памяти между процессами.
ПРИМЕЧАНИЕ
Детали реализации mmap и связь этого вызова с механизмами реализации виртуальной памяти описаны в [14] для 4.4BSD и [6] для SVR4.
12.3. Увеличение счетчика в отображаемом в память файле
Изменим программу в листинге 12.1 (которая не работала) таким образом, чтобы родительский и дочерний процессы совместно использовали область памяти, в которой хранится счетчик. Для этого используем отображение файла в память вызовами open и mmap. В листинге 12.2 приведен текст новой программы.
Листинг 12.2. Родительский и дочерний процессы увеличивают значение счетчика в разделяемой памяти//shm/incr2.c
1 #include "unpipc.h"
2 #define SEM_NAME "mysem"
3 int
4 main(int argc, char **argv)
5 {
6 int fd, i, nloop, zero = 0;
7 int *ptr;
8 sem_t *mutex;
9 if (argc != 3)
10 err_quit("usage: incr2 <pathname> <#loops>");
11 nloop = atoi(argv[2]);
12 /* открываем файл, инициализируем нулем и отображаем в память */
13 fd = Open(argv[1], O_RDWR | O_CREAT, FILE_MODE);
14 Write(fd, &zero, sizeof(int));
15 ptr = Mmap(NULL, sizeof(int), PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
16 Close(fd);
17 /* создаем, инициализируем и отключаем семафор */
18 mutex = Sem_open(Px_ipc_name(SEM_NAME), O_CREAT | O_EXCL, FILE_MODE, 1);
19 Sem_unlink(Px_ipc_name(SEM_NAME));
20 setbuf(stdout, NULL); /* stdout не буферизуется */
21 if (Fork() == 0) { /* дочерний процесс */
22 for (i = 0; i < nloop; i++) {
23 Sem_wait(mutex);
24 printf("child: %dn", (*ptr)++);
25 Sem_post(mutex);
26 }
27 exit(0);
28 }
29 /* родительский процесс */
30 for (i = 0; i < nloop; i++) {
31 Sem_wait(mutex);
32 printf("parent: %dn", (*ptr)++);
33 Sem_post(mutex);
34 }
35 exit(0);
36 }
Новый аргумент командной строки11-14 Из командной строки теперь считывается еще один аргумент, задающий полное имя файла, который будет отображен в память. Мы открываем файл для чтения и записи, причем если файл не существует, он будет создан, а затем инициализируем файл нулем.
Вызов mmap и закрытие дескриптора15-16 Вызов mmap позволяет отобразить открытый файл в адресное пространство процесса. Первый аргумент является нулевым указателем, при этом система сама выбирает адрес начала отображаемого сегмента. Длина файла совпадает с размером целого числа. Устанавливается доступ на чтение и запись. Четвертый аргумент имеет значение MAP_SHARED, что позволяет процессам «видеть» изменения, вносимые друг другом. Функция возвращает адрес начала участка разделяемой памяти, мы сохраняем его в переменной ptr.
fork20-34 Мы отключаем буферизацию стандартного потока вывода и вызываем fork. И родительский, и дочерний процессы по очереди увеличивают значение целого, на которое указывает ptr.
Отображенные в память файлы обрабатываются при вызове fork специфическим образом в том смысле, что созданные родительским процессом отображения наследуются дочерним процессом. Следовательно, открыв файл и вызвав mmap с флагом MAP_SHARED, мы получили область памяти, совместно используемую родительским и дочерним процессами. Более того, поскольку эта общая область на самом деле представляет собой отображенный файл, все изменения, вносимые в нее (область памяти, на которую указывает ptr, — размером sizeof (int)), также действуют и на содержимое реального файла (имя которого было указано в командной строке).