Операционные системы 2011 (1114689), страница 42
Текст из файла (страница 42)
е. при одновременной записи нескольких процессов вканал их данные не перемешиваются.В общем случае возможна многонаправленная работа процессов с каналом, т.е.возможна ситуация, когда с одним и тем же каналом взаимодействуют два и болеепроцесса, и каждый из взаимодействующих каналов пишет информацию в канал и читаетиз канала.
Но традиционной схемой организации работы с каналом являетсяоднонаправленная организация, когда канал связывает два (в большинстве случаев) илинесколько взаимодействующих процессов, каждый из которых может либо читать, либописать в канал.Процесс-отецПроцесс-сынpipe();fd[0]fd[1]fork()fd[0]fd[1]каналчтениезаписьРис. 97. Схема взаимодействия процессов с использованием неименованногоканала.Пример. Схема организации взаимодействия процессов с использованиемканала (Рис.
97). Схема всегда такова: некоторый родительский процесс внутри себяпорождает канал, после этого идут обращения к системным вызовам fork() — создаетсядерево процессов. При этом за счет того, что при порождении процесса открытыефайловые дескрипторы наследуются, сыновний процесс также обладает файловымидескрипторами, ассоциированными с каналом, который создал его предок. За счет этогоможно организовать взаимодействие родственных процессов.В следующем примере организуется неименованный канал между отцовским исыновним процессами, причем процесс-отец будет писать в канал, а процесс-сын —читать из него.#include <sys/types.h>#include <unistd.h>149int main(int argc, char **argv){int fd[2];pipe(fd);if (fork()){/*процесс-родитель*/close(fd[0]);/*закрываемненужныйдескриптор */write (fd[1], …);…close(fd[1]);…}else{/*процесс-потомок*/close(fd[1]);/*закрываемненужныйдескриптор */while(read (fd[0], …)){…}…}}В рассмотренном примере после создания канала посредством системного вызоваpipe() и порождения сыновнего процесса посредством системного вызова fork(), отцовскийпроцесс закрывает дескриптор, открытый на чтение из канала, потом производитразличные действия, среди которых он пишет некоторую информацию в канал, после чегозакрывает дескриптор записи в канал, и, наконец, после некоторых действий завершается.Процесс-сын первым делом закрывает дескриптор записи в канал, а после этогоциклически считывает некоторые данные из канала.
Стоит обратить внимание, чтозакрытие дескриптора чтения в канал в отцовском процессе можно не делать, т.к. призавершении процесса все открытые файловые дескрипторы будут автоматически закрыты.Но в сыновнем процессе закрытие дескриптора записи в канал обязательно: в противномслучае, поскольку сыновний процесс читает данные из канала циклически (до получениякода конца файла), он не сможет получить этот код конца файла, а потому он зациклится.А код конца файла не будет помещен в канал, потому что при закрытии дескрипторазаписи в канал в отцовском процессе с каналом все еще будет ассоциирован открытыйдескриптор записи сыновнего процесса.Пример.
Реализация конвейера. Приведенный ниже пример основан на томфакте, что при порождении процесса в ОС Unix он заведомо получает три открытыхфайловых дескриптора: дескриптор стандартного ввода (этот дескриптор имеетнулевой номер), дескриптор стандартного вывода (имеет номер 1) и дескрипторстандартного потока ошибок (имеет номер 2). Обычно на стандартный ввод поступаютданные с клавиатуры, а стандартный вывод и поток ошибок отображаются на дисплеймонитора. Интерпретатор команд UNIX позволяет организовывать цепочки команд, когдастандартный вывод одной команды поступает на стандартный ввод другой команды, итакие цепочки называются конвейером команд.
В конвейере могут участвовать две иболее команды.В данном примере реализуется конвейер команд print|wc, в котором команда printосуществляет печать некоторого текста, а команда wc выводит некоторые статистическиехарактеристики входного потока (количество байт, строк и т.п.).150int main(int argc, char **argv){int fd[2];pipe(fd); /* организовываем канал */if(fork()){/* ПРОЦЕСС-РОДИТЕЛЬ *//* отождествим стандартный вывод с файловымдескриптором канала, предназначенным для записи */dup2(fd[1],1);/* закрываем файловый дескриптор канала,предназначенный для записи */close(fd[1]);/* закрываем файловый дескриптор канала,предназначенный для чтения */close(fd[0]);/* запускаем программу print */execlp(“print”,”print”,0);}/* ПРОЦЕСС-ПОТОМОК *//*отождествляем стандартный ввод с файловым дескрипторомканала, предназначенным для чтения */dup2(fd[0],0);/* закрываем файловый дескриптор канала, предназначенный длячтения */close(fd[0]);/* закрываем файловый дескриптор канала, предназначенный длязаписи */close(fd[1]);/* запускаем программу wc */execl(“/usr/bin/wc”,”wc”,0);}В приведенной программе создаётся канал, затем порождается сыновний процесс.Далее отцовский процесс обращается к системному вызову dup2(), который закрываетфайл, ассоциированный с файловым дескриптором 1 (т.е.
стандартный вывод), иассоциирует файловый дескриптор 1 с файлом, ассоциированным с дескриптором fd[1].Таким образом, теперь через первый дескриптор стандартный вывод будет направляться вканал. После этого файловые дескрипторы fd[0] и fd[1] нам более не нужны, мы ихзакрываем, а в родительском процессе остается ассоциированным с каналом файловыйдескриптор с номером 1. После этого происходит обращение к системному вызовуexeclp(), который запустит команду print, у которой выходная информация будет писатьсяв канал.В сыновнем процессе производятся аналогичные действия, только здесь идетработа со стандартным вводом, т.е.
с нулевым файловым дескриптором. И в концезапускается команда wc, у которой входная информация будет поступать из канала. Темсамым мы запустили конвейер этих команд: синхронизация этих процессов будетпроисходит за счет реализованной в механизме неименованных каналов стратегии FIFO.Пример. «Пинг-понг» (совместное использование сигналов и каналов – длясинхронизации используется канал и аппарат сигналов). В данном примереобеспечивается корректная организация двунаправленной работы, когда каждый извзаимодействующих процессов может и читать из канала, и писать в канал.151Итак, пусть есть два процесса (родительский и сыновний), которые через каналбудут некоторое предопределенное число раз перекидывать «мячик»-счетчик,подсчитывающий количество своих бросков в канал.
Извещение процесса о полученииуправления (когда он может взять «мячик» из канала, увеличить своё значение на 1 иснова бросить в канал) будет происходить на основе механизма сигналов. Таким образом,на канал возлагается роль среды двусторонней передачи информации, для синхронизациииспользуется канал и аппарат сигналов.#include#include#include#include#include#include<signal.h><sys/types.h><sys/wait.h><unistd.h><stdlib.h><stdio.h>#define MAX_CNT 100int target_pid, cnt;int fd[2];int status;void SigHndlr(int s){/* в обработчике сигнала происходит и чтение, и запись */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);}elsekill(target_pid, SIGUSR1);}int main(int argc, char **argv){/* организуем канал */pipe(fd);/* устанавливаем обработчик сигнала для обоих процессов */signal(SIGUSR1, SigHndlr);cnt = 0;152if(target_pid = fork()){/* в этот момент присваивание target_pid:= fork() ужепроработало */write(fd[1], &cnt, sizeof(int)); /* старт.
синхр.*//* Предку остается только ждать завершенияпотомка */while(wait(&status)== -1);printf("Parent is going to be terminated\n");close(fd[1]);close(fd[0]);return 0;}else{/* блокировка до того момента, пока присваиваниеtarget_pid:= fork() не проработает в отце */read(fd[0], &cnt, sizeof(int)); /* старт. синхр.*//* процесс-потомок узнает PID родителя */target_pid = getppid();/* потомок начинает пинг-понг */write(fd[1], &cnt, sizeof(int));kill(target_pid, SIGUSR1);for(;;); /* бесконечный цикл */}}Для синхронизации взаимодействующих процессов используется сигнал SIGUSR1.Обычно в операционных системах присутствуют сигналы, которые не ассоциированы ссобытиями, происходящими в системе, и которые процессы могут использовать по своемуусмотрению. Количество таких пользовательских сигналов зависит от конкретнойреализации.
В приведенном примере реализован следующий принцип работы: процессполучает сигнал SIGUSR1, берет счетчик из канала, увеличивает его на 1 и сновапомещает в канал, после чего посылает своему напарнику сигнал SIGUSR1. Далеедействия повторяются, пока счетчик не возрастет до некоторой фиксированной величиныMAX_CNT, после чего происходят завершения процессов.В качестве счетчика в данной программе выступает целочисленная переменная cnt.Рассмотрим функцию-обработчик сигнала SIGUSR1. В ней проверяется, не превзошло лизначение cnt величины MAX_CNT.