Поиск…


Вступление

Трубы - это механизм однонаправленного межпроцессного или межстрочного обмена в рамках одной машины. Логически, труба состоит из двух подключенных терминалов, одна из которых может быть записана, и другая, из которой эти данные могут быть впоследствии прочитаны, с буфером данных между такими, что записи и чтения не обязательно должны быть синхронными. Трубы следует отличать от трубопроводов , которые являются применением труб.

Базовое создание и использование

Анонимные трубы или просто каналы - это объекты, управляемые ядрами, которые подвергаются процессам в виде пары дескрипторов файлов, одна для конечного конца чтения и одна для конца записи. Они создаются с помощью функции pipe (2):

int pipefds[2];
int result;

result = pipe(pipefds);

При успехе pipe() записывает дескриптор для считываемого конца канала в индекс 0 предоставленного массива и дескриптор для конца записи в индексе 1; эти индексы аналогичны обычным номерам дескрипторов файлов для стандартных потоков.

Создав канал, один использует функции ввода-вывода POSIX для записи на конец записи или чтения с конца чтения:

Процесс 1:

ssize_t bytes_written = write(pipefds[1], "Hello, World!", 14);

Процесс 2:

char buffer[256];
ssize_t bytes_read = read(pipefds[0], buffer, sizeof(buffer));

В качестве альтернативы можно использовать fdopen() чтобы обернуть один из обоих концов конвейера в структуру FILE для использования с функциями C stdio:

FILE *write_end = fdopen(pipefds[1]);

if (write_end) {
    fputs("Hello, World!");
}

Трубы имеют конечные буферы ввода-вывода, и обычные записи в трубы с полными буферами будут блокироваться. Таким образом, трубы не являются безопасным механизмом взаимодействия потока с самим собой, как если бы поток, записывающий в трубу, также является единственным, который читает от него, то как только блок записи блокирует этот поток, блокируется.

Труба сохраняется до тех пор, пока любой процесс имеет открытое описание файла для любого из концов трубы. Функция close (2) может использоваться для закрытия конца трубы, представленного файловым дескриптором, а fclose (3) может использоваться для закрытия конца трубы через FILE обернутый вокруг него.

Создание трубы для дочернего процесса

Файловые дескрипторы и объекты FILE представляют собой ресурсы процесса, которые сами по себе не могут быть обменены между процессами с помощью обычного ввода-вывода. Поэтому для того, чтобы два отдельных процесса могли взаимодействовать через анонимный канал, один или оба участвующих процесса должны наследовать открытый конец канала из процесса, который создал канал, поэтому он должен быть родительским процессом или более отдаленным предком. Простейшим случаем является тот, в котором родительский процесс хочет общаться с дочерним процессом.

Поскольку дочерний процесс должен наследовать необходимое описание открытого файла от его родителя, сначала необходимо создать трубку. Затем родитель вилки. Обычно каждый процесс будет либо строго читателем, либо строго писателем; в этом случае каждый должен закрыть конец трубы, который он не намерен использовать.

void demo() {
    int pipefds[2];
    pid_t pid;

    // Create the pipe
    if (pipe(pipefds)) {
        // error - abort ...
    }

    switch (pid = fork()) {
        case -1:
            // error - abort ...
            break;
        case 0:   /* child */
            close(pipefds[0]);
            write(pipefds[1], "Goodbye, and thanks for all the fish!", 37);
            exit(0);
        default:  /* parent */
            close(pipefds[1]);

            char buffer[256];
            ssize_t nread = read(pipefds[0], sizeof(buffer) - 1);

            if (nread >= 0) {
                buffer[nread] = '\0';
                printf("My child said '%s'\n", buffer);
            }

            // collect the child
            wait(NULL);

            break;
    }
}

Подключение двух дочерних процессов через трубу

