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
Аппарат разделяемых сегментов памяти предоставляет нескольким процессам возможность одновременного доступа к общей области памяти:
•Ранее, мы рассмотрели доступ к разделяемой памяти, который не требовал какого-либо согласования действий между процессами.
•Обеспечивая корректность доступа, процессы тем или иным способом должны синхронизировать свои действия.
•В качестве средства синхронизации удобно использовать семафоры.
Чтобы правильно использовать семафоры, при доступе к разделяемым сегментам памяти, необходимо:
•тщательно проанализировать задачу и выделить в процессах критические интервалы (области прогаммы);
•определить механизмы, обеспечивающие взаимное исключение разделяющих общие данные процессов.
Вкачестве учебного примера, демострирующего совместное использование синхронизации и разделяемые сегменты памяти, рассмотрим задачу «Читатели-писа- тели». Общая интерпретация этой задачи — следующая.
Писатель, владея публичным ресурсом, периодически пишет на нем книги:
•для написания книги, требуется случайный интервал времени 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);