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.