Путаница с dup2 (), exec () и трубами

Я изо всех сил пытался понять концепцию, включающую команды dup2(), exec() и каналы вместе.

То, чего я пытаюсь достичь:

По конвейеру вывод программы X поступает на вход программы Y.

Что-нибудь базовое, например who | sort

с родителем и 2 дочерними элементами, при этом дети несут ответственность за выполнение программ, а родитель передает программы дочерним элементам.

Вот чего я не понимаю в трубах:

P1) Каналы обрабатываются как файлы и должны быть однонаправленными, но что мешает мне использовать один канал для нескольких однонаправленных каналов связи? Итак, скажем, у меня есть pipe1 и три процесса (P - родительский - C1, C2, дочерние), у которых канал открыт путем разветвления. Все эти процессы используют файловые дескрипторы. Допустим, мы все делаем правильно, закрываем неиспользуемые концы труб, P теперь что-то пишет в C1. В чем проблема с использованием канала для связи между C1 и C2 снова? Как раз во время написания этого вопроса меня осенила идея: есть ли проблема с тем, кто читает из него, в то время как многие процессы могут открывать его одновременно (два процесса блокируются для чтения), т.е. система не может точно сказать, кто хочет прочитать записанные в нее буферизованные данные? Если да, то как это реализовано в системе?

Я действительно пытаюсь понять эту концепцию, поэтому, пожалуйста, потерпите меня.

Чтобы применить этот вопрос к реальной жизни, вот какой-то псевдокод, с которым я имею дело:

P:

  • P закрывает ненужный конец чтения pipe1
  • P отправляет программный аргумент ('who') C1 через pipe1
  • P закрывает конец записи
  • P ждет выхода детей

C1:

  • C1 читает аргумент с конца чтения pipe1
  • C1 dup2() расширяет стандарт до конца записи pipe1
  • C1 закрывает оба конца pipe1 (потому что мы его уже обманули)
  • C1 execvp()s программа ('кто')

C2:

  • C2 dup2()s читает конец pipe1 на stdin, чтобы получить ввод для программы, которая будет выполняться
  • C2 закрывает оба конца pipe1
  • C2 ожидает ввода на stdin из C1 от duped pipe1
  • C2 execvp()s программа ('sort') с этим входом


Теперь, если бы я сделал это, как описано выше, мне бы не повезло. Однако, если я представил другой канал pipe2, он будет выглядеть примерно так:
P:

  • P закрывает оба конца ненужной трубы pipe2
  • P закрывает ненужный конец чтения pipe1
  • P отправляет программный аргумент ('who') в C1 через pipe1
  • P закрывает конец записи
  • P ждет выхода детей

C1:

  • C1 закрывает конец чтения pipe2
  • C1 читает аргумент с конца чтения pipe1
  • C1 dup2()s стандарт до конца записи pipe2
  • C1 закрывает конец записи pipe2
  • C1 закрывает оба конца pipe1 - с pipe2, pipe1 в этом дочернем элементе
  • C1 execvp()s программа ('кто')

C2:

  • C2 dup2()s читать с конца pipe2 по stdin
  • C2 закрывает оба конца pipe1
  • C2 ожидает ввода на stdin из C1 от duped pipe2
  • C2 выполняет программу sort с этим входом

Верно ли предположение, что каналы не следует повторно использовать в нескольких процессах, потому что система может не знать, кого «обслуживать»? Или этому есть какая-то другая причина?


person the_critic    schedule 24.04.2015    source источник
comment
Не могли бы вы уточнить немного подробнее? Правильно ли я понимаю ситуацию: P каким-то образом получает ввод типа a | b, затем создает C1 и C2, C1 выполняет a, C2 выполняет b. stdout a должен быть stdin b. Я прав? Это требование? Разве P не может форкнуть C1, а C1 может форкнуть C2?   -  person holgac    schedule 25.04.2015
comment
Кроме того, семейство exec не продолжается после указанной операции, поэтому C1 и C2 прекратят работу после выполнения команд. Это означает, что всякий раз, когда P получает новый ввод, например a | b, ему придется снова выполнить форк. Поскольку это так, связь между P и C1 и P и C2 кажется здесь избыточной.   -  person holgac    schedule 25.04.2015
comment
@holgac Да, вы совершенно правы. Это требование. Я бы знал, как это сделать так, как вы это сказали.   -  person the_critic    schedule 25.04.2015
comment
Да, мне известно, что exec() заменяет текущий процесс. Родитель должен сообщить программы детям, это требование.   -  person the_critic    schedule 25.04.2015
comment
Тогда, как сказал Жиль в своем ответе, вы не можете этого сделать. однонаправленный канал означает, что концы чтения / записи используют один и тот же буфер. Вместо этого вам понадобится труба (p1) между P и C1, другая труба (p2) между P и C2, другая (p3) между C1 и C2. Поскольку C1 и C2 являются братьями и сестрами, они не могут создать канал, которым каждый из них может совместно пользоваться. Вам нужно будет создать p1 перед C1, p2 перед C2 и p3 перед C1 и C2.   -  person holgac    schedule 25.04.2015
comment
@holgac Хорошо, прочитав ваш комментарий и ответ Жиля, думаю, теперь я понял, спасибо!   -  person the_critic    schedule 25.04.2015


Ответы (1)


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

  • Чтение из конвейера - это деструктивная операция: каждый байт, отправленный через конвейер, будет прочитан ровно одним из читателей, кто бы его ни схватил. Если вы хотите транслировать некоторую информацию, вам нужно либо использовать другой механизм IPC, либо явно реплицировать данные (например, команду tee).
  • Если писателей несколько, их записи чередуются несколько непредсказуемым образом. Единственная гарантия состоит в том, что запись размером PIPE_BUF или меньше является атомарной.
  • Если количество писателей падает до нуля, читатель видит условие конца файла.

В описываемой архитектуре у вас есть два независимых канала связи: P отправляет who на C1, а C1 отправляет результат выполнения команды who на C2. В сценарии оболочки это будет похоже на

echo who | { read command; exec command; } | sort

с echo who, выполняемым в исходном процессе, а не в подоболочке.

Ваше первое предложение не работает, потому что нельзя сказать, что выход P перейдет к C1, а результат C1 перейдет к C2. Это все тот же канал, поэтому выход P может идти к C2, а выход C1 может возвращаться к самому себе, или это может быть смесь.

person Gilles 'SO- stop being evil'    schedule 24.04.2015
comment
Хорошо, спасибо, так что вы в основном подтверждаете то, что я предполагал до сих пор. Определенно имеет смысл знать, что трубы используются для общения один на один по указанным причинам. - person the_critic; 25.04.2015