ВУЗ: Не указан
Категория: Не указан
Дисциплина: Не указана
Добавлен: 06.12.2023
Просмотров: 34
Скачиваний: 1
ВНИМАНИЕ! Если данный файл нарушает Ваши авторские права, то обязательно сообщите нам.
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) и имеющего доступ к расширенной информации о сигнале и его причинах.
1 2 3 4 5