Pipes

Este post faz parte da série IPC(Inter process communication)

Antes de seguir esse artigo é imprescindível a instalação da biblioteca hardware caso queria utilizar o hardware da Raspberry.

Introdução

Pipes é um recurso IPC empregado para conectar a saída de um processo a entrada de um outro processo. Pipes é largamente utilizado pelo CLI(Command Line Interface), como por exemplo em uma consulta simples listando todos os arquivos de um respectivo diretório e filtrando por arquivos de extensão *.txt, normalmente usamos ls seguido de grep, como demonstrado no comando abaixo:

$ ls | grep *.txt

O comando descrito transfere os dados de saída gerado pelo comando ls que seriam apresentados no stdout e são passados como argumentos de entrada(stdin) para o comando grep que filtra o resultado e apresenta os arquivos que possuem a extensão. Em outras palavras o Pipes permite que o stdout do [Processo A] se conecte com o stdin do [Processo B].

O fluxo da conexão obedece a seguinte ordem o stdout se conecta com o stdin, porém fluxo contrário não é permitido, para isso é necessário estabelecer um conexão do processo B para o A.

Criando Pipes

Para criar pipes é utilizado a system call

#include <unistd.h>

int pipe(int filedes[2]);

Pipes após um fork

O fork é tem a característica de clonar todo o processo, e devido a isso tudo que for aberto pelo processo pai vai ser refletido no processo filho, sendo assim, se um pipe for aberto no processo pai o processo filho também irá herdar o pipe, a figura a seguir pode representar essa situação:

Para garantir que o processo pai se comunicará com o processo filho na forma correta, é necessário fechar os descritores, de modo que o stdout do pai fique conectado ao stdin do filho, a imagem a seguir representa essa configuração:

Implementação

Para exemplificar Pipes foi criado uma aplicação no estilo cliente-servidor, onde o processo que controla o botão é responsável pela requisição da mudança de estado do LED comportando-se como cliente, e o processo que controla o LED é responsável pelo o controle do pino físico, ou seja, a alteração do estado efetivo do LED comportando-se como servidor da aplicação; A aplicação é composta por três executáveis descritos a seguir:

  • launch_processes – processo responsável por lançar os processos button_process e led_process através da combinação fork e exec
  • button_interface – processo responsável por ler o GPIO em modo de leitura do Raspberry Pi e enviar o estado via Pipe para o processo led_interface
  • led_interface – processo reponsável por controlar o GPIO em modo de escrita do Raspberry e alterar o estado do pino de acordo com o input fornecido pelo processo button_interface.

launch_processes.c

Primeiro são declaradas variáveis para a criação do pipe e duas variáveis para identificar quem é o stdout e o stdin, e é chamada a system call de criação do pipe

int file_pipes[2];
int fd_read;
int fd_write;
char args[BUFSIZ + 1];

int ret = pipe(file_pipes);

Após a criação é feita a atribuição para as variáveis representando a função de cada índice, onde 0 representa o canal de leitura e 1 o canal de escrita

fd_read = file_pipes[0];
fd_write = file_pipes[1];

Para que o fork seja realizado é necessário verificar se o pipe foi criado com sucesso, caso o retorno seja igual a zero, o fluxo continua

if(ret == 0)

Com o pipe criado, clonamos o processo e no processo filho fechamos o descritor referente ao de leitura, e passamos como argumento o descritor de escrita para o processo button_interface

int fork_res = fork();
if(fork_res == 0)
{
    close(fd_read);
    memset(args, 0, sizeof(args));
    sprintf(args, "%d", fd_write);
    (void)execl("button_process", "button_process", args, (char *)0);            
    exit(EXIT_FAILURE);
}

De volta ao processo pai verificamos se o fork anterior foi executado com sucesso, caso sim é iniciada uma nova cópia, porém dessa vez o descritor de escrita é fechado e passamos o descritor de leitura como argumento para o processo led_interface

else if(fork_res > 0)
{
    fork_res = fork();
    if(fork_res == 0)
    {
        close(fd_write);
        memset(args, 0, sizeof(args));
        sprintf(args, "%d", fd_read);
        (void)execl("led_process", "led_process", args, (char *)0);
        exit(EXIT_FAILURE);
    }
}    

Por fim fechamos os descritores do processo pai

close(file_pipes[0]);
close(file_pipes[1]);  

button_interface.c

Declaramos um buffer para formatar a mensagem de envio, e declaramos uma variável para receber o descritor recebido via argumento

char data[64];
char buffer[BUFSIZ + 1];
int fd;
int state = 0;

Configuramos o botão, passando o descritor como argumento

if(button->Init(object) == false)
        return EXIT_FAILURE;

Limpamos o buffer e copiamos o valor de descritor para o buffer, e recuperamos o descritor do pipe em fd

memset(buffer, 0, sizeof(buffer));
sscanf(argv[1], "%d", &fd);

O loop a seguir aguarda que o botão seja pressionado para que a variável state seja alterada, se estiver em 0 vai para 1 e vice-versa

wait_press(object, button);
state ^= 0x01;

Assim que o botão for pressionado, inicia-se a escrita no pipe como o novo estado, em seguida é chamado a system call write para enviar a mensagem através do pipe

memset(data, 0, sizeof(data));
snprintf(data, sizeof(data), "state = %d\n", state);
write(fd, data, strlen(data));                
usleep(500 * _1ms);

Fechamos o descritor de escrita no fim da aplicação

close(fd);

led_interface.c

O processo de configuração se assemelha ao do processo de botão