Подключение двух дочерних процессов через трубу выполняется путем подключения каждого из двух дочерних элементов к родительскому устройству через разные концы одного и того же канала. Обычно родитель не будет участвовать в разговоре между детьми, поэтому он закрывает свои копии обоих концов труб.

int demo() {
    int pipefds[2];
    pid_t child1, child2;

    if (pipe(pipefds)) {
        // error - abort ...
    }

    switch (child1 = fork()) {
        case -1:
            // error - abort
            break;
        case 0:   /* child 1 */
            close(pipefds[0]);
            write(pipefds[1], "Hello, brother!", 15);
            exit(0);
        default:  /* parent */
            // nothing
    }

    switch (child1 = fork()) {
        case -1:
            // error - abort
            break;
        case 0:   /* child 2 */
            char buffer[256];
            ssize_t nread;

            close(pipefds[1]);
            nread = read(pipefds[0], buffer, sizeof(buffer) - 1);
            if (nread < 0) {
                // handle error
            } else {
                buffer[nread] = '\0';
                printf("My brother told me '%s'\n", buffer);
            }
            exit(0);
        default:  /* parent */
            // nothing
    }

    // Only the parent reaches this point
    close(pipefds[0]);
    close(pipefds[1]);
    if (child1 >= 0) {
        wait(NULL);
        if (child2 >= 0) {
            wait(NULL);
        }
    }
}

Создание конвейера в стиле оболочки

Контейнер в стиле оболочки состоит из двух или более процессов, каждый со стандартным выходом, подключенным к стандартным входам следующего. Соединения вывода на входе построены на трубах. Чтобы создать конвейерный набор процессов, вы создаете связанные с подключением к потоку дочерние процессы, как описано в другом примере, и, кроме того, использует функцию dup2 (2) для дублирования каждого конца канала в соответствующем стандартном дескрипторе файла своего процесса. Как правило, рекомендуется закрыть исходный дескриптор файла конца трубы, особенно если, как это часто бывает, для ребенка нужно выполнить следующую команду.

// Most error handling omitted for brevity, including ensuring that pipe ends are
// always closed and child processes are always collected as needed; see other
// examples for more detail.
int demo() {
    int pipefds[2];
    pid_t child1 = -1, child2 = -1;

    pipe(pipefds);

    switch (child1 = fork()) {
        case -1:
            // handle error ...
            break;
        case 0:   /* child 1 */
            close(pipefds[0]);
            dup2(pipefds[1], STDOUT_FILENO);
            close(pipefds[1]);
            execl("/bin/cat", "cat", "/etc/motd", NULL);
            exit(1);  // execl() returns only on error
        default:  /* parent */
            // nothing
    }

    switch (child2 = fork()) {
        case -1:
            // handle error ...
            break;
        case 0:   /* child 2 */
            close(pipefds[1]);
            dup2(pipefds[0], STDIN_FILENO);
            close(pipefds[0]);
            execl("/bin/grep", "grep", "[Ww]ombat", NULL);
            exit(1);  // execl() returns only on error
        default:  /* parent */
            // nothing
    }

    // Only the parent reaches this point
    close(pipefds[0]);
    close(pipefds[1]);
    wait(NULL);
    wait(NULL);
}

Одна из наиболее распространенных ошибок при настройке конвейера заключается в том, чтобы забыть, что один или несколько процессов связаны с концами трубопровода, которые он не использует. Как правило, конечным результатом должно быть то, что каждый конец трубы принадлежит только одному процессу и открыт только в этом процессе.

В таком случае, как демонстрационная функция выше, отказ второго ребенка или родителя закрыть pipefds[1] приведет к тому, что второй ребенок висит, поскольку grep будет продолжать ждать ввода до тех пор, пока не увидит EOF, и это не будет если конец записи трубы остается открытым в любом процессе, таком как родительский процесс или второй сам ребенок.



Modified text is an extract of the original Stack Overflow Documentation
Лицензировано согласно CC BY-SA 3.0
Не связан с Stack Overflow