Порядок выполнения работы
1. Изучить теоретическую часть лабораторной работы.
2. Организовать функционирование процессов следующей структуры:
Отец
Сын 1
Сын 2
Процессы определяют свою работу выводом сообщений вида : N pid (N – текущий номер сообщения) на экран. “Отец” периодически, по очереди, посылает сигнал SIGUSR1 “сыновьям”. “Сыновья” периодически посылают сигнал SIGUSR2 “отцу”. Написать функции-обработчики сигналов, которые при получении сигнала выводят сообщение о получении сигнала на экран. При получении/посылке сигнала они выводят соответствующее сообщение: N pid сын n get/put SIGUSRm.
Предусмотреть механизм для определения “отцом”, от кого из “сыновей” получен сигнал.
3. Для процессов написать функции-обработчики сигналов от клавиатуры, которые запрашивали бы подтверждение на завершение работы при получении такого сигнала.
Лабораторная работа №5
Использование каналов
Цель работы - изучение механизма взаимодействия процессов с использованием каналов.
Теоретическая часть
Каналы являются одной из самых сильных и характерных особенностей ОС UNIX, доступных даже с уровня командного интерпретатора. Они позволяют легко соединять между собой произвольные последовательности команд. Поэтому программы UNIX могут разрабатываться как простые инструменты, осуществляющие чтение из стандартного ввода, запись в стандартный вывод и выполняющие одну, четко определенную задачу. При помощи каналов из этих основных блоков могут быть построены более сложные командные строки.
Каналы создаются в программе при помощи системного вызова pipe. В случае удачного завершения вызов сообщает два дескриптора файла: один – для записи в канал, а другой – для чтения из него. Вызов pipe определяется следующим образом:
#include
int pipe (int filedes[2]);
Переменная filedes является массивом из двух целых чисел, который будет содержать дескрипторы файлов, обозначающие канал. После успешного вызова filedes[0] будет открыт для чтения из канала, а filedes[1] – для записи в канал.
В случае неудачи вызов pipe вернет значение -1. Это может произойти, если в момент вызова произойдет превышение максимально возможного числа дескрипторов файлов, которые могут быть одновременно открыты процессами пользователя (в этом случае переменная errno будет содержать значение EMFILE), или если произойдет переполнение таблицы открытых файлов в ядре (в этом случае переменная errno будет содержать значение ENFILE).
После создания канала с ним можно работать просто при помощи вызовов read и write. Следующий пример демонстрирует это: он создает канал, записывает в него три сообщения, а затем считывает их из канала:
#include
#include
/*Эти строки заканчиваются нулевым символом*/
#define MSGSIZE 16
char *msg1 = “hello, world #1”;
char *msg2 = “hello, world #2”;
char *msg3 = “hello, world #3”;
main ()
{
char inbuf [MSGSIZE];
int p [2], j;
/*Открыть канал*/
if (pipe (p) == -1) {
perror (“Ошибка вызова pipe”);
exit (1);
}
/*Запись в канал*/
write (p[1], msg1, MSGSIZE);
write (p[1], msg2, MSGSIZE);
write (p[1], msg3, MSGSIZE);
/*Чтение из канала*/
for (j=0; j<3; j++)
{
read (p[0], inbuf, MSGSIZE);
printf (“%s\n”, inbuf);
}
exit (0);
}
На выходе программы получим:
hello, world #1
hello, world #2
hello, world #3
Каналы обращаются с данными в порядке «первый вошел – первым вышел» (FIFO). Этот порядок нельзя изменить, поскольку вызов lseek не работает с каналами.
Размеры блоков при записи в канал и чтении из него необязательно должны быть одинаковыми, хотя в нашем примере это и было так. Можно, например, писать в канал блоками по 512 байт, а затем считывать из него по 1 символу, так же как и в случае обычного файла. Тем не менее, использование блоков фиксированного размера дает определенные преимущества.
Работа примера показана графически на рис. 5.1. Эта диаграмма позволяет более ясно представить, что процесс только посылает данные сам себе, используя канал в качестве некой разновидности механизма обратной связи. Это может показаться бессмысленным, поскольку процесс общается только сам с собой.
Достарыңызбен бөлісу: |