Опубликован: 16.09.2004 | Уровень: специалист | Доступ: свободно | ВУЗ: Московский физико-технический институт
Лекция 3:

Организация взаимодействия процессов через pipe и FIFO в UNIX

< Лекция 2 || Лекция 3: 123456 || Лекция 4 >

Понятие о pipe. Системный вызов pipe()

Наиболее простым способом для передачи информации с помощью потоковой модели между различными процессами или даже внутри одного процесса в операционной системе UNIX является pipe (канал, труба, конвейер).

Важное отличие pip’а от файла заключается в том, что прочитанная информация немедленно удаляется из него и не может быть прочитана повторно.

Pipe можно представить себе в виде трубы ограниченной емкости, расположенной внутри адресного пространства операционной системы, доступ к входному и выходному отверстию которой осуществляется с помощью системных вызовов. В действительности pipe представляет собой область памяти, недоступную пользовательским процессам напрямую, зачастую организованную в виде кольцевого буфера (хотя существуют и другие виды организации). По буферу при операциях чтения и записи перемещаются два указателя, соответствующие входному и выходному потокам. При этом выходной указатель никогда не может перегнать входной и наоборот. Для создания нового экземпляра такого кольцевого буфера внутри операционной системы используется системный вызов pipe ().

Системный вызов pipe

Прототип системного вызова

#include <unistd.h>
int pipe(int *fd);

Описание системного вызова

Системный вызов pipe предназначен для создания pip'а внутри операционной системы.

Параметр fd является указателем на массив из двух целых переменных. При нормальном завершении вызова в первый элемент массива – fd[0] – будет занесен файловый дескриптор, соответствующий выходному потоку данных pip’а и позволяющий выполнять только операцию чтения, а во второй элемент массива – fd[1] – будет занесен файловый дескриптор, соответствующий входному потоку данных и позволяющий выполнять только операцию записи.

Возвращаемые значения

Системный вызов возвращает значение 0 при нормальном завершении и значение -1 при возникновении ошибок.

В процессе работы системный вызов организует выделение области памяти под буфер и указатели и заносит информацию, соответствующую входному и выходному потокам данных, в два элемента таблицы открытых файлов, связывая тем самым с каждым pip’ом два файловых дескриптора. Для одного из них разрешена только операция чтения из pip’а, а для другого – только операция записи в pipe . Для выполнения этих операций мы можем использовать те же самые системные вызовы read() и write() , что и при работе с файлами. Естественно, по окончании использования входного или/и выходного потока данных, нужно закрыть соответствующий поток с помощью системного вызова close() для освобождения системных ресурсов. Необходимо отметить, что, когда все процессы, использующие pipe , закрывают все ассоциированные с ним файловые дескрипторы, операционная система ликвидирует pipe . Таким образом, время существования pip’а в системе не может превышать время жизни процессов, работающих с ним.

Прогон программы для pipe в одном процессе

Достаточно яркой иллюстрацией действий по созданию pip'a, записи в него данных, чтению из него и освобождению выделенных ресурсов может служить программа, организующая работу с pip’ом в рамках одного процесса, приведенная ниже:

/* Программа 05-2.с, иллюстрирующая работу с pip'ом в рамках одного 
процесса */ 
#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
int main(){
    int fd[2]; 
    size_t size;
    char string[] = "Hello, world!";
    char resstring[14]; 
    /* Попытаемся создать pipe */
    if(pipe(fd) < 0){
        /* Если создать pipe не удалось, печатаем об этом сообщение 
        и прекращаем работу */
        printf("Can\'t create pipe\n");
        exit(-1); 
    }
    /* Пробуем записать в pipe 14 байт из нашего массива, т.е. всю
    строку "Hello, world!" вместе с признаком конца строки */
    size = write(fd[1], string, 14);
    if(size != 14){
        /* Если записалось меньшее количество байт, сообщаем об 
        ошибке */
        printf("Can\'t write all string\n"); 
        exit(-1); 
    } 
    /* Пробуем прочитать из pip'а 14 байт в другой массив, т.е. всю 
    записанную строку */
    size = read(fd[0], resstring, 14);
    if(size < 0){
        /* Если прочитать не смогли, сообщаем об ошибке */
        printf("Can\'t read string\n"); 
        exit(-1); 
    } 
    /* Печатаем прочитанную строку */
    printf("%s\n",resstring);
    /* Закрываем входной поток*/
    if(close(fd[0]) < 0){ 
        printf("Can\'t close input stream\n");
    }
    /* Закрываем выходной поток*/
    if(close(fd[1]) < 0){ 
        printf("Can\'t close output stream\n");
    }
    return 0; 
}
Листинг 5.2. Программа 05-2.с, иллюстрирующая работу с pip'ом в рамках одного процесса

