Shared File

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

Introdução

Shared File é o mecanismo IPC mais básico, que consiste simplesmente em manipular um arquivo comum com as operações básicas de open, write, read e close, com essas operações é possível inserir informações no arquivo e bem como realizar a leitura delas. Arquivos normalmente são usados para guardar diversos tipos de informação como configurações, logs, anotações entre outros, a figura baixo ajuda a ilustrar a comunicação entre dois processos por meio de uma arquivo.

Na figura é possível observar a comunicação entre dois processos distintos, sendo um o Produtor(Button), e o outro o Consumidor(LED).
Para esse cenário o Produtor insere no arquivo a informação que o Consumidor irá consumir, porém para que o acesso ao arquivo ocorra de forma sincronizada, faz-se necessário o uso da estrutura struct flock que funciona como uma espécie de chave para acessar o arquivo, que por meio da função fcntl, é possível verificar se o arquivo está com o acesso liberado, para poder manipulá-lo. A seguir podemos ver a systemcall fcntl:

#include <unistd.h>
#include <fcntl.h>

int fcntl(int fd, int cmd, ... /* arg */ );

para mais informações sobre essa estrutura execute no terminal o comando:

$ man 2 fcntl

Implementação

Para demonstrar o uso desse IPC, iremos utilizar o modelo Produtor/Consumidor, onde o processo Produtor(button_process) vai escrever seu estado interno no arquivo, e o Consumidor(led_process) vai ler o estado interno e vai aplicar o estado para si. Aplicação é composta por três executáveis sendo eles:

  • launch_processes – é responsável por lançar os processos button_process e led_process atráves da combinação fork e exec
  • button_interface – é reponsável por ler o GPIO em modo de leitura da Raspberry Pi e escrever o estado interno no arquivo
  • led_interface – é reponsável por ler do arquivo o estado interno do botão e aplicar em um GPIO configurado como saída

launch_processes

No main criamos duas variáveis para armazenar o PID do button_process e do led_process, e mais duas variáveis para armazenar o resultado caso o exec venha a falhar.

int pid_button, pid_led;
int button_status, led_status;

Em seguida criamos um processo clone, se processo clone for igual a 0, criamos um array de strings com o nome do programa que será usado pelo exec, em caso o exec retorne, o estado do retorno é capturado e será impresso no stdout e aborta a aplicação. Se o exec for executado com sucesso o programa button_process será carregado.

pid_button = fork();

if(pid_button == 0)
{
    //start button process
    char *args[] = {"./button_process", NULL};
    button_status = execvp(args[0], args);
    printf("Error to start button process, status = %d\n", button_status);
    abort();
}   

O mesmo procedimento é repetido novamente, porém com a intenção de carregar o led_process.

pid_led = fork();

if(pid_led == 0)
{
    //Start led process
    char *args[] = {"./led_process", NULL};
    led_status = execvp(args[0], args);
    printf("Error to start led process, status = %d\n", led_status);
    abort();
}

button_interface

Definimos o nome do arquivo que vai armazenar o estado interno, e uma função de apoio para realizar a criação do arquivo que será usado para compartilhar as informações

#define FILENAME "/tmp/data.dat"
static void create_file(void);

Aqui é criado uma variável para a formatação do dado que será armazenado no arquivo.

char buffer[2];

Neste ponto é criada uma variável do tipo flock que vai controlar o acesso ao arquivo.

struct flock lock;

Aqui é criado o descritor que é o responsável por armazenar o id do arquivo, e já aproveitamos para inicializar a variável state que contém o estado que o LED vai assumir.

int fd;
int state = 0;

Neste trecho inicializamos a interface do botão com o descritor preenchido conforme selecionado no momento da compilação.

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

Aqui reside o core da aplicação, neste fragmento, o programa fica em polling aguardando que o botão seja pressionado, caso não, aguarda 1 ms para não consumir processamento. Se for pressionado realiza a troca de estado e interrompe o laço while.

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

Aqui é feita a abertura do arquivo

if ((fd = open(FILENAME, O_RDWR, 0666)) < 0)
    continue;

Setamos as configurações de proteção, nesse caso corresponde em travar para escrita, sempre partindo do início do arquivo, e recebe o PID do processo. Aplicamos a configuração, caso falhe é aguardado 1ms para uma nova tentativa.

lock.l_type = F_WRLCK;
lock.l_whence = SEEK_SET;
lock.l_start = 0;
lock.l_len = 0;
lock.l_pid = getpid();
while (fcntl(fd, F_SETLK, &lock) < 0)
    usleep(_1ms);

Com a trava obtida, iniciamos a formatação do dado para ser escrito no arquivo, e assim a sua escrita no arquivo.

snprintf(buffer, sizeof(buffer), "%d", state);
write(fd, buffer, strlen(buffer));

Após a escrita configuramos para o arquivo para ser destravado para ser utilizado por outro processo.

lock.l_type = F_UNLCK;
if (fcntl(fd, F_SETLK, &lock) < 0)
    return EXIT_FAILURE;

