Entendendo Pipe e FIFO (parte 2)

Esta é a segunda parte da postagem sobre Pipes e Fifos aqui do blog. Se ainda não viu a primeira parte, pode acessá-la por aqui. Nesta parte será explicado como processos podem ter seus dados interligados, exatamente como o shell faz no comando ls | wc -l. Para entender como este processo ocorre é necessário entender que existem três “arquivos” que são abertos quando um processo se inicia.

Estes arquivos são:

  • entrada padrão ou stdin (file descriptor zero)
  • a saída padrão ou stdout (file descriptor um)
  • saída de erros ou stderr (file descriptor dois)

Ao utilizarmos a função printf, a string é automaticamente enviada para a stdout, ou saída padrão. Ainda assim podemos utilizar a função fprintf ou até mesmo a função write para especificar para onde queremos enviar a string, podendo ser a stdout, stderr ou ainda um fd de um arquivo aberto pelo usuário.

Utilizando estes file descriptors, podemos fazer com que a saída de um processo seja direcionado para a entrada de outro processo, exatamente como se estivéssemos colocando um “cano”  entre os processos. O código abaixo, mostra um exemplo deste redirecionamento ao executar a função ls e direcionar a saída para outro processo. Algumas partes são comuns do exemplo do último artigo, pois o mesmo mecanismo de pipes é utilizado:

Este programa executa duas chamadas fork, sendo cada uma delas para criar um dos processos que serão conectados pelo pipe. O código abordado possui muitas chamadas de sistema relacionadas a arquivos, e todos estes serão explicados agora: fds[1] != STDOUT_FILENO Esta validação é necessária para validar se o stdout não foi previamente redirecionado para o fd[1] por um outro programa que chame o programa atual, e funciona como programação defensiva. dup2(fds[1], STDOUT_FILENO) A chamada de sistema dup2 duplica o fd do primeiro parâmetro, fecha o fd do segundo parâmetro e reabre o fd do segundo parâmetro com as características do primeiro fd. Quando fechamos um arquivo no Linux e reabrimos outro, este tende a pegar o número do fd do arquivo que foi fechado. Neste ponto, ao usar o dup2 nós estamos tentando fazer com que o fd zero, que é usado como entrada padrão, seja o fd[1] do pipe. Ou seja, quando o programa executar um printf, os dados que seriam enviados a saída padrão (file descriptor zero) será enviado ao fd[1] (que na verdade é o novo fd zero). É um pouco confuso no início, mas o código abaixo tenta exemplificar este comportamento de reusar o número de fd:

A execução do programa acima é:
[email protected]: [exemplos_c] # ./show_fd_reuse
fd aberto é: 3
novo fd é: 3

Assim podemos entender um pouco melhor como os sistemas *NIX usam seus fds. E outro detalhe: O fd número três é utilizado, pois os fds zero, um e dois já estão sendo utilizados por stdin, stdout e stderr.

execlp(“ls”, “ls”, (char *)NULL);
Esta chamada de sistema carrega um novo programa no espaço de memória do processo atual. Após a chamada fork, um novo processo é iniciado, e este continua a executar do ponto onde foi criado, e então a chamada execlp é utilizada para carregar o novo processo, apagando o processo pai do espaço de memória do processo criado. Um detalhe importante: este novo processo terá sua saída padrão sendo o fd[1], e desta forma estaremos enviando toda a saída do comando ls para o outro processo que irá ser criado.

Após esse ponto será criado um outro processo que irá pegar a saída do comando ls executado anteriormente.

dup2(fds[0], STDIN_FILENO)
execlp(“wc”, “wc”, “-l”, (char *)NULL)
Primeiramente a entrada padrão do processo do novo processo criado será atribuída ao fds[0], que é a parte de leitura do pipe. Após redirecionar a entrada padrão para que esta leia do pipe, o novo processo então executa o execlp para carregar o programa wc, passando o parâmetro -l.

close(fds[0])
close(fds[1])
wait(NULL)
wait(NULL)
Após executar ambos processos, então é necessário esperar para que ambos os processos terminem. A chamada de sistema wait faz com que a execução do processo pare e espere pelo término de um processo filho. Como não é especificado qual processo filho desejamos parar, então é executada a chamada de sistemas duas vezes para esperar pelo término dos dois processos.

Após a execução do programa, pode-se comparar sua saída do comando ls | wc -l c no bash:
[[email protected] pipe_fifo]$ ls | wc -l
4

[[email protected] pipe_fifo]$ ./pipe_connect
4

Espero que tenham gostado desta segunda parte da postagem. A próxima e última parte abordará Fifos. Assine nosso feed e se inscreva em nossas páginas nas redes sociais para saber em primeira mão quando um novo artigo sair. Até mais!