Чем FIFO (именованный канал) отличается от обычного канала (безымянный канал)?

Чем FIFO (именованный канал) отличается от обычного канала (|)? Как я понял из Википедии , в отличие от обычного канала, канал FIFO «продолжает жить» после завершения процесса и может быть удален через некоторое время.

Но если f процесс основан на команде оболочки, содержащей канал ( cat x | grep y ), мы можем «сохранить его после процесса», если мы сохраним его в переменной или файле, не так ли тогда FIFO ?

Кроме того, обычный канал также имеет первый стандартный вывод, который он получает, как стандартный вывод для другой команды , так что, разве это также не своего рода первый в первом выходе канал?

«Именованный канал» на самом деле является очень точным названием того, что он есть – он похож на обычный канал, за исключением того, что у него есть имя (в файловой системе).

Канал – обычный, безымянный (“анонимный”), используемый в some-command | grep pattern some-command | grep pattern – это особый вид файла. И я имею в виду файл, вы читаете и пишете в него так же, как вы делаете любой другой файл. Grep на самом деле не волнует, что он читает из канала, а не из терминала, или из обычного файла.

Технически, что происходит за кулисами, так это то, что stdin, stdout и stderr – это три открытых файла (файловых дескриптора), которые передаются при каждом запуске команды. Файловые дескрипторы (которые используются в каждом системном вызове для чтения / записи / и т. Д. Файлов) – это просто числа; stdin, stdout и stderr являются файловыми дескрипторами 0, 1 и 2. Поэтому, когда ваша shell устанавливает some-command | grep some-command | grep что он делает что-то такое:

  1. Запрашивает kernel ​​для анонимного канала. Там нет имени, поэтому это не может быть сделано с open как для обычного файла – вместо этого это делается с pipe или pipe2 , который возвращает два дескриптора файла.

  2. Разветвляет дочерний процесс ( fork() создает копию родительского процесса; здесь открыты обе стороны канала), копирует сторону записи канала в fd 1 (stdout). В ядре есть системный вызов для копирования номеров файловых дескрипторов; это dup2() или dup3() . Затем он закрывает сторону чтения и другую копию стороны записи. Наконец, он использует execve для выполнения some-command . Поскольку канал – это fd 1, стандартный вывод команды some-command – это канал.

  3. Вилы другого дочернего процесса. На этот раз он дублирует сторону чтения канала до fd 0 (stdin) и выполняет grep . Так что grep будет читать из трубы как stdin.

  4. Затем он ждет выхода обоих детей.

  5. В этот момент kernel ​​замечает, что канал больше не открыт, и мусор собирает его. Вот что на самом деле разрушает трубу.

Именованный канал просто дает этому анонимному каналу имя, помещая его в файловую систему. Так что теперь любой процесс, в любой момент в будущем, может получить дескриптор файла для канала, используя обычный open системный вызов. Концептуально канал не будет уничтожен, пока оба читателя / писателя не закроют его, и он не будет unlink из файловой системы.

Кстати, именно так работают файлы в Unix. unlink (системный вызов rm ) просто удаляет одно из имен файла; только когда все имена будут удалены и файл не будет открыт, он будет фактически удален. Несколько ответов здесь исследуют это:

  • Почему жесткие ссылки занимают то же место, что и оригиналы?
  • Как программа-журнал может продолжить запись в удаленный файл?
  • Что Linux делает по-другому, что позволяет мне удалять / заменять файлы, если Windows будет жаловаться, что файл используется в настоящее время?

Сноски

  1. Технически это, вероятно, не соответствует действительности – возможно, можно провести некоторую оптимизацию, зная, а фактические реализации grep часто сильно оптимизированы. Но концептуально это не волнует (и на самом деле прямая реализация grep не будет).
  2. Конечно, kernel ​​на самом деле не хранит все структуры данных в памяти навсегда, а скорее создает их прозрачно, всякий раз, когда первая программа открывает именованный канал (а затем сохраняет их до тех пор, пока он открыт). Так что, как если бы они существовали до тех пор, как имя.
  3. Терминал не является обычным местом для чтения с grep, но это стандартный ввод по умолчанию, когда вы не указываете другой. Так что, если вы grep pattern просто grep pattern в вашу оболочку, grep будет читать с терминала. Единственное использование, которое приходит на ум, – это когда вы собираетесь вставить что-то в терминал.
  4. В Linux анонимные каналы фактически создаются в специальной файловой системе pipefs. Подробности смотрите в разделе Как работают каналы в Linux . Обратите внимание, что это внутренняя деталь реализации Linux.

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

Канал / FIFO работает одинаково независимо от того, был ли подключен писатель и читатель посредством системных вызовов, таких как open("/path/to/named_pipe", O_WRONLY); или с помощью pipe(2) для создания нового анонимного канала и возврата дескрипторов открытых файлов как на чтение, так и на запись.

fstat(2) в дескрипторе файла канала даст вам sb.st_mode & S_IFMT == S_IFIFO любом случае.


Когда вы запускаете foo | bar foo | bar :

  • Оболочка разветвляется как обычно для любой не встроенной команды
  • Затем выполняет системный вызов pipe(2) для получения двух файловых дескрипторов: ввода и вывода анонимного канала.
  • Тогда это снова разветвляется.
  • Ребенок (где fork() вернул 0)
    • закрывает сторону чтения канала (оставляя запись открытой)
    • и перенаправляет стандартный stdout на запись с помощью dup2(pipefd[1], 1)
    • затем исполняется execve("/usr/bin/foo", ...)
  • Родитель (где fork() вернул не-0 дочерний PID)
    • закрывает сторону записи канала (оставляя чтение fd открытым)
    • и перенаправляет stdin из чтения fd с dup2(pipefd[0], 0)
    • затем исполняется execve("/usr/bin/bar", ...)

Вы попадаете в очень похожую ситуацию, если запускаете foo > named_pipe & bar < named_pipe .

Именованный канал - это рандеву для процессов для установления каналов между собой.


Ситуация похожа на анонимные файлы tmp против файлов с именами. Вы можете open("/path/to/dir", O_TMPFILE | O_RDWR, S_IRUSR | S_IWUSR); создать временный файл без имени ( O_TMPFILE ), как если бы вы открыли "/path/to/dir/tmpfile" с помощью O_CREAT а затем отменили его связь, оставив вам дескриптор файла для удаленного файла.

Используя linkat , вы даже можете связать этот анонимный файл с файловой системой, дав ему имя, если он был создан с помощью O_TMPFILE . ( Вы не можете сделать это в Linux для файлов, которые вы создали с именем, которое затем удалили. )