Como os pipes funcionam no Linux

25

Eu tenho lido sobre como os pipes são implementados no kernel do Linux e queria validar meu entendimento. Se estiver incorreto, a resposta com a explicação correta será selecionada.

  • O Linux possui um VFS chamado pipefs montado no kernel (não no espaço do usuário)
  • O pipefs possui um único super bloco e é montado em sua própria raiz ( pipe:), ao lado de/
  • pipefs não podem ser visualizados diretamente ao contrário da maioria dos sistemas de arquivos
  • A entrada para pipefs é via pipe(2)syscall
  • O pipe(2)syscall usado pelos shells para canalizar com o |operador (ou manualmente de qualquer outro processo) cria um novo arquivo nos pipefs que se comporta praticamente como um arquivo normal
  • O arquivo no lado esquerdo do operador de stdoutcanal é redirecionado para o arquivo temporário criado nos pipefs
  • O arquivo no lado direito do operador do tubo está stdindefinido como o arquivo em pipefs
  • pipefs é armazenado na memória e, através de alguma mágica do kernel, não deve ser paginado

Esta é a explicação de como os tubos (por exemplo ls -la | less) funcionam praticamente corretos?

Uma coisa que não entendo é como algo como o bash definiria um processo ' stdinou stdoutpara o descritor de arquivo retornado por pipe(2). Ainda não consegui encontrar nada sobre isso.

Brandon Wamboldt
fonte
Observe que você está falando de duas camadas consideravelmente diferentes de coisas com o mesmo nome. A pipe()chamada do kernel, juntamente com o mecanismo que a suporta ( pipefs, etc), é um nível muito mais baixo do que o |operador oferecido no seu shell. O último é geralmente implementado usando o primeiro, mas não precisa ser.
Greg Hewgill
Sim, estou me referindo especificamente às operações de nível inferior, com a suposição de que o |operador está apenas chamando pipe(2)como um processo como o bash.
Brandon Wamboldt
Veja também Qual é a diferença entre "Redirecionamento" e "Tubo"?
Sergiy Kolodyazhnyy 12/09

Respostas:

19

Sua análise até o momento é geralmente correta. A maneira como um shell pode definir o stdin de um processo para um descritor de canal pode ser (pseudocódigo):

pipe(p) // create a new pipe with two handles p[0] and p[1]
fork() // spawn a child process
    close(p[0]) // close the write end of the pipe in the child
    dup2(p[1], 0) // duplicate the pipe descriptor on top of fd 0 (stdin)
    close(p[1]) // close the other pipe descriptor
    exec() // run a new process with the new descriptors in place
Greg Hewgill
fonte
Obrigado! Apenas curioso por que a dup2chamada é necessária e você não pode apenas atribuir diretamente o descritor de pipe ao stdin?
Brandon Wamboldt
3
O chamador não escolhe qual é o valor numérico do descritor de arquivo quando ele é criado pipe(). A dup2()chamada permite que o chamador copie o descritor de arquivo para um valor numérico específico (necessário porque 0, 1, 2 são stdin, stdout, stderr). Esse é o equivalente do kernel de "atribuir diretamente ao stdin". Observe que a variável global da biblioteca de tempo de execução C stdiné a FILE *, que não está relacionada ao kernel (embora tenha sido inicializada para ser conectada ao descritor 0).
Greg Hewgill
Ótima resposta! Estou um pouco perdido nos detalhes. Imaginando por que você fecha (p [1]) antes de executar exec ()? Quando o dup2 retornar, não p [1] apontará para fd 0? Então close (p [1]) fecha o descritor de arquivo 0. Então, como podemos ler a partir do stdin do processo filho?
user1559897
@ user1559897: A dup2chamada não muda p[1]. Em vez disso, ele cria as duas alças p[1]e 0aponta para o mesmo objeto do kernel (o pipe). Como o processo filho não precisa de dois identificadores stdin (e não saberia qual é o identificador numerado de p[1]qualquer maneira), ele p[1]foi fechado antes exec.
Greg Hewgill
@GregHewgill Gotchu. THX!
User1559897