организовать двустороннюю поочередную связь процесса-родителя и процесса-ребенка через pipe, используя для синхронизации сигналы sigusr1 и sigusr2. |
Организация взаимодействия процессов через pipe и FIFO в UNIX
Понятие FIFO. Использование системного вызова mknod() для создания FIFO. Функция mkfifo()
Как мы выяснили, доступ к информации о расположении pip'а в операционной системе и его состоянии может быть осуществлен только через таблицу открытых файлов процесса, создавшего pipe , и через унаследованные от него таблицы открытых файлов процессов-потомков. Поэтому изложенный выше механизм обмена информацией через pipe справедлив лишь для родственных процессов, имеющих общего прародителя, инициировавшего системный вызов pipe(), или для таких процессов и самого прародителя и не может использоваться для потокового общения с другими процессами. В операционной системе UNIX существует возможность использования pip'а для взаимодействия других процессов, но ее реализация достаточно сложна и лежит далеко за пределами наших занятий.
Для организации потокового взаимодействия любых процессов в операционной системе UNIX применяется средство связи, получившее название FIFO (от First Input First Output) или именованный pipe . FIFO во всем подобен pip’у, за одним исключением: данные о расположении FIFO в адресном пространстве ядра и его состоянии процессы могут получать не через родственные связи, а через файловую систему. Для этого при создании именованного pip’а на диске заводится файл специального типа, обращаясь к которому процессы могут получить интересующую их информацию. Для создания FIFO используется системный вызов mknod() или существующая в некоторых версиях UNIX функция mkfifo() .
Следует отметить, что при их работе не происходит действительного выделения области адресного пространства операционной системы под именованный pipe , а только заводится файл-метка, существование которой позволяет осуществить реальную организацию FIFO в памяти при его открытии с помощью уже известного нам системного вызова open() .
После открытия именованный pipe ведет себя точно так же, как и неименованный. Для дальнейшей работы с ним применяются системные вызовы read() , write() и close() . Время существования FIFO в адресном пространстве ядра операционной системы, как и в случае с pip’ом, не может превышать время жизни последнего из использовавших его процессов. Когда все процессы, работающие с FIFO, закрывают все файловые дескрипторы, ассоциированные с ним, система освобождает ресурсы, выделенные под FIFO. Вся непрочитанная информация теряется. В то же время файл-метка остается на диске и может использоваться для новой реальной организации FIFO в дальнейшем.
Важно понимать, что файл типа FIFO не служит для размещения на диске информации, которая записывается в именованный pipe . Эта информация располагается внутри адресного пространства операционной системы, а файл является только меткой, создающей предпосылки для ее размещения.
Не пытайтесь просмотреть содержимое этого файла с помощью Midnight Commander (mc)!!! Это приведет к его глубокому зависанию!
Особенности поведения вызова open() при открытии FIFO
Системные вызовы read() и write() при работе с FIFO имеют те же особенности поведения, что и при работе с pip’ом. Системный вызов open() при открытии FIFO также ведет себя несколько иначе, чем при открытии других типов файлов, что связано с возможностью блокирования выполняющих его процессов. Если FIFO открывается только для чтения, и флаг O_NDELAY не задан, то процесс, осуществивший системный вызов, блокируется до тех пор, пока какой-либо другой процесс не откроет FIFO на запись. Если флаг O_NDELAY задан, то возвращается значение файлового дескриптора, ассоциированного с FIFO. Если FIFO открывается только для записи, и флаг O_NDELAY не задан, то процесс, осуществивший системный вызов, блокируется до тех пор, пока какой-либо другой процесс не откроет FIFO на чтение. Если флаг O_NDELAY задан, то констатируется возникновение ошибки и возвращается значение -1. Задание флага O_NDELAY в параметрах системного вызова open() приводит и к тому, что процессу, открывшему FIFO, запрещается блокировка при выполнении последующих операций чтения из этого потока данных и записи в него.
Прогон программы c FIFO в родственных процессах
Для иллюстрации взаимодействия процессов через FIFO рассмотрим такую программу:
/* Программа 05-4.с, осуществляющая однонаправленную связь через FIFO между процессом-родителем и процессом-ребенком */ #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <unistd.h> #include <stdio.h> int main(){ int fd, result; size_t size; char resstring[14]; char name[]="aaa.fifo"; /* Обнуляем маску создания файлов текущего процесса для того, чтобы права доступа у создаваемого FIFO точно соответствовали параметру вызова mknod() */ (void)umask(0); /* Попытаемся создать FIFO с именем aaa.fifo в текущей директории */ if(mknod(name, S_IFIFO | 0666, 0) < 0){ /* Если создать FIFO не удалось, печатаем об этом сообщение и прекращаем работу */ printf("Can\'t create FIFO\n"); exit(-1); } /* Порождаем новый процесс */ if((result = fork()) < 0){ /* Если создать процесс не удалось, сообщаем об этом и завершаем работу */ printf("Can\'t fork child\n"); exit(-1); } else if (result > 0) { /* Мы находимся в родительском процессе, который будет передавать информацию процессу-ребенку. В этом процессе открываем FIFO на запись.*/ if((fd = open(name, O_WRONLY)) < 0){ /* Если открыть FIFO не удалось, печатаем об этом сообщение и прекращаем работу */ printf("Can\'t open FIFO for writing\n"); exit(-1); } /* Пробуем записать в FIFO 14 байт, т.е. всю строку "Hello, world!" вместе с признаком конца строки */ size = write(fd, "Hello, world!", 14); if(size != 14){ /* Если записалось меньшее количество байт,то сообщаем об ошибке и завершаем работу */ printf("Can\'t write all string to FIFO\n"); exit(-1); } /* Закрываем входной поток данных и на этом родитель прекращает работу */ close(fd); printf("Parent exit\n"); } else { /* Мы находимся в порожденном процессе, который будет получать информацию от процесса-родителя. Открываем FIFO на чтение.*/ if((fd = open(name, O_RDONLY)) < 0){ /* Если открыть FIFO не удалось, печатаем об этом сообщение и прекращаем работу */ printf("Can\'t open FIFO for reading\n"); exit(-1); } /* Пробуем прочитать из FIFO 14 байт в массив, т.е. всю записанную строку */ size = read(fd, resstring, 14); if(size < 0){ /* Если прочитать не смогли, сообщаем об ошибке и завершаем работу */ printf("Can\'t read string\n"); exit(-1); } /* Печатаем прочитанную строку */ printf("%s\n",resstring); /* Закрываем входной поток и завершаем работу */ close(fd); } return 0; }Листинг 5.4. Программа 05-4.с, осуществляющая однонаправленную связь через FIFO между процессом-родителем и процессом-ребенком
Наберите программу, откомпилируйте ее и запустите на исполнение. В этой программе информацией между собой обмениваются процесс-родитель и процесс-ребенок. Обратим внимание, что повторный запуск этой программы приведет к ошибке при попытке создания FIFO, так как файл с заданным именем уже существует. Здесь нужно либо удалять его перед каждым прогоном программы с диска вручную, либо после первого запуска модифицировать исходный текст, исключив из него все, связанное с системным вызовом mknod() . С системным вызовом, предназначенным для удаления файла при работе процесса, мы познакомимся позже (на семинарах 11–12) при изучении файловых систем.
Написание, компиляция и запуск программы с FIFO в неродственных процессах
Для закрепления полученных знаний напишите на базе предыдущего примера две программы, одна из которых пишет информацию в FIFO, а вторая – читает из него, так чтобы между ними не было ярко выраженных родственных связей (т.е. чтобы ни одна из них не была потомком другой).
Неработающий пример для связи процессов на различных компьютерах
Если у вас есть возможность, найдите два компьютера, имеющих разделяемую файловую систему (например, смонтированную с помощью NFS), и запустите на них программы из предыдущего раздела так, чтобы каждая программа работала на своем компьютере, а FIFO создавалось на разделяемой файловой системе. Хотя оба процесса видят один и тот же файл с типом FIFO, взаимодействия между ними не происходит, так как они функционируют в физически разных адресных пространствах и пытаются открыть FIFO внутри различных операционных систем.