Трубопровод как межпроцессное взаимодействие - PullRequest
9 голосов
/ 12 декабря 2008

Я заинтересован в написании отдельных программных модулей, которые работают как независимые потоки, которые я мог бы соединить с конвейерами. Мотивация заключается в том, что я могу писать и тестировать каждый модуль совершенно независимо, возможно, даже писать их на разных языках или запускать разные модули на разных машинах. Здесь есть множество возможностей. Некоторое время я пользовался трубопроводами, но не знаком с нюансами его поведения.

  • Кажется, что принимающая сторона будет блокировать ожидание ввода, что я и ожидал, но будет ли блокирующий конец иногда ждать, пока кто-то прочитает из потока?
  • Если я записываю eof в поток, могу ли я продолжать запись в этот поток, пока не закрою его?
  • Есть ли различия в поведении именованных и безымянных каналов?
  • Имеет ли значение, какой конец трубы я сначала открываю именованными трубами?
  • Соответствует ли поведение каналов между различными системами Linux?
  • Зависит ли поведение каналов от используемой оболочки или способа ее настройки?
  • Есть ли какие-либо другие вопросы, которые я должен задавать, или вопросы, о которых мне следует знать, если я хочу использовать каналы таким образом?

Ответы [ 3 ]

4 голосов
/ 12 декабря 2008

Все это основано на UNIX-подобной системе; Я не знаком со специфическим поведением последних версий Windows.

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

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

Если я записываю eof в поток, могу ли я продолжать писать в этот поток, пока не закрою его?

Хм, ты имеешь в виду CTRL-D, 0x04? Конечно, пока поток настроен таким образом. Viz.

506 # cat | od -c
abc
^D
efg
0000000    a   b   c  \n 004  \n   e   f   g  \n                        
0000012

Есть ли различия в поведении именованных и безымянных каналов?

Да, но они тонкие и зависят от реализации. Самым большим из них является то, что вы можете записать в именованный канал до запуска другого конца; при использовании неназванных каналов дескрипторы файлов становятся общими в процессе fork / exec, поэтому нет доступа к временному буферу без запуска процессов.

Имеет ли значение, какой конец трубы я сначала открываю именованными трубами?

Нет.

Соответствует ли поведение каналов между различными системами Linux?

В пределах разумного, да. Размеры буфера и т. Д. Могут варьироваться.

Зависит ли поведение каналов от используемой оболочки или способа ее настройки?

Нет. Когда вы создаете канал, под прикрытием происходит то, что ваш родительский процесс (оболочка) создает канал, который имеет пару файловых дескрипторов, а затем выполняет команду fork, как этот псевдокод:

Родитель

create pipe, returning two file descriptors, call them fd[0] and fd[1]
fork write-side process
fork read-side process

Написать сторона

close fd[0]
connect fd[1] to stdout
exec writer program

Читать сторона

close fd[1]
connect fd[0] to stdin
exec reader program

Есть ли еще какие-либо вопросы, которые я должен задать, или вопросы, о которых мне следует знать, если я хочу использовать трубы таким образом?

Все, что вы хотите сделать, действительно выложится в такой строке? Если нет, вы можете подумать о более общей архитектуре. Но понимание того, что желательно иметь множество отдельных процессов, взаимодействующих через «узкий» интерфейс канала, является хорошим.

[Обновлено: сначала я изменил индексы файловых дескрипторов. Теперь они верны, см. man 2 pipe.]

4 голосов
/ 13 декабря 2008

Как отметили Дашогун и Чарли Мартин, это большой вопрос. Некоторые части их ответов неточны, поэтому я тоже отвечу.

Я заинтересован в написании отдельных программных модулей, которые работают как независимые потоки, которые я мог бы соединить с конвейерами.

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

Если вы действительно имели в виду процессы, то это основа классического подхода Unix к созданию инструментов. Многие из стандартных программ Unix являются фильтрами, которые читают из стандартного ввода, каким-то образом преобразуют его и записывают результат в стандартный вывод. Например, tr, sort, grep и cat - это все фильтры, и это лишь некоторые из них. Это отличная парадигма для подражания, когда данные, которыми вы манипулируете, позволяют это Не все манипуляции с данными способствуют этому подходу, но есть много таких.

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

Хорошие моменты. Имейте в виду, что на самом деле нет конвейерного механизма между машинами, хотя вы можете подобраться к нему с помощью таких программ, как rsh или (лучше) ssh. Однако внутренне такие программы могут считывать локальные данные из каналов и отправлять эти данные на удаленные машины, но они обмениваются данными между машинами через сокеты, не используя каналы.

Здесь есть множество возможностей. Некоторое время я пользовался трубопроводами, но не знаком с нюансами его поведения.

OK; Задавать вопросы - это один (хороший) способ учиться. Эксперимент, конечно, другой.

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

Да. Существует ограничение на размер буфера канала. Классически это было довольно мало - 4096 или 5120 были общими ценностями. Вы можете обнаружить, что современный Linux использует большее значение. Вы можете использовать fpathconf() и _PC_PIPE_BUF, чтобы узнать размер буфера канала. POSIX требует, чтобы буфер был 512 (то есть _POSIX_PIPE_BUF равен 512).

Если я записываю eof в поток, могу ли я продолжать запись в этот поток, пока не закрою его?

Технически, нет возможности записать EOF в поток; Вы закрываете дескриптор канала, чтобы указать EOF. Если вы думаете о control-D или control-Z как о символе EOF, то это всего лишь обычные символы в том, что касается каналов - они имеют эффект EOF только при вводе на терминале, который работает в каноническом режиме (готово). или нормально).

Есть ли различия в поведении именованных и безымянных каналов?

Да и нет. Самым большим отличием является то, что безымянные каналы должны быть установлены одним процессом и могут использоваться только этим процессом и дочерними элементами, которые совместно используют этот процесс в качестве общего предка. И наоборот, именованные каналы могут использоваться ранее не связанными процессами. Следующая большая разница является следствием первого; с безымянным каналом вы возвращаете два файловых дескриптора из одного вызова функции (системы) в pipe(), но вы открываете FIFO или именованный канал, используя обычную функцию open(). (Кто-то должен создать FIFO с помощью вызова mkfifo(), прежде чем вы сможете его открыть; для безымянных каналов такая предварительная настройка не требуется.) Однако, если у вас открыт файловый дескриптор, между именованным каналом и безымянная труба.

Имеет ли значение, какой конец трубы я сначала открываю именованными трубами?

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

Соответствует ли поведение каналов между различными системами Linux?

Да. Я не слышал о каких-либо проблемах с трубами в системах, где я их использовал, и не испытывал их.

Зависит ли поведение каналов от используемой оболочки или способа ее настройки?

Нет: каналы и FIFO не зависят от используемой вами оболочки.

Есть ли еще какие-либо вопросы, которые я должен задавать, или вопросы, о которых мне следует знать, если я хочу использовать каналы таким образом?

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


Я заметил выше и в комментариях к другим ответам, что конвейерные буферы классически ограничены довольно маленькими размерами @Charlie Martin отметил, что некоторые версии Unix имеют динамические конвейерные буферы, и они могут быть довольно большими.

Я не уверен, какие из них он имеет в виду. Я использовал следующую тестовую программу для Solaris, AIX, HP-UX, MacOS X, Linux и Cygwin / Windows XP (результаты приведены ниже):

#include <unistd.h>
#include <signal.h>
#include <stdio.h>
#include <fcntl.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>

static const char *arg0;

static void err_syserr(char *str)
{
    int errnum = errno;
    fprintf(stderr, "%s: %s - (%d) %s\n", arg0, str, errnum, strerror(errnum));
    exit(1);
}

int main(int argc, char **argv)
{
    int pd[2];
    pid_t kid;
    size_t i = 0;
    char buffer[2] = "a";
    int flags;

    arg0 = argv[0];

    if (pipe(pd) != 0)
        err_syserr("pipe() failed");
    if ((kid = fork()) < 0)
        err_syserr("fork() failed");
    else if (kid == 0)
    {
        close(pd[1]);
        pause();
    }
    /* else */
    close(pd[0]);
    if (fcntl(pd[1], F_GETFL, &flags) == -1)
        err_syserr("fcntl(F_GETFL) failed");
    flags |= O_NONBLOCK;
    if (fcntl(pd[1], F_SETFL, &flags) == -1)
        err_syserr("fcntl(F_SETFL) failed");
    while (write(pd[1], buffer, sizeof(buffer)-1) == sizeof(buffer)-1)
    {
        putchar('.');
        if (++i % 50 ==  0)
            printf("%u\n", (unsigned)i);
    }
    if (i % 50 !=  0)
        printf("%u\n", (unsigned)i);
    kill(kid, SIGINT);
    return 0;
}

