Материал: 6262

Внимание! Если размещение файла нарушает Ваши авторские права, то обязательно сообщите нам

176

Соответственно, на листинге 5.2, приведен исходный текст программы 2, которая изменяет только второй и третий элементы массива целых чисел в раз-деляемом сегменте памяти.

Листринг 5.2 - Текст программы 2, использующей разделяемый сегмент памяти

/* Программа 2 (lab11.2.с) для иллюстрации работы с разделяемой памятью

*Мы организуем разделяемую память для массива из трех целых чисел.

*Первый элемент массива является счетчиком числа запусков программы 1,

*второй элемент массива – счетчиком числа запусков программы 2,

*третий элемент массива – счетчиком числа запусков обеих программ */

#include <sys/types.h> #include <sys/ipc.h> #include <sys/shm.h> #include <stdio.h> #include <stdlib.h> #include <errno.h>

int main()

 

{

 

int *array;

// Указатель на разделяемую память

int shmid;

// IPC дескриптор для области разделяемой памяти

int new = 1;

// Флаг необходимости инициализации элементов массива

//Имя файла, используемое для генерации ключа.

//Файл с таким именем должен существовать в текущей директории char pathname[] = "/home/upk/lab11";

key_t key;

// IPC ключ

//Генерируем IPC ключ из имени файла и номера экземпляра области

//разделяемой памяти 0

if((key = ftok(pathname,1)) < 0){

perror("lab11.2 - Не могу сгенерировать ключ..."); exit(-1);

}

//Пытаемся эксклюзивно создать разделяемую память для сгенерированного

//ключа, т.е. если для этого ключа она уже существует, системный вызов

//вернет отрицательное значение. Размер памяти определяем как размер

//массива из трех целых переменных, права доступа 0666 – чтение и

//запись разрешены для всех

if((shmid = shmget(key, 3*sizeof(int), 0666|IPC_CREAT|IPC_EXCL)) < 0){

//В случае возникновения ошибки пытаемся определить:

//возникла ли она из-за того, что сегмент разделяемой

//памяти уже существует или по другой причине

if(errno != EEXIST){

// Если по другой причине – прекращаем работу perror("lab11.2 - Не могу открыть разделяемую память: "); exit(-1);

}else {

//Если из-за того, что разделяемая память уже существует,

//то пытаемся получить ее IPC дескриптор и, в случае удачи,

//сбрасываем флаг необходимости инициализации элементов массива if((shmid = shmget(key, 3*sizeof(int), 0)) < 0){

perror("lab11.2 - Не могу найти разделяемую память: "); exit(-1);

}

new = 0;

}

}

//Пытаемся отобразить разделяемую память в адресное пространство текущего

//процесса. Обратите внимание на то, что для правильного сравнения мы

//явно преобразовываем значение -1 к указателю на целое.

177

if((array = (int *)shmat(shmid, NULL, 0)) == (int *)(-1)){ perror("lab11.2 - Не могу подсоединить разделяемую память: "); exit(-1);

}

//В зависимости от значения флага new либо инициализируем массив,

//либо увеличиваем соответствующие счетчики

if(new){

array[0] = 0; array[1] = 1; array[2] = 1;

} else {

array[1] += 1; array[2] += 1;

}

//Печатаем новые значения счетчиков, удаляем разделяемую память из

//адресного пространства текущего процесса и завершаем работу

printf("lab11.2: Программа 1 была запущена %d раз, программа 2 - %d раз, Общее - %d раз\n", array[0], array[1], array[2]);

if(shmdt(array) < 0){

perror("lab11.2 - Не могу отсоединить разделяемую память: "); exit(-1);

}

return 0;

}

Замечание

После запуска, каждая из программ изменяет свою часть разделяемого сегмента памяти и завершает свою работу, но созданный сегмент остается в ОС и любая запущенная программа может получить к нему доступ. В частности, можно воспользоваться утилитой ipcs.

Отсоединение, ранее подключенного сегмента разделяемой памяти, осуществляется системным вызовом:

int shmdt (const void * shmaddr);

где shmaddr - задает начальный адрес отсоединяемого сегмента.

При успешном завершении функции, возвращется результат равеный 0 и обновляется содержимое структуры shmid_ds:

shm_dtime устанавливается в текущее время;

shm_lpid устанавливается в идентификатор вызывающего процесса;

shm_nattch уменьшается на 1; если это значение становится равным 0, а сег-

мент помечен на удаление, то сегмент удаляется из памяти; в любом случае, освобождается занятая ранее этим сегментом область памяти в адресном пространстве процесса.

Вслучае неудачи, возвращается -1 и в переменную errno устанавливается код

ошибки:

EACCES — вызывающий процесс не имеет нужных прав доступа;

EIDRM - удален идентификатор shmid;

EINVAL - неправильный идентификатор shmid;

ENOMEM - невозможно выделить память для дескриптора или таблицы стра-

178

ниц.

Замечание

Перед использованием функции shmdt(), отсоединяемый сегмент разделяемой памяти должен быть присоединен с помощью функции shmget().

Общее управление сегментами разделяемой памяти, с использованием структуры типа shmid_ds, осуществляется посредством системного вызова:

int shmctl(int shmid, int cmd, struct shmid_ds *buf);

где shmid - является дескриптором System V IPC для сегмента разделяемой памяти или - значением, которое вернул системный вызов shmget(...), при создании сегмента или при его поиске по ключу;

cmd - определяет управляющие команды:

IPC_STAT - получить информацию о состоянии разделяемого сегмента, которая копируется в буфер buf;

IPC_SET - переустановить характеристики разделяемого сегмента памяти по информации содержащейся в буфере buf;

IPC_RMID - удалить разделяемый сегмент, причем пользователь должен быть