E por fim fechamos o arquivo e aguardamos 100ms e aguardamos a próxima interação com o botão.

close(fd);
usleep(100 * _1ms);

led_interface

Definimos o nome do arquivo que vai recuperado o estado gerado pelo botão.

#define FILENAME "/tmp/data.dat"

Aqui é criado uma variável para a recuperação do dado que foi armazenado no arquivo.

char buffer[2];

Neste ponto é criada uma variável do tipo flock que vai controlar o acesso ao arquivo.

struct flock lock;

Aqui é criado o descritor que é o responsável por armazenar o id do arquivo, e já aproveitamos para inicializar a variável state_old que contém o estado que o LED vai assumir, e por último criamos uma variável state_curr, que vai receber o valor contido no arquivo.

int fd = -1;
int state_old = 0;
int state_curr;

Neste trecho inicializamos a interface do LED com o descritor preenchido conforme selecionado no momento da compilação.

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

Aqui realizamos a abertura do arquivo, em modo de leitura, caso não seja possível obter o descritor, aguarda 1s e realiza uma nova tentativa.

if ((fd = open(FILENAME, O_RDONLY)) < 0)
{
    usleep(_1ms * 1000);
    continue;
}

Aqui preenchemos a estrutura com os parâmetros de trava para escrita, onde o arquivo é travado para escrita, sempre partindo do início do arquivo, e passamos o PID do processo.

lock.l_type = F_WRLCK;
lock.l_whence = SEEK_SET;
lock.l_start = 0;
lock.l_len = 0;
lock.l_pid = getpid();

Aqui obtemos as configuração atuais do arquivo, para podermos verificar quais são suas configurações

while (fcntl(fd, F_GETLK, &lock) < 0)
    usleep(_1ms);

Caso esteja travado, fechamos o descritor e retornamos para o início do processo

if (lock.l_type != F_UNLCK){
    close(fd);
    continue;
}

Se estiver destravado setamos a trava para modo de leitura.

lock.l_type = F_RDLCK;
while (fcntl(fd, F_SETLK, &lock) < 0)
    usleep(_1ms);

Lemos o conteúdo do arquivo.

while (read(fd, &buffer, sizeof(buffer)) > 0);            

Convertemos o valor lido e verificamos se o valor atual é diferente do valor anterior, caso for diferente aplica o novo estado, se não, não aplica a modificação.

state_curr = atoi(buffer);
if(state_curr != state_old)
{
    LED_set(&led, (eState_t)state_curr);
    state_old = state_curr;
}

Após a leitura configuramos para o arquivo para ser destravado para ser utilizado por outro processo.

lock.l_type = F_UNLCK;
if (fcntl(fd, F_SETLK, &lock) < 0)
    return EXIT_FAILURE;

E por fim fechamos o arquivo e aguardamos 100ms e aguardamos a próxima leitura.

close(fd);
usleep(100* _1ms);

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_SharedFile
$ cd Raspberry_IPC_SharedFile
$ 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

cssouza  16871  3449  0 07:15 pts/4    00:00:00 ./button_process
cssouza  16872  3449  0 07:15 pts/4    00:00:00 ./led_process

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/shared_file_fifo

Output do LOG quando enviado o comando algumas vezez

Apr 17 07:15:22 cssouza-Latitude-5490 LED SHARED FILE[16872]: LED Status: On
Apr 17 07:15:23 cssouza-Latitude-5490 LED SHARED FILE[16872]: LED Status: Off
Apr 17 07:15:24 cssouza-Latitude-5490 LED SHARED FILE[16872]: LED Status: On
Apr 17 07:15:24 cssouza-Latitude-5490 LED SHARED FILE[16872]: LED Status: Off
Apr 17 07:15:25 cssouza-Latitude-5490 LED SHARED FILE[16872]: LED Status: On
Apr 17 07:15:25 cssouza-Latitude-5490 LED SHARED FILE[16872]: LED Status: Off
Apr 17 07:15:26 cssouza-Latitude-5490 LED SHARED FILE[16872]: LED Status: On
Apr 17 07:15:26 cssouza-Latitude-5490 LED SHARED FILE[16872]: 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

Apesar de ser uma implementação simples, esse exemplo possui uma alta incidência de concorrência no acesso ao arquivo, para funcionar de forma razoável foi necessário inserir alguns atrasos para evitar(reduzir) a concorrência. Não é recomendado, pois precisa de outros mecanismos para sincronizar o seu acesso, como signal ou semaphore(que veremos mais adiante), mais a idéia é demonstrar o seu uso na forma pura sem interferência de outro IPC.

Referência

IPC(Inter process communication)

FIFO Signal – O que é e como usar?
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
2 Comentários
recentes
antigos mais votados
Inline Feedbacks
View all comments
MARCOS DE LIMA CARLOS
MARCOS DE LIMA CARLOS
11/05/2021 17:50

Show o artigo! Bem completo.

Home » Linux Embarcado » Shared File

EM DESTAQUE

WEBINARS

VEJA TAMBÉM

JUNTE-SE HOJE À COMUNIDADE EMBARCADOS

Talvez você goste: