Как отметили Дашогун и Чарли Мартин, это большой вопрос. Некоторые части их ответов неточны, поэтому я тоже отвечу.
Я заинтересован в написании отдельных программных модулей, которые работают как независимые потоки, которые я мог бы соединить с конвейерами.
Остерегайтесь попыток использовать каналы в качестве механизма связи между потоками одного процесса. Поскольку вы могли бы открыть и прочитать и записать концы канала в одном процессе, вы никогда не получите индикацию 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 символов. Когда он обнаруживает проблему записи (буфер заполнен, так как дочерний элемент ничего не читает), он останавливает цикл, записывает окончательный счет и убивает дочерний элемент.