- •Введение
- •1 Тема 7. Подсистема управления вводом-выводом
- •1.1 Язык С как стандарт взаимодействия с ОС
- •1.2 Системные операции для работы с файловой системой
- •1.2.1 Системные вызовы open() и close()
- •1.2.2 Системные вызовы read() и write()
- •1.2.3 Системный вызов lseek()
- •1.3 Создание специальных файлов
- •1.4 Запрос информации о статусе файлов
- •1.5 Каналы
- •1.5.1 Полудуплексные каналы UNIX
- •1.5.2 Именованные каналы FIFO
- •1.6 Дублирование дескрипторов файлов
- •1.7 Монтирование и демонтирование ФС
- •1.8 Ссылки на имена файлов
- •1.9 Лабораторная работа по теме №7
- •1.9.1 Интегрированная среда разработки Eclipse
- •1.9.2 Список заданий выполняемых работ
- •1.9.3 Проблема типов в языке С
- •1.9.4 Анализ структуры MBR блочных устройств
- •1.9.5 Запрос информации о статусе файлов
- •1.9.6 Неименованные каналы ядра ОС
- •1.9.7 Именованные каналы FIFO
- •1.9.8 Монтирование flashUSB
- •1.9.9 Работа с именами файлов
- •2 Тема 8. Подсистема управления памятью
- •2.1 Классификация способов управления ОЗУ
- •2.2 Программный и аппаратный способы адресации памяти
- •2.3 Страничная и сегментная адресации памяти
- •2.4 Комбинированный способ адресации памяти
- •2.5 Лабораторная работа по теме №8
- •2.5.1 Структура поцесса
- •2.5.2 Определяемые сегменты процесса
- •2.5.3 Создание и удаление процессов из памяти
- •2.5.4 Динамическое выделение и освобождение памяти процесса
- •3 Тема 9. Базовое взаимодействие процессов
- •3.1 Подсистема управления процессами
- •3.2 Синхронизация процессов
- •3.3 Стандарты POSIX
- •3.4 Системные вызовы ОС по управлению процессами
- •3.5 Системный вызов fork() и каналы процесса
- •1.5.1 Пример использования каналов процессов
- •1.5.2 Имитация конвейеров языка shell
- •3.6 Нити (Threads)
- •3.7 Сигналы POSIX
- •3.8 Лабораторная работа по теме №9
- •3.8.1 Системные вызовы общей группы
- •3.8.2 Управление потоками процессов
- •3.8.3 Обработка сигналов ОС
- •4 Тема 10. Асинхронное взаиодействие процессов
- •4.1 Проблемы распределения ресурсов ОС
- •4.2 Системный пакет IPC
- •4.3 Утилиты управления средствами пакета IPC
- •Утилита ipcmk
- •Утилита ipcs
- •Утлита ipcrm
- •4.4 Семафоры
- •4.5 Задача об обедающих философах
- •4.5.1 Описание задачи
- •4.5.2 Выбор стратегии решения
- •4.5.3 Модель философа
- •4.5.4 Программа-монитор
- •4.6 Лабораторная работа по теме №10
- •4.6.1 Синхронизация двух процессов
- •4.6.2 Задача «Обедающие философы»
- •5 Тема 11. Эффективное взаиодействие процессов
- •5.1 Прикладные средства пакета IPC
- •5.2 Разделяемые сегменты памяти
- •5.3 Задача о читателях и писателях
- •5.4 Передача сообщений
- •5.5 Лабораторная работа по теме №11
- •5.5.1 Задачи с разделяемыми сегментами памяти
- •5.5.2 Программы передачи сообщений
- •6 Тема 12. Системная шина D-Bus
- •6.1 Графические среды ОС
- •6.2 Рабочий стол пользователя
- •6.3 Различия графических сред ОС
- •6.4 X-сервер UNIX
- •6.5 Архитектура шины D-Bus
- •6.5.2 Бибиотека libdbus
- •6.5.3 Проекции ПО D-Bus на языки программирования
- •6.6 Лабораторная работа по теме №12
- •6.6.1 Утилита qdbus
- •6.6.2 Взаимодействие через шину с приложением evince
- •Заключение
- •Список использованных источников
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);
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",