Содержание занятия
Скачать 0.66 Mb.
|
1.5 Разделяемые сегменты памятиВ стандарте POSIX-2001 разделяемый объект памяти определяется как объект, представляющий собой память, которая может быть параллельно отображена в адресное пространство более чем одного процесса. Таким образом, процессы могут иметь общие области виртуальной памяти и разделять содержащиеся в них данные. Единицей разделяемой памяти являются сегменты. Разделение памяти обеспечивает наиболее быстрый обмен данными между процессами. Работа с разделяемой памятью начинается с того, что один из взаимодействующих процессов посредством функции shmget() создает разделяемый сегмент, специфицируя первоначальные права доступа к нему и его размер в байтах. Чтобы получить доступ к разделяемому сегменту, его нужно присоединить (для этого служит функция shmat()), т. е. разместить сегмент в виртуальном пространстве процесса. После присоединения, в соответствии с правами доступа, процессы могут читать данные из сегмента и записывать их (быть может, синхронизируя свои действия с помощью семафоров). Когда разделяемый сегмент становится ненужным, его следует отсоединить с помощью функции shmdt(). Предусмотрена возможность выполнения управляющих действий над разделяемыми сегментами (функция shmctl()). Описание перечисленных функций представлено в пример 8.40. #include int shmget (key_t key, size_t size, int shmflg); void *shmat (int shmid, const void *shmaddr, int shmflg); int shmdt (const void *shmaddr); int shmctl (int shmid, int cmd, struct shmid_ds *buf); Листинг 8.40. Описание функций для работы с разделяемыми сегментами памяти. Структура shmid_ds, ассоциированная с идентификатором разделяемого сегмента памяти, должна содержать по крайней мере следующие поля. struct ipc_perm shm_perm; /* Данные о правах доступа к разделяемому сегменту */ size_t shm_segsz; /* Размер сегмента в байтах */ pid_t shm_lpid; /* Идентификатор процесса, выполнившего последнюю операцию над разделяемым сегментом */ pid_t shm_cpid; /* Идентификатор процесса, создавшего разделяемый сегмент */ shmatt_t shm_nattch; /* Текущее число присоединений сегмента */ time_t shm_atime; /* Время последнего присоединения */ time_t shm_dtime; /* Время последнего отсоединения */ time_t shm_ctime; /* Время последнего изменения посредством shmctl() */ Функция shmget() аналогична msgget() и semget(); аргумент size задает нижнюю границу размера сегмента в байтах; реализация, учитывающая, например, правила выравнивания, имеет право создать разделяемый сегмент большего размера. Структура shmid_ds инициализируется в соответствии с общими для средств межпроцессного взаимодействия правилами. Поле shm_segsz устанавливается равным значению аргумента size. Число уникальных идентификаторов разделяемых сегментов памяти ограничено; попытка его превышения ведет к неудачному завершению shmget() (возвращается -1). Вызов shmget() завершится неудачей и тогда, когда значение аргумента size меньше минимально допустимого либо больше максимально допустимого размера разделяемого сегмента. Чтобы присоединить разделяемый сегмент, используется функция shmat(). Аргумент shmid задает идентификатор разделяемого сегмента; аргумент shmaddr - адрес, по которому сегмент должен быть присоединен, т. е. тот адрес в виртуальном пространстве процесса, который получит начало сегмента. Поскольку свойства сегментов зависят от аппаратных особенностей управления памятью, не всякий адрес является приемлемым. Если установлен флаг SHM_RND, адрес присоединения округляется до величины, кратной константе SHMLBA. Если shmaddr задан как пустой указатель, реализация выбирает адрес присоединения по своему усмотрению. По умолчанию присоединяемый сегмент будет доступен и на чтение, и на запись (если процесс обладает необходимыми правами). Флаг SHM_RDONLY предписывает присоединить сегмент только для чтения. При успешном завершении функции shmat() результат равен адресу, который получил присоединенный сегмент; в случае неудачи возвращается -1. (Разумеется, для использования результата shmat() в качестве указателя его нужно преобразовать к требуемому типу.) Отсоединение сегментов производится функцией shmdt(); аргумент shmaddr задает начальный адрес отсоединяемого сегмента. Управление разделяемыми сегментами осуществляется при помощи функции shmctl(), аналогичной msgctl(). Как и для очередей сообщений, для разделяемых сегментов определены управляющие команды IPC_STAT (получить информацию о состоянии разделяемого сегмента), IPC_SET (переустановить характеристики), IPC_RMID (удалить разделяемый сегмент). Удалять сегмент нужно после того, как от него отсоединились все процессы. Аппарат разделяемых сегментов предоставляет нескольким процессам возможность одновременного доступа к общей области памяти. Обеспечивая корректность доступа, процессы тем или иным способом должны синхронизировать свои действия. В качестве средства синхронизации удобно использовать семафор. В пример 8.41 показана реализация так называемого критического интервала - механизма, обеспечивающего взаимное исключение разделяющих общие данные процессов. Для "создания" подобного механизма необходимо породить разделяемый сегмент памяти, присоединить его во всех процессах, которым предоставляется доступ к разделяемым данным, а также породить и проинициализировать простейший семафор. После этого монопольный доступ к разделяемой структуре обеспечивается применением P- и V-операций. #include #include #include #include #include int main (void) { struct region { pid_t fpid; } *shm_ptr; struct sembuf P = {0, -1, 0}; struct sembuf V = {0, 1, 0}; int shmid; int semid; shmid = shmget (IPC_PRIVATE, sizeof (struct region), 0777); semid = semget (IPC_PRIVATE, 1, 0777); (void) semctl (semid, 0, SETVAL, 1); switch (fork ()) { case -1: perror ("FORK"); return (1); case 0: if ((int) (shm_ptr = (struct region *) shmat (shmid, NULL, 0)) == (-1)) { perror ("CHILD-SHMAT"); return (2); } if (semop (semid, &p, 1) != 0) { perror ("CHILD-SEMOP-P"); return (3); } printf ("Процесс-потомок вошел в критический интервал\n"); shm_ptr->fpid = getpid (); /* Монопольный доступ */ printf ("Процесс-потомок перед выходом из критического интервала\n"); if (semop (semid, &V, 1) != 0) { perror ("CHILD-SEMOP-V"); return (4); } (void) shmdt (shm_ptr); return 0; } if ((int) (shm_ptr = (struct region *) shmat (shmid, NULL, 0)) == (-1)) { perror ("PARENT-SHMAT"); return (2); } if (semop (semid, &p, 1) != 0) { perror ("PARENT-SEMOP-P"); return (3); } printf ("Родительский процесс вошел в критический интервал\n"); shm_ptr->fpid = getpid (); /* Монопольный доступ */ printf ("Родительский процесс перед выходом из критического интервала\n"); if (semop (semid, &V, 1) != 0) { perror ("PARENT-SEMOP-V"); return (4); } (void) wait (NULL); printf ("Идентификатор родительского процесса: %d\n", getpid ()); printf ("Идентификатор процесса в разделяемой структуре: %d\n", shm_ptr->fpid); (void) shmdt (shm_ptr); (void) semctl (semid, 1, IPC_RMID); (void) shmctl (shmid, IPC_RMID, NULL); return 0; } Листинг 8.41. Пример работы с разделяемыми сегментами памяти. Результат работы приведенной программы может выглядеть так, как показано в пример 8.42. Родительский процесс вошел в критический интервал Родительский процесс перед выходом из критического интервала Процесс-потомок вошел в критический интервал Процесс-потомок перед выходом из критического интервала Идентификатор родительского процесса: 2161 Идентификатор процесса в разделяемой структуре: 2162 Листинг 8.42. Возможный результат синхронизации доступа к разделяемым данным. В пример 8.43 представлен пример использования разделяемых сегментов памяти в сочетании с обработкой сигнала SIGSEGV, который посылается процессу при некорректном обращении к памяти. Идея в том, чтобы создавать разделяемые сегменты, "накрывающие" запрашиваемые адреса. При некотором воображении пример можно считать основой программной реализации виртуальной памяти. /* * * * * * * * * * * * * * * * * * * * * * * * * * * */ /* Реализация "виртуальной" памяти из одного сегмента. */ /* Используются разделяемые сегменты памяти */ /* и обработка сигнала SIGSEGV */ /* * * * * * * * * * * * * * * * * * * * * * * * * * * */ #include #include #include #include #include /* Константа, зависящая от реализации */ #define SHM_BASE_ADDR 0x40014000 static int shm_id = -1; static void *shm_addr; /* Реакция на сигнал SIGSEGV. */ /* Создаем и присоединяем на чтение разделяемый сегмент, */ /* накрывающий переданный адрес. */ /* Если это не помогло, переприсоединяем сегмент на запись */ static void sigsegv_sigaction (int sig, siginfo_t *sig_info, void *addr) { struct shmid_ds shmid_ds; if (shm_id == -1) { /* Сегмента еще нет. Создадим */ if ((shm_id = shmget (IPC_PRIVATE, SHMLBA, S_IRUSR)) == -1) { perror ("SHMGET"); exit (1); } /* Присоединим сегмент на чтение */ if ((int) (shm_addr = shmat (shm_id, sig_info->si_addr, SHM_RDONLY | SHM_RND)) == (-1)) { perror ("SHMAT-RDONLY"); exit (2); } return; } else { /* Сегмент уже есть, но обращение по адресу вызвало сигнал SIGSEGV. */ /* Значит, это была попытка записи, и сегмент нужно */ /* переприсоединить на запись, поменяв соответственно режим доступа */ if (shmctl (shm_id, IPC_STAT, &shmid_ds) == -1) { perror ("SHMCTL-IPC_STAT"); exit (3); } shmid_ds.shm_perm.mode |= S_IWUSR; if (shmctl (shm_id, IPC_SET, &shmid_ds) == -1) { perror ("SHMCTL-IPC_SET"); exit (4); } (void) shmdt (shm_addr); if (shmat (shm_id, shm_addr, 0) != shm_addr) { perror ("SHMAT-RDWD"); exit (5); } } } int main (void) { char *test_ptr; struct sigaction sact; /* Установим реакцию на сигнал SIGSEGV */ (void) sigemptyset (&sact.sa_mask); sact.sa_flags = SA_SIGINFO; sact.sa_sigaction = sigsegv_sigaction; (void) sigaction (SIGSEGV, &sact, (struct sigaction *) NULL); /* Убедимся, что разделяемые сегменты инициализируются нулями */ test_ptr = (char *) (SHM_BASE_ADDR + 3); printf ("Результат попытки чтения до записи: %x\n", *test_ptr); /* Попробуем записать */ *test_ptr = 'A'; printf ("Результат попытки чтения после записи: %x\n", *test_ptr); return (shmctl (shm_id, IPC_RMID, NULL)); } Листинг 8.43. Пример работы с разделяемыми сегментами памяти и сигналами. Обратим внимание на использование флагов округления адреса присоединения разделяемого сегмента (SHM_RND) и присоединения только на чтение (SHM_RDONLY), а также обработчика сигналов, задаваемого полем sa_sigaction структуры типа sigaction (в сочетании с флагом SA_SIGINFO) и имеющего доступ к расширенной информации о сигнале и его причинах. |