System V – Semaphore

Esse é 16º artigo da série comunicação entre processos. Esse artigo aborda o uso do System V Semaphore.
Semaphore

Em programação Multithread ou Multiprocessos existem pontos onde é necessário compartilhar a mesma informação, normalmente conhecidos como variáveis globais, ou de forma mais charmosa: “variáveis de contexto”. Onde essas variáveis guardam algum tipo de informação ou estado interno, e seu acesso de forma não sincronizada pode acarretar em um comportamento indesejável. Neste artigo será visto um IPC conhecido como Semaphore, que garante que isso tipo de coisa não aconteça.

Semaphore System V

Semaphore System V diferente dos outros IPC’s não é utilizado para transferir dados, mas sim para coordená-los. Para exemplificar tome uma variável global em um contexto onde se tem duas Threads, sendo a Thread A responsável por incrementar o conteúdo, e a Thread B responsável por decrementar esse conteúdo e estão concorrendo o acesso dessa posição de memória. Em um dado instante de tempo o conteúdo esteja com o valor 10, neste ponto a Thread A incrementa o conteúdo em 1 totalizando 11, porém no mesmo instante a Thread B decrementa totalizando 9. Por uma análise sequencial o valor esperado seria o próprio 10, mas ao fim totalizou 9 o que aconteceu aqui foi que as Threads concorreram resultando nesse resultado inesperado. Para garantir que o acesso seja feito de forma sincronizada é necessário estabelecer uma regra de acesso onde quando uma Thread estiver usando a outra precisa esperar para assim acessar. Os Semaphores podem ter dois tipos: contador e exclusão mútua.

Systemcalls

Para usar Semaphores é necessário algumas funções sendo a primeira semget que é responsável por criar o identificador do Semaphore.

semop é usado para alterar os valores do Semaphore, essa função possui uma alternativa para ser usado com o timeout.

semctl controla diretamente as informações do semaphore

ipcs

A ferramenta ipcs é um utilitário para poder verificar o estado dos IPC’s sendo eles: Queues, Semaphores e Shared Memory, o seu funcionamento será demonstrado mais a seguir. Para mais informações execute:

Implementação

Para facilitar a implementação a API do Semaphore foi abstraída para facilitar o uso.

semaphore.h

Nesse header é criado dois enums um para identificar o estado e o outro seu tipo. Foi criado uma estrutura que contém todos os argumentos necessários para manipular o semaphore

Aqui é criada uma abstração que permite uma leitura mais simplificada de como manipular o semaphore.

semaphore.c

Para o controle do semaphore é necessário uma estrutura equivalente a que foi usada na queue que é usada para inicializar e destruir o semaphore

Aqui p semaphore é inicializado utilizando uma chave, a quantidade, as permissões e flag de criação, logo em seguida é verificado qual o tipo e caso for o principal, seu valor recebe 1 isso permite que seja bloqueado em caso de uso.

Para realizar o bloqueio utilizando a estrutura sembuf com os valores {0, -1, SEM_UNDO} utilizando a função semop após a operação setando seu estado para locked.

Para realizar a liberação utilizando a estrutura sembuf com os valores {0, 1, SEM_UNDO} utilizando a função semop após a operação setando seu estado para unlocked.

Por fim, para remover o semaphore é utilizada a função semctl com a flag IPC_RMID com o identificador do sempahore.

Para demonstrar o uso desse IPC, será utilizado o modelo Produtor/Consumidor, onde o processo Produtor(button_process) vai escrever seu estado em uma mensagem, e inserir na queue, e o Consumidor(led_process) vai ler a mensagem da queue e aplicar ao seu estado interno. A aplicação é composta por três executáveis sendo eles:

  • launch_processes – é responsável por lançar os processos button_process e led_process através da combinação fork e exec
  • button_interface – é responsável por ler o GPIO em modo de leitura da Raspberry Pi e escrever o estado em uma mensagem e inserir na queue
  • led_interface – é responsável por ler a mensagem da queue e aplicar em um GPIO configurado como saída

launch_processes.c

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.

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.

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

button_interface.h

Para realizar o uso da interface de botão é necessário preencher os callbacks que serão utilizados pela implementação da interface, sendo a inicialização e a leitura do botão.

A assinatura do uso da interface corresponde ao contexto do botão, que depende do modo selecionado, o semaphore, e a interface do botão devidamente preenchida.

button_interface.c

A implementação da interface baseia-se em inicializar o botão, inicializar o semaphore, e no loop realiza o lock do semaphore e aguarda o pressionamento do botão que libera o semaphore para outro processo(nesse caso o processo de LED) utilizar.

led_interface.h

Para realizar o uso da interface de LED é necessário preencher os callbacks que serão utilizados pela implementação da interface, sendo a inicialização e a função que altera o estado do LED.

A assinatura do uso da interface corresponde ao contexto do LED, que depende do modo selecionado, o semaphore, e a interface do LED devidamente preenchida.

led_interface.c

A implementação da interface baseia-se em inicializar o LED, inicializar o semaphore, e no loop realiza o lock do semaphore e altera o seu estado e libera o semaphore para outro processo(nesse caso o processo de Button) utilizar.

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 facilitar 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:

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

Modo RASPBERRY

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.

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

O output

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

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

Output dos LOG’s quando enviado o comando algumas vezez

MODO RASPBERRY

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

ipcs funcionamento

Para inspecionar os semaphores presentes é necessário passar o argumento -s que representa queue, o comando fica dessa forma:

O Output gerado na máquina onde o exemplo foi executado

Matando os processos

Para matar os processos criados execute o script kill_process.sh

Conclusão

O semaphore é um recurso muito útil que serve para sincronizar o acesso de regiões de memória compartilhada, conhecida também como sessão crítica, porém as vezes o seu uso pode acarretar problemas de deadlocks, o que torna difícil de depurar e encontrar o problema em um programa multithreaded ou multiprocesso. Normalmente é utilizado em conjunto com a Shared Memory outro IPC que será visto no próximo artigo. Mas para evitar esse tipo de preocupação uma solução mais adequada seria usar sockets ou queues para enviar as mensagens para os seus respectivos interessados.

Referência

Outros artigos da série

<< Queue System VShared Memory System V >>
Notificações
Notificar
guest
0 Comentários
Inline Feedbacks
View all comments

WEBINAR

Visão Computacional para a redução de erros em processos manuais

DATA: 23/09 ÀS 17:00 H