Comunicação entre Processos
Ok, em nossos estudos sobre a chama de sistema fork() vimos que é possível, a partir de um processo (dito pai), criar outro processo, o filho.
Também fizemos isso usando system() e execv().
Vimos que ao ser criado o processo filho, este se torna uma cópia fiel do código do processo pai, mas com suas próprias variáveis, registros, dados etc.
Porém, uma coisa é de vital importância nos processos: a comunicação entre eles.
Processos não existem isolados. Geralmente eles são invocados com alguns dados, para que faça alguma função específica, algum cálculo etc, e retornem algum tipo de informação para o pai ou outro processo.
Ou seja, é bem comum em certos programas, e no Sistema Operacional, que dois ou mais processos se comuniquem, um invocando o outro, passando informações, recebendo, esperando um terminar para iniciar o outro, checando se um processo terminou etc etc etc.
Por exemplo, o comando no terminal do Linux: ps aux | grep [algo]
O comando "ps aux" vai te listar todos os processos que estão ocorrendo no sistema, uma tabela enorme.
Já o "grep [algo]" exibe as linhas de um texto que apareceu a palavra 'algo'.
O símbolo | vai fazer a comunicação entre esses dois processos: pega o resultado de ps aux e joga como entrada pro grep.
Por exemplo, "ps aux | grep chrome", o resultado na sua tela vai ser as linhas com os dados dos processos executados pelo browser Google Chrome.
Note que o grep só pode executar quando o ps aux terminar. Ou seja, eles se comunicaram, trocaram informações para uma finalidade só.
Vamos aprender como fazer isso usando pipes em C.
Pipe em C - O que é
Pipe em inglês quer dizer tubo, cano...ou seja, algo que tem uma abertura em um lado, um caminho e uma abertura na outra ponta. Os pipes conectam um local a outro diretamente.O nome de pipe, em C, não é por menos: vamos estabelecer uma comunicação entre dois processos, um do lado e outro do outro do pipe.
Um processo escreve o dado de um lado, e outro processo lê do outro. Uma comunicação direta entre eles.
Nosso cano, ou pipe, porém é diferente: ele tem um sentido. Entre informação só de um lado, e sai só do outro.
Assim, precisaremos de duas coisas para definir um pipe: algo que caracterize a entrada, que onde iremos escrever (usando a chamada write()) e algo que caracterize a saída, onde iremos ler (chamada read()).
Essas duas coisas que iremos precisar parar criar o pipe são os file descriptors, que nada mais são que dois inteiros que vão definir a entrada-padrão e a saída-padrão do pipe. Na verdade precisaremos apenas de uma coisa, um vetor de inteiros de duas posições.
O primeiro elemento (elemento 0) do vetor define a leitura (saída) de dados, e o segundo elemento (de índice 1) define a escrita de dados no pipe (entrada).
Funcionamento de um Pipe em C (fonte: beej.us) |
Pipe em C - Como Programar
Para definir os file descriptors, primeiro declarando um inteiro de duas posições e o passamos para a função pipe().Essa chamada de sistema pipe() retorna -1 em caso de erro.
Assim nosso código inicial de criação de pipes em C é:
int fd[2]; if (pipe(fd) == -1) { perror("pipe"); exit(1); }
Faremos agora uso de duas outras chamadas de sistema agora, a write e a read.
Ambas funções tem três parâmetros.
O primeiro é o file descriptor, para especificar o pipe que estão usando.
O segundo argumento que passamos para essas chamadas de sistema é um endereço de memória, ou seja, passamos um ponteiro.
O terceiro dado que passamos para essas funções é o número de bytes, dizendo que a partir daquele endereço de memória (do segundo parâmetro), quantas informações vamos escrever/ler pelo pipe.
Exemplo de uso de Pipe em C - Pai envia string para processo Filho
Vamos fazer um exemplo do uso de Pipe em C, onde vamos criar uma string e enviar ele do processo pai para o filho. Vamos usar as bibliotecas unistd.h e sys/types.h para este exemplo.O primeiro passo é criar nosso pipe, transformando o vetor de inteiros fd em nossos file descriptors.
Em seguida, usamos a chamada de sistema fork() para criar o processo filho.
No processo pai, declaramos a string 'str' e fechamos o fd[0], pois este se refere a leitura no pipe, e o pai só vai escrever no pipe. Fazemos isso utilizando a função close().
Declaramos nossa string, e escrevemos ela no pipe.
Lembre-se que uma string é um vetor, que é um ponteiro que aponta para o primeiro elemento dela.
Por isso passamos o nome dela mesmo 'str' como argumento, pois ela é um ponteiro.
O número de bytes que queremos passar a parte do primeiro endereço de memória da string é sizeof(str) + 1, o '+1' é por causa do caractere \0, que delimita uma string.
Já no processo filho, declaramos a string str_recebida, que onde o pipe vai armazenar tudo que vai ler.
Veja o código deste exemplo:
#include <stdio.h> #include <stdlib.h> #include <sys/types.h> #include <unistd.h> #define BUFFER 256 int main(void) { int fd[2]; /* File descriptors pro Pipe*/ pid_t pid; /* Variável para armazenar o pid*/ /* Criando nosso Pipe */ if(pipe(fd)<0) { perror("pipe") ; return -1 ; } /* Criando o processo filho*/ if ((pid = fork()) < 0) { perror("fork"); exit(1); } /* Processo Pai*/ if (pid > 0) { /*No pai, vamos ESCREVER, então vamos fechar a LEITURA do Pipe neste lado*/ close(fd[0]); char str[BUFFER] = "Aprendi a usar Pipes em C!"; printf("String enviada pelo pai no Pipe: '%s'", str); /* Escrevendo a string no pipe */ write(fd[1], str, sizeof(str) + 1); exit(0); } /* Processo Filho*/ else { char str_recebida[BUFFER]; /* No filho, vamos ler. Então vamos fechar a entrada de ESCRITA do pipe */ close(fd[1]); /* Lendo o que foi escrito no pipe, e armazenando isso em 'str_recebida' */ read(fd[0], str_recebida, sizeof(str_recebida)); printf("String lida pelo filho no Pipe : '%s'\n\n", str_recebida); exit(0); } return(0); }
O resultado foi:
4 comentários:
Olá, muito bom o post, estou com uma dúvida do motivo de colocar "exit(0)" no fork e "return -1" para o pipe em caso de erro
Olá. Queria reportar um equívoco no código. A função sizeof() retorna o tamanho em bytes da estrutura passada como argumento. No caso, sizeof(str) retorna 256, já que str foi declarada como um vetor de 256 bytes (cada char é 1 byte). Então o processo pai está escrevendo no pipe a string e mais outros bytes que no caso serão considerados lixo.
A função para obter o tamanho da string é strlen(), da biblioteca string.h, que aí sim retorna o tamanho da string sem considerar o \0.
Além disso, na parte do filho faltou somar 1 ao ler a string (depois de trocar o sizeof por strlen)
Excelente site, grande parte da minha base de C e Java veio daqui! Muito obrigado!
Muito obrigado pela aula
Foi a melhor aula que tive sobre pipes
E em pouco tempo
Muito obrigado pela aula
Foi a melhor aula sobre pipes que tive
E em pouco tempo pôde aprender muitíssimo
Postar um comentário