Agora, iremos entrar em mais detalhes sobre o uso de pipes em C.
Pipes e Sincronismo entre Processos
Agora, vamos fazer algo um pouco mais complexo e interessante: não vamos somente enviar a informação em um sentido (escrevendo de um lado, e lendo de outro), vamos fazer com que um processos escreva e leia, e o outro também.
Vamos resolver a seguinte questão:
Crie um programa em C onde o processo pai cria um processo filho.
No processo pai, será pedido dois números inteiros, que deverão ser enviados ao filho via pipe, e este vai fazer a soma e devolver o resultado desta operação para o pai, e tudo se repete.
Isso deve ocorrer indefinidamente.
A grande dificuldade desse tipo de evento, é o sincronismo.
Ou seja, existem estágios. Em alguns momentos um processo vai escrever algum dado, e o outro não deve fazer nada, além de esperar.
Depois ocorre o contrário, o processo que esperava é que vai escrever, e o outro é que tem que esperar. Isso é o sincronismo entre pipes, saber quando cada processo deve agir.
Usando dois Pipes para trocar informações
No exemplo do tutorial passado sobre o uso de pipes em C, as informações fluíam apenas em um sentido do pipe. Ou seja, um processo escrevia e o outro lia.Na questão proposta, isso também ocorre.
Bem como o contrário: o pai escreve e o filho lê, depois é o filho que escreve e o pai que lê.
Uma solução para este tipo de situação, bem corriqueira em sistemas operacionais, é através do uso de dois pipes. Veja a imagem que ilustra essa solução:
Uso de dois pipes para troca de informações entre processos |
O pai vai escrever no Pipe 1, o fd1 e vai ler pelo Pipe 2, o fd2.
Já o filho vai ler do Pipe 1 e escrever no Pipe 2.
Ou seja, agora ambos processos podem enviar e receber informações do outro.
Vamos ver como passar essa ideia para código (em C, claro ;)
Como usar dois Pipes em C
O grande cuidado que devemos ter quando estamos trocando informações entre processos usando mais de um pipe é saber o momento em que cada coisa vai fazer algo, ou seja, a sincronia.No próximo item deste tutorial, temos o código em C pronto, comentando e bem organizado.
Agora vamos explicá-lo!
O grande 'segredo' da coisa é a variável 'turn', existente em cada processo, que começa com valor 0.
Turn em inglês quer dizer turno, vez, rodada...ou seja, essa variável vai ditar as regras sobre o que cada processo deve fazer em cada momento.
Depois da criação dos pipes e do uso da chamada de sistema fork(), entramos no processo pai.
Aqui, declaramos o vetor de inteiros num que vai armazenar os dois inteiros do usuário, e o inteiro soma que vai receber o valor da soma dos filhos.
Em seguida devemos fechar os 'lados' do pipe que não vamos usar no processo pai, pela figura acima vemos que é o fd1[0] e o fd2[1], através da função close.
Agora o processo pai e o filho entram em looping infinito, definido por while(1).
Vamos definir dois estados, que ditarão as regras do sincronismo.
O primeiro estado é quando turn=0, aqui vai acontecer duas coisas: o pai vai pedir os números ao usuário e o filho vai esperar por esses dados.
Os números enviados pelo pai estão em um vetor, então basta passarmos o nome desse vetor e o número de bytes que queremos passar pela função write(), este número é o tamanho da variável num (que é calculado por sizeof(num) ).
Quando o pai termina essa tarefa, ele passa o estado para turn=1
Ainda no filho, em turn=0, ele vai receber um vetor e vai fazer a sua variável numeros, que é um ponteiro, apontar para esse vetor que recebeu pela função read, e vai ler sizeof(numeros) bytes.
Em suma: ele vai receber o vetor de inteiros com duas posições, pois foi isso que o pai passou.
Agora que recebeu, também muda seu estado para turn=1
Ok, o pai enviou e o filho recebeu, essa primeira parte já foi. Agora é turn=1
Neste passo, o filho calcula a soma dos dois números (na qual o vetor numeros aponta) e manda esses eles via função write para o pipe. No fim do processo filho, ele volta para turn=0, para que tudo posso ocorrer de novo.
Do outro lado do pipe, ainda no estágio turn=1, o processo pai fica esperando um inteiro através do pipe, usando a read.
Assim que recebe, armazena esse inteiro em sua variável soma, que é um inteiro.
Por fim, o pai exibe a soma e retorna para o valor turn=0, para que o processo volte do começo.
Código comentado em C do uso de Pipes Sincronizados
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> int main(void) { int fd1[2], /* Pai vai escrever e Filho ler por esse file descriptor */ fd2[2], /* Pai vai ler e o Filho escrever por esse file descriptor */ turn=0; /* Vai definir o que cada um vai fazer (ler, escrever, aguardar...) */ pid_t pid; /* Armazena o pid, para o tratamento de pai e filho */ /* Cria o pipe 1 */ if(pipe(fd1)<0) { perror("pipe") ; return -1 ; } /* Cria o pipe 2 */ if(pipe(fd2)<0) { perror("pipe") ; return -1 ; } /* Cria processo filho. */ pid = fork(); if(pid == -1) { perror("fork") ; return -1 ; } if(pid > 0) { /* Processo pai*/ int num[2], /* Números que o processo pai lê*/ soma; /* Resultado da soma, recebido pelo filho*/ /* Fechando o descritor LEITURA no primeiro pipe. */ close(fd1[0]); /* Fechando o descritor ESCRITA no segundo pipe. */ close(fd2[1]); while(1) if(turn==0){ /* Pai vai escreever */ printf("Insira o numero 1: "); scanf("%d", &num[0]); printf("Insira o numero 2: "); scanf("%d", &num[1]); write(fd1[1], num, sizeof(num)); /* Enviando o vetor de números pro filho */ turn=1; /* Passa para o próximo passo, que é o pai ler a soma do filho */ }else if(turn==1){ /* Pai vai ler a soma */ read(fd2[0], &soma, sizeof(soma)); /* Pai leu o resultado da soma, e armazenou no inteiro 'soma' */ printf("Soma: %d\n\n", soma); turn=0; /* Retorna pro passo anterior, pra começar tudo de novo */ } close(fd2[0]); close(fd1[1]); } else { int numeros[2], soma; /* Fechando o descritor ESCRITA no primeiro pipe. */ close(fd1[1]); /* Fechando o descritor LEITURA no segundo pipe. */ close(fd2[0]); while(1){ if(turn==0){ /* Filho vai ler o vetor de numeros do pai */ read(fd1[0], numeros, sizeof(numeros) ); /* Recebeu o vetor de inteiros do pai e colocou no vetor 'numeros' */ turn=1; /* Passa para o próximo passo, que é o filho somar e escrever o resultado da soma */ }else if(turn==1){ /* Filho calcula a soma e retorna pro pai */ soma = numeros[0] + numeros[1]; write(fd2[1], &soma, sizeof(soma)); /* Envia a soma, qúe está na variável 'soma', para o pai */ turn=0; /* Volta para o passo anterior, que é esperar vetor de inteiros do pai */ } } close(fd2[1]); close(fd1[0]); } return 0 ; }
Veja o programa funcionando:
Programa que usa dois pipes de maneira sincronizada |
3 comentários:
Parabéns pela explicação!! Estava com dúvidas e entendi! Reproduzi seu algoritmo, fui compilar mas não rodou, isso seria por eu usar o Windows??
as chamadas de sistemas utilizadas sao para sistemas linux, no windows seria outro esquema
o código só funciona em linux no ambiente windows leva em consideração a API win32
Postar um comentário