paulo1205
(usa Ubuntu)
Enviado em 07/03/2016 - 11:59h
Pipes (que em Português significa "canos" ou "tubos") são simples: dados colocados na ponta de entrada do tubo (por onde o programa envia dados) ficam disponíveis na ponta de saída do tubo (de onde um programa pode receber dados).
Depois de criado um pipe, você escreve ou lê dele do mesmo jeito como escreve ou lê de um arquivo.
Quando você pede ao sistema a criação de um pipe (
pipe ()), ele devolve dois descritores de arquivo: o primeiro se refere ao lado de recepção (leitura, com
read ()), e o segundo corresponde ao lado de envio (escrita, com
write ()).
Pipes geralmente servem para comunicação entre processos diferentes. Isso se consegue devido à propriedade de sistema UNIX-like de manter abertos os descritores de arquivo quando se cria um processo novo (
fork() ) e quando se substitui o programa corrente por outro (
execve (), exceto quando o descritor é marcado com a propriedade
FD_CLOEXEC via
fcntl ()).
Quando se tenta ler dados de um
pipe (), a chamada a
read () no processo leitor aguarda a chegada da quantidade solicitada de bytes. Se, no entanto, a ponta de envio de dados já tiver sido fechada, a leitura() é interrompida, devolvendo apenas a quantidade de bytes disponíveis. Em particular, um leitura que retorne zero bytes é um sinal inequívoco de que a ponta de envio foi fechada.
Quando se tenta escrever num pipe, a chamada a
write () pode bloquear se o buffer interno do sistema operacional referente ao pipe estiver cheio (o que geralmente significa que a ponta de recepção de dados os está lendo numa velocidade menor do que a de escrita na ponta de envio). Quando se tenta escrever dados no pipe cuja ponta de recepção foi fechada, o sistema operacional envia o sinal
SIGPIPE ao processo, cujo tratamento padrão é matar o processo. Se o processo tiver definido um tratador não-padrão de
SIGPIPE , incluindo os tratamentos de ignorar ou bloquear o sinal (
sigaction () ou
sigprocmask ()), a chamada a
write () retorna erro, e a variável
errno recebe o valor
EPIPE .
Se, em vez de
read (),
write () e
close () você preferir usar funções de mais alto nível, como
fgetc ()/
fgets ()/
fscanf ()/
fread (),
fputc ()/
fprintf ()/
fwrite () e
fclose (), você pode associar os descritores do pipe a streams, por meio da função
fdopen ().
Se o programa com o qual você deseja se comunicar espera ler da entrada padrão (ou escrever na saída padrão), você pode usar as chamadas
dup () e
dup2 () para mudar o número do descritor de uma das pontas do pipe.
Entendeu tudo? Bom, é mais fácil entender quando você sabe como funciona um processo no UNIX.
De todo modo, segue abaixo um exemplo (inútil, mas funcional, e cujo resultado você pode testar depois), usando a maioria das funções que eu referi acima. Ele cria um pipe e um processo filho, no qual executa o comando externo
gzip , a fim de comprimir texto formatado enviado pelo processo pai e gravar num arquivo de saída os dados comprimidos (na vida real ninguém faria esse tipo de coisa, pois seria mais eficiente usar as funções da biblioteca Zlib para gerar o arquivo diretamente, com um único processo). Note que eu verifico erro em todos os lugares em que existe chance de falhas.
/* Gera arquivo comprimido com linhas que contam de 0 a 99. */
#include <errno.h>
#include <signal.h>
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
const char OUTPUT_FILE[]="/tmp/saida.gz";
int main(void){
int pipe_fds[2];
if(pipe(pipe_fds)==-1){
fprintf(stderr, "Falha ao tentar criar pipe: %s.\n", strerror(errno));
return 1;
}
/*
Neste ponto, tenho o pipe criado. pipe_fds[0] é a ponta de leitura,
e pipe_fds[1], a de escrita.
*/
pid_t child=fork();
if(child==-1){
fprintf(stderr, "Falha ao criar processo filho: %s.\n", strerror(errno));
return 1;
}
if(child==0){
/* Processo filho (child==0). */
/*
O processo filho recebe uma cópia de todos os descritores de arquivo
do processo pai. Isso significa que, neste momento, tanto o pai quanto
o filho podem tanto escrever quanto ler do pipe.
Como o filho não vai escrever no pipe, é NECESSÁRIO fechar aqui
a ponta de escrita.
*/
close(pipe_fds[1]);
/*
Pai e filho executam ao mesmo tempo, mas depois que o pai tiver
fechado a ponta de leitura, de fato teremos um "tubo" que tem
realmente apenas um descritor associado à ponta de entrada (no
pai, que escreve em pipe_fds[1]) e um à de saída (no filho, que lê
de pipe_fds[0]).
*/
/*
Como o gzip vai ler da entrada padrão, vamos copiar o descritor
de leitura do pipe por cima da entrada padrão, e depois fechar
o descritor original.
*/
if(dup2(pipe_fds[0], STDIN_FILENO)==-1){
fprintf(stderr, "Falha ao associar pipe com entrada padrão: %s.\n", strerror(errno));
_exit(1);
}
close(pipe_fds[0]);
if(freopen(OUTPUT_FILE, "wb", stdout)==NULL){
fprintf(stderr, "Falha ao criar arquivo de saída: %s.\n", strerror(errno));
_exit(1);
}
execlp("gzip", "gzip", "-9", NULL);
fprintf(stderr, "Erro ao executar gzip: %s.\n", strerror(errno));
_exit(1);
}
/* Processo pai (child>0). */
/*
O processo filho recebe uma cópia de todos os descritores de arquivo
do processo pai. Isso significa que, neste momento, tanto o pai quanto
o filho podem tanto escrever quanto ler do pipe.
Como o pai não vai ler do pipe, é NECESSÁRIO fechar aqui a ponta de
leitura.
*/
close(pipe_fds[0]);
/*
Pai e filho executam ao mesmo tempo, mas depois que o filho tiver
fechado a ponta de escrita, de fato teremos um "tubo" que tem
realmente apenas um descritor associado à ponta de entrada (no
pai, que escreve em pipe_fds[1]) e um à de saída (no filho, que lê
de pipe_fds[0]).
*/
FILE *send2gzip=fdopen(pipe_fds[1], "w");
if(send2gzip==NULL){
fprintf(stderr, "Falha ao associar stream à entrada do pipe: %s.\n", strerror(errno));
return 1;
}
/*
Ignoro o sinal enviado em caso de falha de escrita no PIPE, fazendo com
que, em caso de erro, as operações de escrita retornem com erro, em vez
de matar o processo.
*/
struct sigaction sa;
memset(&sa, 0, sizeof sa);
sa.sa_handler=SIG_IGN;
sigaction(SIGPIPE, &sa, NULL);
int n;
for(n=0; n<100; n++){
errno=0;
fprintf(send2gzip, "%d\n", n);
if(errno!=0){
fprintf(stderr, "Falha ao escrever \"%d\" no pipe: %s.\n", n, strerror(errno));
return 1;
}
}
if(fclose(send2gzip)==EOF){
fprintf(stderr, "Falha ao escrever no pipe: %s.\n", strerror(errno));
return 1;
}
int status;
waitpid(child, &status, 0);
if(status!=0){
fprintf(stderr, "Processo filho terminou com erro (status=%d).\n", status);
return 1;
}
return 0;
}