Файл: Содержание занятия.rtf

ВУЗ: Не указан

Категория: Не указан

Дисциплина: Не указана

Добавлен: 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