181
}
//Отображаем разделяемую память в адресное пространство текущего
//процесса. Обратите внимание на то, что для правильного сравнения мы
//явно преобразовываем значение -1 к указателю на целое.
if((array = (int *)shmat(shmid, NULL, 0)) == (int *)(-1)){ perror("lab11.3: Не могу подсоединить разделяемую память\n"); exit(-1);
}
while(1){
// Процессы-читатели создаются через случайное время ts ts = tsleep; sleep (ts);
no++;
if((id = fork()) < 0){
printf("Ошибка fork() для процесса %d\n", no); continue;
}
if(id == 0) {
// Дочерние процессы-читатели
printf ("Стартовал читатель %d: semid=%d shmid=%d\n", no, semid, shmid);
// Читатель пытается начать |
чтение |
|||
sembuf[0].sem_num = 0; |
// Увеличивает значение семафора №0 на 1 |
|||
sembuf[0].sem_op |
= 1; |
|
|
|
sembuf[1].sem_num = |
1; |
// Ждет нуля семафора №1 |
||
sembuf[1].sem_op |
= |
0; |
|
|
if (semop (semid, sembuf, 2) < 0) {
perror ("SEMOP - читатель ожидание 0:\n"); exit (-1);
}
if(*array == 0){
printf ("Читатель %d: нет информации для чтения...\n", no); semctl (semid, 0, SETVAL, 0);
exit(-1);
}
// Читает
printf ("Читатель %d читает: книгу %d\n", no, array[0]); tr = tread; sleep (tr);
// Завершает чтение: уменьшает значение семафора №0 на 1 errsem = semctl (semid, 0, GETVAL);
if (errsem < 0) {
perror ("SETVAL0"); exit (-2);
}
if(errsem > 0) { sembuf[0].sem_num = 0; sembuf[0].sem_op = -1;
if (semop (semid, sembuf, 1) < 0) {
perror ("SEMOP - немогу уменьшить семафор:\n"); exit (-3);
}
}
printf("Читатель %i: завершил чтение и вышел...\n", no); exit(0);
}
// Удаляем "зомби"
while((id=waitpid(-1, NULL, WNOHANG)) > 0) // Ждем без блокировки printf("ДП id=%i - завершил работу...\n", id);
}
while((id=wait(NULL)) > 0){
printf("ДП id=%i - завершил работу...\n", id);
}
puts("lab11.3 - Все процессы-читатели: завершили работу..."); return 0;
}
182
Замечание
Для правильного запуска процессов-читателей — смотри пункт 5.5.2 по выполнению лабораторной работы.
Алгоритм процесса-писателя, представлен на листинге 5.4:
•создаются, если не созданы, ключи key1 и key2, массив семафоров sembuf[2] и сегмент разделяемой памяти shareseg;
•процесс-писатель обнуляет значения семафоров независимо от того, создал он их или использует уже созданные;
•выполняется цикл по количеству задуманных публикаций;
вкаждом цикле:
•блокируется подключение новых процессов-читателей;
•ожидается завершение чтения, - уже читающих;
•объявляется о написании новой книги и выполняется сам процесс — случайный интервал времени twrite;
•после написания книги, разрешается доступ на чтение и обдумывание
нового произведения случайный интервал времени tsleep;
после завершения всех циклов:
•ожидается завершение всех читателей и обнуляется shareseg;
•печатается сообщение о завершении работы программы и осуществляется выход.
Листинг 5.4. Алгоритм, моделирующий действия процесса-писателя
#include <unistd.h> #include <stdio.h> #include <stdlib.h> #include <sys/sem.h> #include <sys/shm.h> #include <sys/types.h> #include <sys/wait.h>
/* Программа-писатель */
#define tsleep |
(rand () % 3 + 1) |
|
#define twrite |
(rand () % 5 + 1) |
|
int main (void) { |
|
|
key_t key1; |
// Ключ набора семафоров |
|
key_t key2; |
// IPC ключ сегмента разделяемой памяти |
|
int semid; |
|
// Идентификатор набора семафоров |
int shmid; |
|
// Идентификатор разделяемой памяти |
int *array; |
// Указатель на разделяемую память |
|
int ts; |
|
// Время обдумывания нового произведения |
int tw; |
|
// Время продолжительности очередной записи |
struct |
sembuf sembuf[2]; |
// Доступ к семафору — набор из двух семафоров |
|
sembuf[0].sem_flg = 0; |
// Операции |
с блокировкой |
|
sembuf[1].sem_flg = 0; |
// Операции |
с блокировкой |
|
struct |
shmid_ds ds; |
// Структура параметров разделяемой памяти |
|
// Создание и инициализация набора 2-х семафоров if((key1 = ftok ("/home/upk/lab11", 2)) < 0){
printf("lab11.4: Не могу сгенерировать ключ: key1\n");
183
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){ printf("lab11.4: Не могу сгенерировать ключ: key2\n"); exit(-1);
}
//Пытаемся создать разделяемую память для сгенерированного ключа.
//Размер памяти определяем как размер массива из одного целого числа,
//права доступа 0666 – чтение и запись разрешены для всех
if((shmid = shmget(key2, sizeof(int), 0666|IPC_CREAT)) < 0){ /* В случае возникновения ошибки — завершаем работу */ printf("lab11.4: Не могу открыть разделяемую память\n"); exit(-1);
}
//Пытаемся отобразить разделяемую память в адресное пространство текущего
//процесса. Обратите внимание на то, что для правильного сравнения мы
//явно преобразовываем значение -1 к указателю на целое.
if((array = (int *)shmat(shmid, NULL, 0)) == (int *)(-1)){ printf("lab11.4: Не могу подсоединить разделяемую память\n"); exit(-1);
}
//Начальное значение разделяемой памяти и печать идентификаторов array[0] = 0;
printf ("Писатель стартовал: semid=%d shmid=%d\n", semid, shmid);
//Обнуление значений семафоров
if (semctl (semid, 0, SETVAL, 0) < 0) {// Обнуляем семафор №0 perror ("SETVAL0");
return (-2);
}
if (semctl (semid, 1, SETVAL, 0) < 0) {// Обнуляем семафор №1 perror ("SETVAL1");
return (-2);
}
while (array[0] < 10) { |
// Цикл из 10 публикаций |
// Остановка процессов-читателей |
|
if (semctl (semid, 1, SETVAL, 1) < 0) { perror ("SETVAL1");
return (-2);
}
// Читаем параметры разделяемой памяти if (shmctl (shmid, IPC_STAT, &ds) < 0) {
perror ("IPC_STAT:"); return (-2);
}
printf("Писатель: число подключений к разд.памяти=%i\n", (int)ds.shm_nattch);
// Писатель ожидает
printf ("Писатель ожидает завершения процесса чтения...\n"); sembuf[0].sem_num = 0; // Ждет нуля семафора №0 sembuf[0].sem_op = 0;
if (semop (semid, sembuf, 1) < 0) {
perror ("SEMOP - писатель ожидание 0:\n"); exit (-1);
}
array[0] +=1;
printf("Писатель объявляет: semid=%i shmid=%i книга=%i\n",
184
semid, shmid, array[0]); // Пишет
tw = twrite; sleep (tw);
printf ("Писатель записал: книгу %d и дает возможность ее прочесть\n", array[0]);
// Разрешение на чтение для процессов-читателей if (semctl (semid, 1, SETVAL, 0) < 0) {
perror ("SETVAL1 - set 0 to sem №1:"); return (-2);
}
// Писатель обдумывает новое произведение ts = tsleep; sleep (3*ts);
}
sembuf[0].sem_num = 0; // Ждет нуля семафора №0 sembuf[0].sem_op = 0;
if (semop (semid, sembuf, 1) < 0) {
perror ("SEMOP - писатель ожидание 0:\n"); exit (-1);
}
array[0] = 0;
//Когда цикл — завершен: Отсоединяем разделяемую память от
//адресного пространства текущего процесса и завершаем работу if(shmdt(array) < 0){
printf("lab11.4: Не могу отсоединить разделяемую память\n"); exit(-1);
}
printf("lab11.4: Нормально завершила работу...\n"); return 0;
}
Замечание
Программа-писатель работает в паре с программой-читателем, поэтому для правильного их запуска — смотри подраздел 5.5.2 по выполнению лабораторной работы.
Третьим и последним средством, входящим в System V IPC, являются очереди сообщений.
Очереди сообщений - это наиболее семантически нагруженный способ взаимодействия процессов через каналы связи, в котором на передаваемую информацию накладывается определенная структура, так что процесс, принимающий данные, может четко определить, где заканчивается одна порция информации и начинается другая.
Такая модель позволяет задействовать один и тот же канал связи для передачи данных в двух направлениях между несколькими процессами.
Очереди сообщений, как семафоры и разделяемая память, являются средством связи с непрямой адресацией, что требует:
•инициализации их, для организации взаимодействия процессов;
•специальных действий, для освобождения системных ресурсов по окончании
взаимодействия.
Пространством имен очередей сообщений является то же самое множество зна-
185
чений ключа, генерируемых с помощью функции ftok(), а для передачи данных используются системные примитивы, в виде функций send() и receive(), которым в качестве параметра передаются IPC-дескрипторы очередей сообщений, однозначно идентифицирующие эти данные во всей вычислительной системе.
Очереди сообщений имеют следующие особенности:
•располагаются в адресном пространстве ядра операционной системы в
виде однонаправленных списков и имеют ограничение по объему информации, хранящейся в каждой очереди.
•каждый элемент списка представляет собой отдельное сообщение.
•каждое сообщения имеет атрибут, называемый типом сообщения.
Выборка сообщений из очереди, соответствующая примитиву receive(), может быть выполнена тремя способами:
•В порядке FIFO, независимо от типа сообщения.
•В порядке FIFO, для сообщений конкретного типа.
•Первым выбирается сообщение с минимальным типом, не превышающим
некоторого заданного значения, пришедшее раньше других сообщений с тем же типом.
Реализация примитивов send() и receive() обеспечивает скрытое от пользователя взаимоисключение, во время помещения сообщения в очередь или его получения из очереди. Она также обеспечивает:
•блокировку процесса, при попытке выполнить примитив receive() над пустой
очередью или очередью, в которой отсутствуют сообщения запрошенного типа;
•блокировку процесса, при попытке выполнить примитив send() для очереди, в которой нет свободного места.
Замечание
Очереди сообщений, как и другие средства System V IPC, позволяют организовать взаимодействие процессов, не находящихся одновременно в вычислительной системе.
Общий набор примитивов передачи сообщений представлен четырьмя системными вызовами:
#include <types.h> #include <ipc.h> #include <msg.h>
int msgget(key_t key, int msgflg);
int msgsnd(int msqid, struct msgbuf *ptr, int length, int flag);
int msgrcv(int msqid, struct msgbuf *ptr, int length, long type, int flag); int msgctl(int msqid, int cmd, struct msqid_ds *buf);
Системный вызов msgget() предназначен для выполнения операции доступа к очереди сообщений и, в случае ее успешного завершения, возвращает дескриптор System V IPC для этой очереди: целое неотрицательное число, однозначно характеризующее очередь сообщений внутри вычислительной системы и использующееся