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.


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

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

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
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.