Pipes em C - Comunicação entre Processos (IPC - Interprocess Communication)

Agora que já aprendemos como criar e gerenciar processos com a chamada de sistema fork(), bem como os processos disputam recursos de um computador, vamos aprender agora um dos conceitos de comunicação entre processos mais importantes existentes, os pipes.



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).

Pipes em C - O que é, Como usar, Para que servem, write e read
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:
Pipes em C - Como programar e usar

4 comentários:

Eduardo Morais disse...

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

Rafael Rodrigues dos Santos disse...

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!

Anônimo disse...

Muito obrigado pela aula
Foi a melhor aula que tive sobre pipes

E em pouco tempo

João Capemba disse...

Muito obrigado pela aula
Foi a melhor aula sobre pipes que tive

E em pouco tempo pôde aprender muitíssimo

Veja também: