2011. Машбук (1114722), страница 41
Текст из файла (страница 41)
В этом случаепроцесс, осуществляющий чтение, может выбрать из канала все оставшиесяданные и признак конца файла, благодаря которому блокирования при чтении вэтом случае не происходит. Соответственно, если заблокированы два и болеепроцесса на чтение, то порядок разблокировки определяется конкретнойреализацией.При записи в канал:Если процесс пытается записать большее число байтов, чем доступное свободноепространство канала (но не превышающее предельный размер канала), тозаписывается возможное количество данных, после чего процесс, осуществляющийзапись, блокируется до тех пор, пока в канале не появится достаточное количествоместа для завершения операции записи.Процесс может избежать такого блокирования, изменив для канала режимблокировки с использованием системного вызова fcntl().
В неблокирующем148режиме в ситуации, описанной выше, будет записано возможное количестводанных, и управление будет сразу возвращено процессу.Если же процесс пытается записать в канал порцию данных, превышающуюпредельный размер канала, то будет записано доступное количество данных, послечего процесс заблокируется до появления в канале свободного места любогоразмера (пусть даже и всего 1 байт), затем процесс разблокируется, вновьпроизводит запись на доступное место в канале, и если данные для записи еще неисчерпаны, вновь блокируется до появления свободного места и т.д., пока не будутзаписаны все данные, после чего происходит возврат из вызова write().Если процесс пытается осуществить запись в канал, с которым не ассоциирован ниодин дескриптор чтения, то он получает сигнал SIGPIPE (тем самым ОСуведомляет его о недопустимости такой операции).В стандартной ситуации (при отсутствии переполнения) система гарантируетатомарность операции записи, т.
е. при одновременной записи нескольких процессов вканал их данные не перемешиваются.В общем случае возможна многонаправленная работа процессов с каналом, т.е.возможна ситуация, когда с одним и тем же каналом взаимодействуют два и болеепроцесса, и каждый из взаимодействующих каналов пишет информацию в канал и читаетиз канала. Но традиционной схемой организации работы с каналом являетсяоднонаправленная организация, когда канал связывает два (в большинстве случаев) илинесколько взаимодействующих процессов, каждый из которых может либо читать, либописать в канал.Процесс-отецПроцесс-сын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 канал в обе стороны, процессам не хватает синхронизации. Исправим это.Итак, пусть есть два процесса (родительский и сыновний), которые через каналбудут некоторое предопределенное число раз перекидывать «мячик»-счетчик,подсчитывающий количество своих бросков в канал. Извещение процесса о полученииуправления (когда он может взять «мячик» из канала, увеличить своё значение на 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)); /* старт.