Наберите программу, откомпилируйте ее и запустите на исполнение.

Организация связи через pipe между процессом-родителем и процессом-потомком. Наследование файловых дескрипторов при вызовах fork() и exec()

Понятно, что если бы все достоинство pip'ов сводилось к замене функции копирования из памяти в память внутри одного процесса на пересылку информации через операционную систему, то овчинка не стоила бы выделки. Однако таблица открытых файлов наследуется процессом-ребенком при порождении нового процесса системным вызовом fork() и входит в состав неизменяемой части системного контекста процесса при системном вызове exec() (за исключением тех потоков данных, для файловых дескрипторов которых был специальными средствами выставлен признак, побуждающий операционную систему закрыть их при выполнении exec(), однако их рассмотрение выходит за рамки нашего курса). Это обстоятельство позволяет организовать передачу информации через pipe между родственными процессами, имеющими общего прародителя, создавшего pipe .

Прогон программы для организации однонаправленной связи между родственными процессами через pipe

Давайте рассмотрим программу, осуществляющую однонаправленную связь между процессом-родителем и процессом-ребенком:

/* Программа 05-3.с, осуществляющая однонаправленную связь через pipe 
между процессом-родителем и процессом-ребенком */ 
#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
int main(){
    int fd[2], result;
    size_t size;
    char resstring[14]; 
    /* Попытаемся создать pipe */
    if(pipe(fd) < 0){
        /* Если создать pipe не удалось, печатаем об этом сообщение
        и прекращаем работу */
        printf("Can\'t create pipe\n");
        exit(-1); 
    } 
    /* Порождаем новый процесс */ 
    result = fork(); 
    if(result){ 
        /* Если создать процесс не удалось, сообщаем об этом и 
        завершаем работу */
        printf("Can\'t fork child\n");
        exit(-1);
    } else if (result > 0) {
        /* Мы находимся в родительском процессе, который будет 
        передавать информацию процессу-ребенку. В этом процессе
        выходной поток данных нам не понадобится, поэтому 
        закрываем его.*/
        close(fd[0]);
        /* Пробуем записать в pipe 14 байт, т.е. всю строку 
        "Hello, world!" вместе с признаком конца строки */
        size = write(fd[1], "Hello, world!", 14);
        if(size != 14){
            /* Если записалось меньшее количество байт, сообщаем
            об ошибке и завершаем работу */
            printf("Can\'t write all string\n"); 
            exit(-1); 
        } 
        /* Закрываем входной поток данных, на этом 
        родитель прекращает работу */
        close(fd[1]);
        printf("Parent exit\n");
    } else {
        /* Мы находимся в порожденном процессе, который будет 
        получать информацию от процесса-родителя. Он унаследовал
        от родителя таблицу открытых файлов и, зная файловые 
        дескрипторы, соответствующие pip, иможет его использовать.
        В этом процессе входной поток данных нам не 
        ипонадобится, поэтому закрываем его.*/
        close(fd[1]);
        /* Пробуем прочитать из pip'а 14 байт в массив, т.е. всю
        записанную строку */
        size = read(fd[0], resstring, 14);
        if(size < 0){

            /* Если прочитать не смогли, сообщаем об ошибке и
            завершаем работу */

            printf("Can\'t read string\n"); 
            exit(-1); 
        } 
        /* Печатаем прочитанную строку */
        printf("%s\n",resstring);
        /* Закрываем входной поток и завершаем работу */
        close(fd[0]);
    }    
    return 0; 
}
Листинг 5.3. Программа 05-3.с, осуществляющая однонаправленную связь через pipe между процессом-родителем и процессом-ребенком

Наберите программу, откомпилируйте ее и запустите на исполнение.

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

< Лекция 2 || Лекция 3: 123456 || Лекция 4 >
лия логовина
лия логовина

организовать двустороннюю поочередную связь процесса-родителя и процесса-ребенка через pipe, используя для синхронизации сигналы sigusr1 и sigusr2.

Макар Оганесов
Макар Оганесов
Сергей Пархоменко
Сергей Пархоменко
Россия, Ростов-на-Дону, ЮФУ (ДГТУ), 2008