char buffer[BUFSIZ + 1];
int fd;   
int state_cur;
int state_old;

Aplica a inicialização do LED de acordo com o descritor, e recupera o valor do descritor de leitura do pipe

if (led->Init(object) == false)
    return EXIT_FAILURE;

sscanf(argv[1], "%d", &fd);

Realiza a leitura do pipe via polling para verificar se houve alteração do estado, se houve atualiza a variável de estado e aplica o novo estado ao LED

memset(buffer, 0, sizeof(buffer));
read(fd, buffer, BUFSIZ);
sscanf(buffer, "state = %d", &state_cur);        

if (state_cur != state_old)
{

    state_old = state_cur;
    led->Set(object, (uint8_t)state_cur);
}
usleep(1);    

Por fim fechamos o descritor de leitura ao fim da aplicação

close(fd);

Para o código fonte completo clique aqui

Compilando, Executando e Matando os processos

Para compilar e testar o projeto é necessário instalar a biblioteca de hardware necessária para resolver as dependências de configuração de GPIO da Raspberry Pi.

Compilando

Para faciliar a execução do exemplo, o exemplo proposto foi criado baseado em uma interface, onde é possível selecionar se usará o hardware da Raspberry Pi 3, ou se a interação com o exemplo vai ser através de input feito por FIFO e o output visualizado através de LOG.

Clonando o projeto

Pra obter uma cópia do projeto execute os comandos a seguir:

$ git clone https://github.com/NakedSolidSnake/Raspberry_IPC_Pipe
$ cd Raspberry_IPC_Pipe
$ mkdir build && cd build

Selecionando o modo

Para selecionar o modo devemos passar para o cmake uma variável de ambiente chamada de ARCH, e pode-se passar os seguintes valores, PC ou RASPBERRY, para o caso de PC o exemplo terá sua interface preenchida com os sources presentes na pasta src/platform/pc, que permite a interação com o exemplo através de FIFO e LOG, caso seja RASPBERRY usará os GPIO’s descritos no artigo.

Modo PC

$ cmake -DARCH=PC ..
$ make

Modo RASPBERRY

$ cmake -DARCH=RASPBERRY ..
$ make

Executando

Para executar a aplicação execute o processo launch_processes para lançar os processos button_process e led_process que foram determinados de acordo com o modo selecionado.

$ cd bin
$ ./launch_processes

Uma vez executado podemos verificar se os processos estão rodando atráves do comando

$ ps -ef | grep _process

O output

pi        4725     1  1 23:53 pts/0    00:00:00 button_process 4
pi        4726     1  0 23:53 pts/0    00:00:00 led_process 3

Aqui é possível notar que o button_process possui um argumento com o valor 4, e o led_process possui também um argumento com o valor 3, esses valores representam os descritores gerados pelo pipe system call, onde o 4 representa o descritor de escrita e o 3 representa o descritor de leitura.

Interagindo com o exemplo

Dependendo do modo de compilação selecionado a interação com o exemplo acontece de forma diferente

MODO PC

Para o modo PC, precisamos abrir um terminal e monitorar os LOG’s

$ sudo tail -f /var/log/syslog | grep LED

Dessa forma o terminal irá apresentar somente os LOG’s referente ao exemplo.

Para simular o botão, o processo em modo PC cria uma FIFO para permitir enviar comandos para a aplicação, dessa forma todas as vezes que for enviado o número 0 irá logar no terminal onde foi configurado para o monitoramento, segue o exemplo

$ echo "0" > /tmp/pipe_file

Output do LOG quando enviado o comando algumas vezez

Apr  3 20:56:19 cssouza-Latitude-5490 LED PIPE[22810]: LED Status: On
Apr  3 20:56:20 cssouza-Latitude-5490 LED PIPE[22810]: LED Status: Off
Apr  3 20:56:21 cssouza-Latitude-5490 LED PIPE[22810]: LED Status: On
Apr  3 20:56:34 cssouza-Latitude-5490 LED PIPE[22810]: LED Status: Off
Apr  3 20:56:50 cssouza-Latitude-5490 LED PIPE[22810]: LED Status: On
Apr  3 20:56:51 cssouza-Latitude-5490 LED PIPE[22810]: LED Status: Off
Apr  3 20:56:51 cssouza-Latitude-5490 LED PIPE[22810]: LED Status: On
Apr  3 20:56:52 cssouza-Latitude-5490 LED PIPE[22810]: LED Status: Off

MODO RASPBERRY

Para o modo RASPBERRY a cada vez que o botão for pressionado irá alternar o estado do LED.

Matando os processos

Para matar os processos criados execute o script kill_process.sh

$ cd bin
$ ./kill_process.sh

Conclusão

Pipe é um IPC muito utilizado no shell, porém como opção de uso de comunicação entre processos que possuem tempo de vida indeterminado não muito viável, por não permitir o fluxo de dados de forma bidirecional, e pela dificuldade de manter os descritores de leitura e escrita.

Referências

IPC(Inter process communication)

fork, exec, e daemon FIFO
Licença Creative Commons Esta obra está licenciada com uma Licença Creative Commons Atribuição-CompartilhaIgual 4.0 Internacional.
Comentários:
Notificações
Notificar
0 Comentários
recentes
antigos mais votados
Inline Feedbacks
View all comments
Home » Software » Pipes

EM DESTAQUE

WEBINARS

VEJA TAMBÉM

JUNTE-SE HOJE À COMUNIDADE EMBARCADOS

Talvez você goste: