Canais nomeados, descritores de arquivo e EOF

10

Duas janelas, mesmo usuário, com prompts do bash. Na janela 1, digite:

$ mkfifo f; exec <f

Portanto, o bash agora está tentando ler o descritor de arquivo 0, que é mapeado para o pipe nomeado f. Na janela 2, digite:

$ echo ls > f

Agora a janela 1 imprime um sl e o shell morre. Por quê?

Próxima experiência: abra a janela-1 novamente com exec <f. Na janela 2, digite:

$ exec 3>f
$ echo ls >&3

Após a primeira linha acima, a janela 1 é ativada e imprime um prompt. Por quê? Após a segunda linha acima, a janela 1 imprime a lssaída e o shell permanece ativo. Por quê? De fato, agora na janela 2, echo ls > fnão fecha o shell da janela 1.

A resposta deve ter a ver com a existência do descritor de arquivo 3 da janela 2, referenciando o pipe nomeado ?!

Fixee
fonte
1
Depois exec <f, bashnão está tentando ler a partir f, é primeira tentativa de abrir -lo. Ele open()não retornará até que haja algum processo fazendo outra abertura no modo de gravação no canal (nesse momento, o canal será instanciado e o shell lerá a entrada dele).
Stéphane Chazelas
Excelente ponto, @ StéphaneChazelas. Deve ser por isso que, uma vez exec 3>fexecutado, o primeiro shell fornece um prompt. (Ponto
secundário
1
sim, desculpe. Editado agora antes do prazo de 5 minutos
Stéphane Chazelas

Respostas:

12

Tem a ver com o fechamento do descritor de arquivo.

No seu primeiro exemplo, echograva em seu fluxo de saída padrão com o qual o shell é aberto para conectá-lo e f, quando termina, seu descritor é fechado (pelo shell). No lado de recebimento, o shell, que lê a entrada de seu fluxo de entrada padrão (conectado a f) lê ls, é executado lse finalizado devido à condição de fim de arquivo em sua entrada padrão.

A condição de fim de arquivo ocorre porque todos os gravadores no pipe nomeado (apenas um neste exemplo) fecharam sua extremidade do pipe.

No seu segundo exemplo, exec 3>fabre o descritor de arquivo 3 para gravar fe depois echograva lsnele. É o shell que agora tem o descritor de arquivo aberto, não o echocomando. O descritor permanece aberto até você exec 3>&-. No lado de recebimento, o shell, que lê a entrada de seu fluxo de entrada padrão (conectado a f) lê ls, executa lse aguarda por mais entrada (já que o fluxo ainda está aberto).

O fluxo permanece aberto porque todos os gravadores (o shell, via exec 3>fe echo) não fecharam a extremidade do tubo ( exec 3>fainda está em vigor).


Eu escrevi sobre echoacima como se fosse um comando externo. Provavelmente está embutido no shell. O efeito é o mesmo, no entanto.

Kusalananda
fonte
6

Não há muito o que fazer: quando não há gravadores no canal, ele parece fechado para os leitores, ou seja, retorna EOF quando lido e bloqueia quando aberto.

Na página do manual do Linux ( pipe(7), mas veja também fifo(7)):

Se todos os descritores de arquivo referentes à extremidade de gravação de um canal tiverem sido fechados, uma tentativa read(2)do canal verá o final do arquivo ( read(2)retornará 0).

Fechar o final da gravação é o que acontece implicitamente no final do echo ls >fe, como você diz, no outro caso, o descritor de arquivo é mantido aberto.

ilkkachu
fonte
Parece meio análogo às contagens de referência em Java (e outras linguagens OO)! Faz sentido embora.
Fixee
2

Depois de ler as duas respostas de @Kusalananda e @ikkachu, acho que entendi. Na janela 1, o shell está aguardando algo abrir a extremidade de gravação do pipe e depois fechá-la. Depois que o final da gravação é aberto, o shell na janela-1 imprime um prompt. Depois que o final da gravação é fechado, o shell obtém EOF e morre.

Do lado da janela-2 temos as duas situações descritas na minha pergunta: na primeira situação com echo ls > f, não há nenhum descritor de arquivo 3, por isso temos echode desova, e sua stdine stdoutolhar como este:

0 --> tty
1 --> f

Em seguida, echotermina e o shell fecha os dois descritores. Como o descritor de arquivo 1 está fechado e faz referência f, o final da gravação de fé fechado e isso faz com que um EOF seja exibido na janela 1.

Na segunda situação, executamos exec 3>fem nosso shell, fazendo com que o shell pegue este ambiente:

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

Agora, rodamos echo ls >& 3e o shell aloca descritores de arquivos da echoseguinte maneira:

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

Em seguida, o shell fecha os três descritores acima, incluindo f, mas fainda tem uma referência a partir do próprio shell. Essa é a diferença importante. Fechar o descritor 3 exec 3>&-fecharia a última referência aberta e causaria um EOF na janela 1, como observou @Kusalananda.

Fixee
fonte
Este é um bom exemplo de por que você deve deixar os três primeiros descritores de arquivo em paz, a menos que haja um bom motivo de design para modificá-los. Quando você usou o descritor (1) que acabou sendo o descritor de entrada (0) para o outro shell, você não apenas fechou o canal (e o que estava fazendo com esse fluxo de dados específico), mas também fechou a entrada no segundo shell que causou o seu término. Isso é bom, mas apenas se você estiver fazendo isso de propósito. O uso de descritores de arquivos com números mais altos evita efeitos colaterais como esse, porque nada espera que eles estejam em um estado específico ou mesmo definido.
23418 Joe
Para ser sincero, não sei ao certo o que estava tentando dizer nesse comentário, vou excluí-lo.
Stéphane Chazelas