Именованные каналы, файловые дескрипторы и EOF

10

Два окна, один и тот же пользователь, с подсказками bash. В окне типа 1:

$ mkfifo f; exec <f

Таким образом, теперь bash пытается прочитать из файлового дескриптора 0, который сопоставлен с именованным каналом f. В окне типа 2:

$ echo ls > f

Теперь window-1 печатает ls, а затем оболочка умирает. Почему?

Следующий эксперимент: снова открыть окно-1 с помощью exec <f. В окне типа 2:

$ exec 3>f
$ echo ls >&3

После первой строки выше, window-1 просыпается и печатает подсказку. Почему? После второй строки выше, window-1 печатает lsвывод, и оболочка остается в живых. Почему? Фактически, теперь в window-2, echo ls > fне закрывается оболочка window-1.

Ответ должен иметь отношение к существованию файлового дескриптора 3 из окна 2, ссылающегося на именованный канал ?!

Fixee
источник
1
После того, как exec <f, bashне пытается прочитать из fего сначала пытается открыть его. Он open()не вернется, пока какой-нибудь процесс не сделает еще одно открытие в режиме записи в канал (в этот момент будет создан экземпляр канала, и оболочка будет читать входные данные из него).
Стефан Шазелас
Отличная мысль, @ StéphaneChazelas. Это должно быть причиной того, что после exec 3>fзапуска первая оболочка выдает приглашение. (Незначительный момент, вы имели в виду «в режиме записи » в своем комментарии?)
Fixee
1
Да, прости. Отредактировано прямо перед 5-минутным сроком
Стефан Шазелас

Ответы:

12

Это связано с закрытием дескриптора файла.

В вашем первом примере echoзаписывает в свой стандартный поток вывода, который открывает оболочка для соединения с ним f, и когда он завершается, его дескриптор закрывается (оболочкой). На принимающей стороне оболочка, которая считывает ввод из своего стандартного входного потока (подключенного к f), читает ls, запускается lsи затем завершается из-за условия конца файла на своем стандартном вводе.

Условие окончания файла возникает из-за того, что все пишущие в именованный канал (только один в этом примере) закрыли свой конец канала.

Во втором примере exec 3>fоткрывается дескриптор файла 3 для записи f, а затем echoзапись lsв него. Теперь у оболочки есть дескриптор файла, а не echoкоманда. Дескриптор остается открытым, пока вы не сделаете exec 3>&-. На принимающей стороне оболочка, которая читает входные данные из своего стандартного входного потока (подключенного к f), читает ls, запускается, lsа затем ожидает дополнительного ввода (поскольку поток все еще открыт).

Поток остается открытым, потому что все записывающие его (оболочка, via exec 3>fи echo) не закрыли свой конец канала ( exec 3>fвсе еще действует).


Я написал echoвыше, как если бы это была внешняя команда. Скорее всего, он встроен в оболочку. Эффект тот же, тем не менее.

Кусалананда
источник
6

В этом нет ничего особенного: когда нет писателей в канал, он выглядит закрытым для читателей, то есть возвращает EOF при чтении и блоки при открытии.

Из справочной страницы Linux ( pipe(7)но также смотрите fifo(7)):

Если все файловые дескрипторы, относящиеся к концу записи канала, были закрыты, то при попытке read(2)из канала отобразится конец файла ( read(2)вернется 0).

Закрытие конца записи - это то, что неявно происходит в конце echo ls >f, и, как вы говорите, в другом случае файловый дескриптор остается открытым.

ilkkachu
источник
Кажется, что-то вроде подсчета ссылок в Java (и других языках OO)! Имеет смысл, хотя.
Fixee
2

Прочитав два ответа от @Kusalananda и @ikkachu, я понял, что понимаю. В окне 1 оболочка ожидает чего-то, чтобы открыть конец записи канала и затем закрыть его. Как только конец записи открыт, оболочка в окне 1 печатает приглашение. Как только конец записи закрыт, оболочка получает EOF и умирает.

На стороне окна-2 мы имеем две ситуации , описанные в моем вопросе: в первой ситуации с echo ls > f, нет файлового дескриптора 3, поэтому мы echoнерест, а его stdinи stdoutвыглядеть следующим образом :

0 --> tty
1 --> f

Затем echoзавершается, и оболочка закрывает оба дескриптора. Поскольку файловый дескриптор 1 закрыт и ссылается f, конец записи fзакрывается, и это приводит к EOF для window-1.

Во второй ситуации мы запускаем exec 3>fнашу оболочку, заставляя оболочку принимать эту среду:

bash:
0 --> tty
1 --> tty
2 --> tty
3 --> f

Теперь мы запускаем echo ls >& 3и оболочка распределяет файловые дескрипторы echoследующим образом:

echo:
0 --> tty
1 --> f     # because 3 points to f
2 --> tty

Затем оболочка закрывает три описанных выше дескриптора, включая f, но fвсе еще имеет ссылку на нее из самой оболочки. Это важное отличие. Закрывающий дескриптор 3 с exec 3>&-закрывает последнюю открытую ссылку и вызывает EOF к окну-1, как заметил @Kusalananda.

Fixee
источник
Это хороший пример того, почему вы должны оставить первые три файловых дескриптора в покое, если нет веской причины для их изменения. Когда вы использовали дескриптор (1), который оказался входным дескриптором (0) для другой оболочки, вы не только закрыли канал (и то, что вы делали с этим конкретным потоком данных), но также закрыли вход для второго оболочка, которая заставила это прекратить. Это хорошо, но только если вы делаете это нарочно. Использование файловых дескрипторов с более высокими номерами позволяет избежать таких побочных эффектов, потому что ничто не ожидает, что они будут в каком-то конкретном состоянии или даже определены.
Джо
Если честно, я не уверен, что я пытался сказать в этом комментарии, я просто удалю его.
Стефан