владельцем, создателем или суперпользователем процесса; buf NULL или указатель на структуру типа shmid_ds.

При удачном выполнении, - возвращается 0, а при ошибке -1, причем переменной errno присваиваются следующие значения:

EACCES - возникает, если запрашивается IPC_STAT, а shm_perm.modes не дает доступа msqid к чтению;

EFAULT - аргумент cmd равен IPC_SET или IPC_STAT, а адрес, указываемый buf, недоступен;

EINVAL - эта ошибка происходит, если shmid является неверным идентификатором сегмента или cmd является неправильной командой;

EIDRM - эта ошибка возвращается, если shmid указывает на удаленный идентификатор;

EPERM - эта ошибка возвращается, если была произведена попытка выпол-

нить IPC_SET или IPC_RMID, эффективный идентификатор вызывающего процессы не является идентификатором создателя, данным в shm_perm.cuid, владельца, в соответствие с shm_perm.uid, или суперпользователя;

EOVERFLOW - возвращается если запрашивается IPC_STAT, а значения gid

или uid слишком велики для помещения в структуру, на которую указывает buf.

179

5.3 Задача о читателях и писателях

Аппарат разделяемых сегментов памяти предоставляет нескольким процессам возможность одновременного доступа к общей области памяти:

Ранее, мы рассмотрели доступ к разделяемой памяти, который не требовал какого-либо согласования действий между процессами.

Обеспечивая корректность доступа, процессы тем или иным способом должны синхронизировать свои действия.

В качестве средства синхронизации удобно использовать семафоры.

Чтобы правильно использовать семафоры, при доступе к разделяемым сегментам памяти, необходимо:

тщательно проанализировать задачу и выделить в процессах критические интервалы (области прогаммы);

определить механизмы, обеспечивающие взаимное исключение разделяющих общие данные процессов.

Вкачестве учебного примера, демострирующего совместное использование синхронизации и разделяемые сегменты памяти, рассмотрим задачу «Читатели-писа- тели». Общая интерпретация этой задачи — следующая.

Писатель, владея публичным ресурсом, периодически пишет на нем книги:

для написания книги, требуется случайный интервал времени twrite;

для обдумывания новой книги, требуется случайный интервал времени tsleep. Читатели, являясь последовательностью процессов, возникающих через случайные интервалы времени tsleep, обращаются к публичному ресурсу писателя:

читают, если публикация имеется, на что требуется случайный интервал времени tread;

завершают работу, если публикация отсутствует.

Общие требования:

процессы-читатели имеют одновременный доступ на операцию чтения, но обязаны ждать, пока процесс-писатель не закончит свою работу;

процесс-писатель должен дождаться завершения процесса чтения читате-

лей, но не допускает к чтению процессы, которые пришли во время его написания новой книги.

Представим решение данной задачи с помощью одного разделяемого сегмента памяти shareseg и массива из двух семафоров sembuf[2]:

sembuf[0] — число читателей, приступивших к чтению;

sembuf[1] — значение 0 — можно читать.

Алгоритм процесса-читателя, представленный на листинге 5.3:

создаются, если не созданы, ключи key1 и key2, массив семафоров sembuf[2] и сегмент разделяемой памяти shareseg;

процесс-читатель, в бесконечном цикле, через случайный интервал времени

180 tsleep порождает дочерние процессы;

каждый дочерний процесс: ожидает возможности чтения, а затем —

завершает работу, если shareseg=0, или читает случайное время tread, если shareseg>0.

Листинг 5.3 Алгоритм, моделирующий действия процесса-читателя

#include <unistd.h> #include <stdlib.h> #include <stdio.h> #include <sys/sem.h> #include <sys/shm.h> #include <sys/types.h> #include <sys/wait.h>

/* Процесс одного читателя */

#define tsleep (rand () % 3 + 1) #define tread (rand () % 5 + 1)

int main (int argc, char *argv []) {

key_t key1;

// Ключ набора семафоров

key_t key2;

// IPC ключ сегмента разделяемой памяти

int semid;

// Идентификатор набора семафоров

int shmid;

// Идентификатор разделяемой памяти

int *array;

// Указатель на разделяемую память

int id;

// Идентификатор процесса-читателя

int no = 0;

// Номер читателя

int ts;

// Интервал времени между порождением дочерних процессов

int tr;

// Время продолжительности очередного чтения

struct sembuf sembuf[2];

// Доступ к семафору

sembuf[0].sem_flg = 0;

// Операции

с блокировкой

sembuf[1].sem_flg = 0;

// Операции

с блокировкой

int errsem;

// Ошибка возврата при установке семафора

puts("lab11.3 - Процессы-читатели: стартовала..."); // Создание и инициализация набора 2-х семафоров

if((key1 = ftok ("/home/upk/lab11", 2)) < 0){ printf("lab11.4: Не могу сгенерировать ключ: key1\n"); exit(-1);

}

if ((semid = semget (key1, 2, 0666 | IPC_CREAT)) < 0) { perror ("SEMGET");

return (-1);

}

/* Генерируем IPC ключ из имени файла и номера экземпляра области * разделяемой памяти 1 */

if((key2 = ftok("/home/upk/lab11",3)) < 0){ perror("lab11.3: Не могу сгенерировать ключ: key2\n"); exit(-1);

}

/* Пытаемся получить идентификатор разделяемой памяти.

*Размер памяти определяем как размер массива из одного целого числа,

*права доступа 0666 – чтение и запись разрешены для всех */

if((shmid = shmget(key2, sizeof(int), 0666|IPC_CREAT)) < 0){ /* В случае возникновения ошибки — завершаем работу */ perror("lab11.3: Не могу открыть разделяемую память\n"); exit(-1);

Источник: https://studfile.net/preview/16438530/