2011. Машбук (1114722), страница 42
Текст из файла (страница 42)
синхр.*//* процесс-потомок узнает 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. Если нет, то из канала читается новое значение cnt,происходит печать нового значения, после этого увеличивается на 1 значение cnt, и онопомещается в канал, а напарнику посылается сигнал SIGUSR1 (посредством системноговызова kill()).Если же значение cnt оказалось не меньше MAX_CNT, то начинаются действия позавершению процессов, при этом первым должен завершиться сыновний процесс. Дляэтого проверяется идентификатор процесса-напарника (target_pid) на равенствоидентификатору родительского процесса (значению, возвращаемому системным вызовомgetppid()).
Если это так, то в данный момент управление находится у сыновнего процесса,который и инициализирует завершение. Он печатает сообщение о своем завершении,закрывает дескрипторы, ассоциированные с каналом, и завершается посредствомсистемного вызова exit(). Если же указанное условие ложно, то в данный моментуправление находится у отцовского процесса, который сразу же передает его сыновнемупроцессу, посылая сигнал SIGUSR1, при этом ничего не записывая в канал, поскольку усына уже имеется значение переменной cnt.153В самой программе (функции main) происходит организация канала, установкаобработчика сигнала SIGUSR1 и инициализация счетчика нулевым значением. Затемпроисходит обращение к системному вызову fork(), значение которого присваиваетсяпеременной целевого идентификатора target_pid.
Если мы находимся в родительскомпроцессе, то в этой переменной будет находиться идентификатор сыновнего процесса.После этого отцовский процесс начинает ожидать завершения сыновнего процессапосредством обращения к системному вызову wait(). Дождавшись завершения, отцовскийпроцесс выводит сообщение о своем завершении, закрывает дескрипторы,ассоциированные с каналом, и завершается.Если же системный вызов fork() возвращает нулевое значение, то это означает, чтов данный момент мы находимся в сыновнем процессе, поэтому первым делом переменнойtarget_pid присваивается значение идентификатора родительского процесса посредствомобращения к системному вызову getppid().
После чего процесс пишет в канал значениепеременной cnt, посылает отцовскому процессу сигнал SIGUSR1, тем самым, начиная«игру», и входит в бесконечный цикл.Пример. «Пинг-понг» (совместное использование сигналов и каналов – длясинхронизации используется только аппарат сигналов).#include <signal.h>#include <sys/types.h>#include <sys/wait.h>#include <unistd.h>#include <stdlib.h>#include <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("pid=%d cnt=%d \n", target_pid, cnt);cnt++;write(fd[1], &cnt, sizeof(int));kill(target_pid, SIGUSR1);}elseif (target_pid == getppid()){printf("Child is going to be terminated\n");close(fd[1]); close(fd[0]);exit(0);}elsekill(target_pid, SIGUSR1);}void Init(int s)154{target_pid = getppid();write(fd[1], &cnt, sizeof(int));kill(target_pid, SIGUSR1);}int main(int argc, char **argv){pipe(fd);signal (SIGUSR2, Init);signal (SIGUSR1, SigHndlr);cnt = 0;if ((target_pid = fork()) > 0){/* в этот момент присваивание target_pid:= fork()проработало *//* отсылаем сыну сигнал для старта */kill(target_pid, SIGUSR2);ужеwhile(wait(&status)== -1);printf("Parent is going to be terminated\n");close(fd[1]); close(fd[0]);return 0;}else{for(;;);}}3.1.4 Именованные каналыРассмотренные выше программные каналы имеют важное ограничение: так какдоступ к ним возможен только посредством дескрипторов, возвращаемых припорождении канала, необходимым условием взаимодействия процессов через каналявляется передача этих дескрипторов по наследству при порождении процесса.Именованные каналы (FIFO-файлы) расширяют свою область применения за счет того,что подключиться к ним может любой процесс в любое время, в том числе и послесоздания канала.
Это возможно благодаря наличию у них имен.FIFO-файл (именованный канал) представляет собой специальный тип файла вфайловой системе UNIX, который обладает всеми атрибутами файла, такими как имявладельца, права доступа и размер. Особенностью этих файлов является их организацияпо стратегии FIFO (т.е. невозможны операции, связанные с перемещением файловогоуказателя).Таким образом, файлы FIFO могут использоваться для организации взаимодействияпроцессов, при этом в отличие от неименованных каналов эти файлы могут существоватьнезависимо от процессов, взаимодействующих через них. Эти файлы хранятся на внешнихзапоминающих устройствах, поэтому возможно открыть этот файл, записать в негоинформацию, а через любой промежуток времени (в течение которого допустимыперезагрузки системы) прочитать записанную информацию.155Для создания файлов FIFO в различных реализациях используются разныесистемные вызовы, одним из которых может являться mkfifo()3.#include <sys/types.h>#include <sys/stat.h>int mkfifo (char *pathname, mode_t mode);В этом вызове первый аргумент представляет собой имя создаваемого канала, вовтором аргументе указываются режимы открытия, устанавливаются права доступа кканалу для владельца, группы и прочих пользователей, и кроме того, устанавливаетсяфлаг, указывающий на то, что создаваемый объект является именно FIFO-файлом (вразных версиях ОС он может иметь разное символьное обозначение – S_IFIFO илиI_FIFO).После создания именованного канала любой процесс может установить с ним связьпосредством системного вызова open().
При этом действуют следующие правила:если процесс открывает FIFO-файл для чтения, он блокируется до тех пор, пока какойлибо процесс не откроет тот же канал на запись;если процесс открывает FIFO-файл на запись, он будет заблокирован до тех пор, покакакой-либо процесс не откроет тот же канал на чтение;процесс может избежать такого блокирования, указав в вызове open() специальныйфлаг (в разных версиях ОС он может иметь разное символьное обозначение —O_NONBLOCK или O_NDELAY). В этом случае в ситуациях, описанных выше, вызовopen() сразу же вернет управление процессу.Правила работы с именованными каналами (в частности, особенности операцийчтения-записи) полностью аналогичны правилам работы с неименованным каналами.Ниже рассматривается пример, где один из процессов является сервером,предоставляющим некоторую услугу, другой же процесс, который хочет воспользоватьсяэтой услугой, является клиентом.
Клиент посылает серверу запросы на предоставлениеуслуги, а сервер отвечает на эти запросы.Пример. Модель «клиент-сервер». Процесс-сервер запускается на выполнениепервым, создает именованный канал (FIFO-файл), открывает его на чтение внеблокирующем режиме и входит в цикл, пытаясь прочесть что-либо.
Процесс-клиентподключается к каналу с известным ему именем, записывает в него свой идентификатор, азатем закрывает файловый дескриптор, ассоциированный с данным FIFO-файлом.Прочитав идентификатор клиента, сервер выходит из цикла, печатает этот идентификатор,а затем закрывает дескриптор, ассоциированный с данным FIFO-файлом, и уничтожаетсам файл./* процесс-сервер*/#include <stdio.h>#include <sys/types.h>#include <sys/stat.h>#include <fcntl.h>#include <unistd.h>int main(int argc, char **argv){int fd;3Для создания файла FIFO в UNIX System V.3 и ранее используется системный вызов mknod(), а вBSD UNIX и System V.4 — вызов mkfifo() (этот вызов поддерживается и стандартом POSIX).156int pid;mkfifo("fifo", S_IFIFO | 0666);/*создали специальный файл FIFO с открытыми для всехправами доступа на чтение и запись*/fd = open("fifo", O_RDONLY | O_NONBLOCK);/* открыли канал на чтение*/while(read (fd, &pid, sizeof(int)) == -1) ;printf("Server %dpid);got message from %d !\n", getpid(),close(fd);unlink("fifo");/*уничтожили именованный канал*/return 0;}/* процесс-клиент*/#include <sys/types.h>#include <sys/stat.h>#include <unistd.h>#include <fcntl.h>int main(int argc, char **argv){int fd;int pid = getpid( );fd = open("fifo", O_RDWR);write(fd, &pid, sizeof(int));close(fd);return 0;}3.1.5 Нелокальные переходыРассмотрим некоторые дополнительные возможности по организации управленияходом процесса в UNIX, а именно возможность передачи управления в точку,расположенную вне данной функции.Как известно, оператор goto позволяет осуществлять безусловный переход тольковнутри одной функции.
Это ограничение связано с необходимостью сохраненияцелостности стека: в момент входа в функцию в стеке отводится место, называемоестековым кадром, где записываются адрес возврата, фактические параметры, отводитсяместо под автоматические переменные. Стековый кадр освобождается при выходе изфункции. Соответственно, если при выполнении безусловного перехода процесс минует157тот фрагмент кода, где происходит освобождение стекового кадра, и управлениенепосредственно перейдет в другую часть программы (например, в объемлющуюфункцию), то фактическое состояние стека не будет соответствовать текущему участкукода, и тем самым стек подвергнется разрушению.Однако такое ограничение в некоторых случаях создает большое неудобство:например, в случае возникновения ошибки в рекурсивной функции, после обработкиошибки имеет смысл перейти в основную функцию, которая может находиться нанесколько уровней вложенности выше текущей.