Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Конспект Граур.doc
Скачиваний:
59
Добавлен:
14.11.2019
Размер:
3.7 Mб
Скачать

Пример. Реализация конвейера.

. Конвейер – это две программы (два процесса), которые исполняются параллельно и при этом стандартный вывод первой программы посылается на стандартный ввод второй программы, т.е. по мере того, как 1-й процесс генерирует свой вывод, он сразу же выдается на ввод второму процессу. Реализуется это очень легко с помощью каналов. Первым делом порождается канал, затем происходит порождение процесса потомка. В процессе потомке с помощью системного вызова dup2(), записывающая сторона канала дублируется на стандартный вывод, после чего системный вызов dup2() открывает второй дескриптор и теперь дескриптор с номером 1, описывающий стандартный вывод, будет смотреть в канал (весь вывод будет помешен в канал) после чего закрываются ненужные дескрипторы, записывающий в канал и читающий, и вызывается замена тела процесса на ту программу, которая собственно является первой программой. В процессе отце происходит всё наоборот, здесь в читающей стороне канала открывается второй дескриптор с номером 0 (стандартный ввод), это означает, что в дальнейшем весь стандартный ввод будет браться из канала, происходит тоже самое, закрываются ненужные дескрипторы, записывающий в канал и читающий, и происходит замена тела программы на программу wc.

Пример реализации конвейера print|wcвывод программы print будет подаваться на вход программы wc. Программа print печатает некоторый текст. Программа wc считает количество прочитанных строк, слов и символов.

#include <sys/types.h>

#include <unistd.h>

#include <stdio.h>

int main(int argc, char **argv)

{

int fd[2];

pipe(fd); /*организован канал*/

if (fork())

{

/*процесс-родитель*/

dup2(fd[1], 1); /* отождествили стандартный вывод с файловым дескриптором канала, предназначенным для записи */

close(fd[1]); /* закрыли файловый дескриптор канала, предназначенный для записи */

close(fd[0]); /* закрыли файловый дескриптор канала, предназначенный для чтения */

exelp(“print”, ”print”, 0); /* запустили программу print */

}

/*процесс-потомок*/

dup2(fd[0], 0); /* отождествили стандартный ввод с файловым дескриптором канала, предназначенным для чтения*/

close(fd[0]); /* закрыли файловый дескриптор канала, предназначенный для чтения */

close(fd[1]); /* закрыли файловый дескриптор канала, предназначенный для записи */

execl(“/usr/bin/wc”, ”wc”, 0); /* запустили программу wc */

}

Пример. Совместное использование сигналов и каналов – «пинг-понг».

Следующий пример - совместное использования сигналов и каналов.

Каналы, как правило, используются как однонаправленное средство. Но их можно использовать и как двунаправленное средство, т.е. для реализации передачи данных в обоих направлениях, но в этом случае понадобятся дополнительные средства синхронизации. Средством синхронизации являются сигналы. Здесь процессы посылают друг другу число, всякий раз увеличивая его на 1, и когда число достигнет определенного максимума, оба процесса завершаются, т.е. фактически они передают друг другу это число определенное количество раз. Средой передачи данных служит канал, средством синхронизации для того, чтобы осуществить двустороннюю передачу данных служит сигнал. Здесь определяется константа максимального значения числа, после которого нужно выйти.

Первым делом смотрим на обработчик сигнала. Обработчик сигнала, в данном случае будет использоватьcя сигнал SIGUSER1 для синхронизации, т.е. посылать его процессу всякий раз когда пришла его очередь читать из канала, т.к. взаимные скорости выполнения отца и сына неизвестны. SIGUSER1 - это сигнал, который не соответствует какому-либо определенному событию в ОС, а его семантика определяется пользователями, т.е. это как раз тот случай, когда семантика сигнала отдается на усмотрение программисту.

#include <signal.h>

#include <sys/types.h>

#include <sys/wait.h>

#include <unistd.h>

#include <stdlib.h>

#include <stdio.h>

#define MAX_CNT 100

int target_pid, cnt;

int fd[2];

int status;

void SigHndlr(int s)

{

/* в обработчике сигнала происходит и чтение, и запись */

signal(SIGUSR1, SigHndlr);

if (cnt < MAX_CNT)

{

read(fd[0], &cnt, sizeof(int));

printf("%d \n", cnt);

cnt++;

write(fd[1], &cnt, sizeof(int));

/* посылаем сигнал второму: пора читать из канала */

kill(target_pid, SIGUSR1);

}

else

if (target_pid == getppid())

{

/* условие окончания игры проверяется потомком */

printf("Child is going to be terminated\n");

close(fd[1]); close(fd[0]);

/* завершается потомок */

exit(0);

} else

kill(target_pid, SIGUSR1);

}

int main(int argc, char **argv)

{

pipe(fd); /* организован канал */

signal (SIGUSR1, SigHndlr);

/* установлен обработчик сигнала для обоих процессов */

cnt = 0;

if (target_pid = fork())

{

/* Предку остается только ждать завершения потомка */

while(wait(&status) == -1);

printf("Parent is going to be terminated\n");

close(fd[1]); close(fd[0]);

return 0;

}

else

{

/* процесс-потомок узнает PID родителя */

target_pid = getppid();

/* потомок начинает пинг-понг */

write(fd[1], &cnt, sizeof(int));

kill(target_pid, SIGUSR1);

for(;;); /* бесконечный цикл */

}

}

Процесс – сын начинает игру. Во-первых, устанавливается target_pid, который в процессе-отце был установлен в fork(), в процессе-сыне он устанавливается с помощью вызова getppid(), который возвращает pid по требованию. И он записывает текущее значение в канал и посылает сигнал SIGUSR1 своему предку. Далее происходит бесконечный цикл. Т.е. вся обработка «пинг-понг», которая заключается в чтении данных из канала, увеличения на 1, проверки значения (не достигло ли оно своего максимума) и записи нового увеличенного значения в канал – все это происходит в обработчике канала, т.е. здесь функция main() в процессе-сыне - бесконечный цикл, а в процессе – отце wait() – ожидание завершения потомка. Процесс-сын завершается когда нашел максимум, а процесс-отец в этом случае выходит из wait(), закрывает канал и тоже выходит.

Неименованные каналы являются достаточно мощным средством передачи данных, однако, у них имеется важный недостаток, о котором уже говорилось, а именно то, что они доступны только родственным процессам. Т.е. поскольку к ним не возможен доступ по имени, а возможен только с помощью файловых дескрипторов, то нужно сначала породить канал, затем породить потомков и только таким образом они могут унаследовать дескрипторы записи и чтения в канал. Это важное ограничение, поскольку два не связанных между собой процесса, которые были порождены ранее, чем средства межпроцессного взаимодействия канал не могут использовать. Эту проблему решает средство - именованные каналы