Мне было бы интересно получить дополнительные результаты от других платформ. Вот размеры, которые я нашел. Должен признаться, что все результаты оказались выше, чем я ожидал, но мы с Чарли, возможно, обсуждаем значение «довольно большой», когда речь идет о размерах буфера.

  • 8196 - HP-UX 11.23 для IA-64 (сбой fcntl (F_SETFL))
  • 16384 - Solaris 10
  • 16384 - MacOS X 10.5 (O_NONBLOCK не работал, хотя fcntl (F_SETFL) не работал)
  • 32768 - AIX 5.3
  • 65536 - Cygwin / Windows XP (O_NONBLOCK не работал, хотя fcntl (F_SETFL) не работал)
  • 65536 - SuSE Linux 10 (и CentOS) (ошибка fcntl (F_SETFL))

Из этих тестов ясно, что O_NONBLOCK работает с конвейерами на некоторых платформах, а не на других.

Программа создает трубу и разветвляется. Потомок закрывает конец записи канала и затем засыпает, пока не получит сигнал - вот что делает pause (). Затем родительский объект закрывает конец канала чтения и устанавливает флаги в дескрипторе записи, чтобы он не блокировал попытку записи в полном канале. Затем он зацикливается, записывая по одному символу за раз, и печатая точку для каждого написанного символа, а также счет и перевод строки через каждые 50 символов. Когда он обнаруживает проблему записи (буфер заполнен, так как дочерний элемент ничего не читает), он останавливает цикл, записывает окончательный счет и убивает дочерний элемент.

4 голосов
/ 12 декабря 2008

Ух ты, вопросов много. Посмотрим, смогу ли я покрыть все ...

Кажется, что принимающая сторона блок ожидания ввода, который я бы ожидать

Вы ожидаете, что действительный вызов read будет блокироваться до тех пор, пока что-то там не будет. Тем не менее, я полагаю, что есть некоторые функции C, которые позволят вам «посмотреть», что (и сколько) ожидает в канале. К сожалению, я не помню, блокирует ли это тоже.

будет ли отправляющий конец блокировать иногда в ожидании кого-то читать из Поток

Нет, отправка никогда не должна блокироваться. Подумайте о последствиях, если бы это был канал через сеть к другому компьютеру. Хотели бы вы подождать (возможно, с большой задержкой), пока другой компьютер ответит, что он его получил? Теперь это другой случай, если дескриптор читателя места назначения был закрыт. В этом случае у вас должна быть некоторая проверка ошибок, чтобы справиться с этим.

Если я напишу eof в поток, могу ли я продолжайте писать в этот поток пока я не закрою это

Я думаю, это зависит от того, какой язык вы используете, и от его реализации каналов. В Си я бы сказал нет. В оболочке Linux, я бы сказал, да. Кто-то еще с большим опытом должен был бы ответить на этот вопрос.

Есть ли различия в поведении именованные и безымянные трубы? Насколько я знаю, да. Тем не менее, у меня нет большого опыта с именованными против неназванных. Я считаю, что разница:

  • Одиночное направление против двунаправленной связи
  • Чтение И запись в потоки "in" и "out" потока

Имеет ли значение, какой конец трубы I сначала открыть с именованными каналами?

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

Соответствует ли поведение труб между различными системами Linux?

Опять же, это зависит от того, на каком языке, но обычно да. Вы когда-нибудь слышали о POSIX? Это стандарт (по крайней мере, для Linux, Windows делает свое дело).

Зависит ли поведение труб на оболочке, которую я использую или как я имею настроил это?

Это становится немного больше серой области. Ответ должен быть «нет», поскольку оболочка должна выполнять системные вызовы. Тем не менее, все до тех пор, пока все готово.

Есть ли еще вопросы, на которые я должен ответить? спрашивать

Вопросы, которые вы задавали, показывают, что вы хорошо понимаете систему. Продолжайте исследовать и сосредоточьтесь на том уровне, на котором вы собираетесь работать (shell, C и так далее). Вы узнаете намного больше, просто попробовав это.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...