Como um FIFO (pipe nomeado) difere de um pipe regular (pipe sem nome)? [duplicado]

12

Como um FIFO (pipe nomeado) difere de um pipe regular (|)? Pelo que entendi de Wikipedia , ao contrário de um pipe regular, um pipe FIFO "continua vivo" após o processo ter terminado e pode ser excluído algum dia depois.

Mas se o processo é baseado em um comando shell contendo um pipe ( cat x | grep y ), poderíamos "mantê-lo ativo após o processo" se o armazenarmos em uma variável ou arquivo, então não é um FIFO?

Além disso, um pipe regular também tem o primeiro stdout que recebe, como stdin para outro comando , então não é também um tipo de primeiro no primeiro canal também?

    
por user9303970 10.04.2018 / 23:05

2 respostas

19

"Named pipe" é na verdade um nome muito preciso para o que é - é como um pipe regular, exceto pelo fato de ter um nome (em um sistema de arquivos).

Um canal - o nome comum, não nomeado ("anônimo") usado em some-command | grep pattern é um tipo especial de arquivo. E eu quero dizer arquivo, você lê e escreve para ele assim como você faz todos os outros arquivos. O Grep realmente não se importa se está lendo um pipe em vez de um terminal³ ou um arquivo comum.

Tecnicamente, o que acontece nos bastidores é que stdin, stdout e stderr são três arquivos abertos (descritores de arquivo) passados para cada execução de comando. Os descritores de arquivos (que são usados em todos os arquivos syscall para leitura / gravação / etc.) São apenas números; stdin, stdout e stderr são descritores de arquivos 0, 1 e 2. Assim, quando seu shell configura some-command | grep , o que ele faz é algo assim:

  1. Pergunta ao kernel por um canal anônimo. Não há nome, portanto, isso não pode ser feito com open , como em um arquivo normal. Em vez disso, ele é feito com pipe ou pipe2 , que retorna dois descritores de arquivo.⁴

  2. Garante um processo filho ( fork() cria uma cópia do processo pai; ambos os lados do canal estão abertos aqui), copia o lado de gravação do canal para fd 1 (stdout). O kernel tem um syscall para copiar em torno de números de descritores de arquivos; é dup2() ou dup3() . Em seguida, fecha o lado de leitura e outra cópia do lado de gravação. Finalmente, usa execve para executar some-command . Como o pipe é fd 1, stdout de some-command é o pipe.

  3. Garfos de outro processo filho. Desta vez, duplica o lado de leitura do pipe para fd 0 (stdin) e executa grep . Então o grep irá ler o pipe como stdin.

  4. Em seguida, ele espera que ambas as crianças saiam.

  5. Neste ponto, o kernel percebe que o pipe não está mais aberto e o lixo o coleta. Isso é o que realmente destrói o tubo.

Um pipe nomeado apenas dá um nome ao pipe anônimo colocando-o no sistema de arquivos. Então agora qualquer qualquer processo, a qualquer momento no futuro, pode obter um descritor de arquivo para o pipe usando um open syscall comum. Conceitualmente, o pipe não será destruído até que todos os leitores / escritores o tenham fechado e seja unlink ed do sistema de arquivos.²

Isto, a propósito, é como os arquivos em geral funcionam no Unix. unlink (o syscall atrás de rm ) apenas remove um dos nomes para o arquivo; somente quando todos os nomes forem removidos e nada tiver o arquivo aberto, ele será realmente excluído. Algumas respostas aqui exploram isso:

NOTAS

  1. Tecnicamente, isso provavelmente não é verdade - provavelmente é possível fazer algumas otimizações sabendo, e implementações reais do grep têm sido altamente otimizadas. Mas, conceitualmente, não se importa (e de fato uma implementação direta do grep).
  2. É claro que o kernel na verdade não mantém todas as estruturas de dados na memória para sempre, mas as recria, transparentemente, sempre que o primeiro programa abre o pipe nomeado (e as mantém contanto que estejam abertas). Então é como se eles existissem desde que o nome existisse.
  3. O terminal não é um lugar comum para o grep ler, mas é o padrão stdin quando você não especifica outro. Então, se você digitar apenas grep pattern no seu shell, grep estará lendo no terminal. O único uso para isso que vem à mente é se você está prestes a colar algo no terminal.
  4. No Linux, os pipes anônimos são criados em um sistema de arquivos especial, pipefs. Veja Como os canos funcionam no Linux para detalhes. Note que este é um detalhe de implementação interna do Linux.
por 11.04.2018 / 01:11
3

Eu acho que você está se misturando entre sintaxe de shell para pipelines versus a programação de sistemas Unix subjacente. Um pipe / FIFO é um tipo de arquivo que não é armazenado em disco, mas passou os dados de um gravador para um leitor através de um buffer no kernel.

Um pipe / FIFO funciona da mesma forma se o escritor e o leitor se conectaram fazendo chamadas de sistema como open("/path/to/named_pipe", O_WRONLY); , ou com um pipe(2) para criar um novo canal anônimo e retornar descritores de arquivos abertos para os lados de leitura e gravação.

fstat(2) no descritor de arquivo pipe lhe dará sb.st_mode & S_IFMT == S_IFIFO de qualquer forma.

Quando você executa foo | bar :

  • O shell se bifurca como normal para qualquer comando não embutido
  • Em seguida, faz uma chamada do sistema pipe(2) para obter dois descritores de arquivo: a entrada e a saída de um canal anônimo.
  • Em seguida, ele bifurca novamente .
  • O filho (onde fork() retornou 0)
    • fecha o lado de leitura do pipe (deixando o write fd aberto)
    • e redireciona stdout para o write fd com dup2(pipefd[1], 1)
    • , em seguida, execve("/usr/bin/foo", ...)
  • O pai (onde fork() retornou o PID não 0 filho)
    • fecha o lado de gravação do pipe (deixando a leitura fd aberta)
    • e redireciona stdin da leitura fd com dup2(pipefd[0], 0)
    • , em seguida, execve("/usr/bin/bar", ...)

Você entra em uma situação muito semelhante se executar foo > named_pipe & bar < named_pipe .

Um pipe nomeado é um ponto de encontro para que os processos estabeleçam pipes entre eles.

A situação é semelhante a arquivos tmp anônimos vs. arquivos com nomes. Você pode open("/path/to/dir", O_TMPFILE | O_RDWR, S_IRUSR | S_IWUSR); criar um arquivo temporário sem nome ( O_TMPFILE ), como se tivesse aberto "/path/to/dir/tmpfile" com O_CREAT e, em seguida, desvinculado, deixando você com um descritor de arquivo para um arquivo excluído.

Usando linkat , você pode até mesmo vincular esse arquivo anônimo ao sistema de arquivos, dando-lhe um nome, se ele foi criado com O_TMPFILE . ( Você não pode fazer isso no Linux para arquivos criados com um nome excluído, no entanto. )

    
por 11.04.2018 / 02:21

Tags