- Introdução
- Systemcalls
- Implementação
- posix_semaphore.h
- posix_semaphore.c
- launch_processes.c
- button_interface.h
- button_interface.c
- led_interface.h
- led_interface.c
- Compilando, Executando e Matando os processos
- Compilando
- Clonando o projeto
- Selecionando o modo
- Modo PC
- Modo RASPBERRY
- Executando
- Interagindo com o exemplo
- MODO PC
- MODO RASPBERRY
- Matando os processos
- Conclusão
- Referência
POSIX Semaphore é uma padronização desse recurso para que fosse altamente portável entre os sistemas. Não difere tanto da Semaphore System V, é normalmente usado para sincronização de Threads.
Para utilizar a API referente ao Semaphore é necessário realizar a linkagem com a biblioteca pthread
Esta função cria ou abre um semaphore existente
#include <fcntl.h> /* For O_* constants */
#include <sys/stat.h> /* For mode constants */
#include <semaphore.h>
sem_t *sem_open(const char *name, int oflag);
sem_t *sem_open(const char *name, int oflag, mode_t mode, unsigned int value);
Esta função incrementa o semaphore, permitindo assim que outro processo ou thread que estejam em estado de wait possa realizar o lock
#include <semaphore.h>
int sem_post(sem_t *sem);
Essa função decrementa o semaphore apontado pelo descritor.
#include <semaphore.h>
int sem_wait(sem_t *sem);
int sem_trywait(sem_t *sem);
int sem_timedwait(sem_t *sem, const struct timespec *abs_timeout);
Fecha o semaphore, permitindo a liberação do recurso alocado para o seu uso
#include <semaphore.h>
int sem_close(sem_t *sem);
Remove o semaphore imediatamente, o semaphore é destruído quando todos os processos que o utiliza fecha.
#include <semaphore.h>
int sem_unlink(const char *name);
Para facilitar o uso desse mecanismo, o uso da API referente a Semaphore POSIX é feita através de uma abstração na forma de uma biblioteca.
Para o seu uso é criado um estrutura que guarda o seu contexto, e o nome do semaphore
typedef struct
{
void *handle;
const char *name;
} POSIX_Semaphore;
As ações pertinentes ao semaphore permitem criar, pegar, liberar, esperar e liberar o recurso.
bool POSIX_Semaphore_Create(POSIX_Semaphore *semaphore);
bool POSIX_Semaphore_Get(POSIX_Semaphore *semaphore);
bool POSIX_Semaphore_Post(POSIX_Semaphore *semaphore);
bool POSIX_Semaphore_Wait(POSIX_Semaphore *semaphore);
bool POSIX_Semaphore_Cleanup(POSIX_Semaphore *semaphore);
Aqui em POSIX_Semaphore_Create criamos o semaphore baseado no nome e seu contexto e guardado em handle.
bool POSIX_Semaphore_Create(POSIX_Semaphore *semaphore)
{
bool status = false;
do
{
if(!semaphore)
break;
semaphore->handle = sem_open(semaphore->name, O_RDWR | O_CREAT, S_IRWXU | S_IRWXG | S_IRWXO, 0);
if(!semaphore->handle)
break;
status = true;
} while(false);
return status;
}
Na POSIX_Semaphore_Get é verificado se o semaphore está disponível para uso.
bool POSIX_Semaphore_Get(POSIX_Semaphore *semaphore)
{
bool status = false;
do
{
if(!semaphore)
break;
if(sem_post(semaphore->handle) == 0)
status = true;
semaphore->handle = sem_open(semaphore->name, O_RDWR);
if(!semaphore->handle)
break;
status = true;
} while(false);
return status;
}
Incrementa o semaphore para o habilitar seu uso
bool POSIX_Semaphore_Post(POSIX_Semaphore *semaphore)
{
bool status = false;
if(semaphore && semaphore->handle)
{
if(sem_post(semaphore->handle) == 0)
status = true;
}
return status;
}
Aguarda que o semaphore seja liberado para ser usado
bool POSIX_Semaphore_Wait(POSIX_Semaphore *semaphore)
{
bool status = false;
if(semaphore && semaphore->handle)
{
if(sem_wait(semaphore->handle) == 0)
status = true;
}
return status;
}
Libera os recursos alocados para o semaphore
bool POSIX_Semaphore_Cleanup(POSIX_Semaphore *semaphore)
{
bool status = false;
if(semaphore && semaphore->handle)
{
sem_close(semaphore->handle);
sem_unlink(semaphore->name);
status = true;
}
return status;
}
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 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 interno no arquivo
- led_interface - é responsável por ler do arquivo o estado interno do botão e aplicar em um GPIO configurado como saída
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();
}
Para usar a interface do botão precisa implementar essas duas callbacks para permitir o seu uso.
typedef struct
{
bool (*Init)(void *object);
bool (*Read)(void *object);
} Button_Interface;
A assinatura do uso da interface corresponde ao contexto do botão, que depende do modo selecionado, o contexo do Semaphore, e a interface do botão devidamente preenchida.
bool Button_Run(void *object, POSIX_Semaphore *semaphore, Button_Interface *button);
A implementação da interface baseia-se em inicializar o botão, inicializar o Semaphore, e no loop incrementa o semaphore mediante o pressionamento do botão.
bool Button_Run(void *object, POSIX_Semaphore *semaphore, Button_Interface *button)
{
if(button->Init(object) == false)
return false;
if(POSIX_Semaphore_Create(semaphore) == false)
return false;
while(true)
{
wait_press(object, button);
POSIX_Semaphore_Post(semaphore);
}
POSIX_Semaphore_Cleanup(semaphore);
return false;
}
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.
typedef struct
{
bool (*Init)(void *object);
bool (*Set)(void *object, uint8_t state);
} LED_Interface;
A assinatura do uso da interface corresponde ao contexto do LED, que depende do modo selecionado, o contexo do Semaphore, e a interface do LED devidamente preenchida.
bool LED_Run(void *object, POSIX_Semaphore *semaphore, LED_Interface *led);
A implementação da interface baseia-se em inicializar o LED, inicializar o Semaphore, e no loop verifica se há semaphore disponível para poder alterar o seu estado.
bool LED_Run(void *object, POSIX_Semaphore *semaphore, LED_Interface *led)
{
int status = 0;
if(led->Init(object) == false)
return false;
if(POSIX_Semaphore_Create(semaphore) == false)
return false;
while(true)
{
if(POSIX_Semaphore_Wait(semaphore) == true)
{
status ^= 0x01;
led->Set(object, status);
}
}
return false;
}
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.
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.
Pra obter uma cópia do projeto execute os comandos a seguir:
$ git clone https://github.com/NakedSolidSnake/Raspberry_IPC_Semaphore_POSIX
$ cd Raspberry_IPC_Semaphore_POSIX
$ mkdir build && cd build
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.
$ cmake -DARCH=PC ..
$ make
$ cmake -DARCH=RASPBERRY ..
$ make
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
Dependendo do modo de compilação selecionado a interação com o exemplo acontece de forma diferente.
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/sema_posix_fifo
Output do LOG quando enviado o comando algumas vezez
Apr 16 16:51:26 cssouza-Latitude-5490 LED SEMAPHORE POSIX[29345]: LED Status: Off
Apr 16 16:51:26 cssouza-Latitude-5490 LED SEMAPHORE POSIX[29345]: LED Status: On
Apr 16 16:51:26 cssouza-Latitude-5490 LED SEMAPHORE POSIX[29345]: LED Status: Off
Apr 16 16:51:49 cssouza-Latitude-5490 LED SEMAPHORE POSIX[29345]: LED Status: On
Apr 16 16:51:51 cssouza-Latitude-5490 LED SEMAPHORE POSIX[29345]: LED Status: Off
Apr 16 16:51:51 cssouza-Latitude-5490 LED SEMAPHORE POSIX[29345]: LED Status: On
Para o modo RASPBERRY a cada vez que o botão for pressionado irá alternar o estado do LED.
Para matar os processos criados execute o script kill_process.sh
$ cd bin
$ ./kill_process.sh
POSIX Semaphore é um modo moderno de realizar sincronização entre os acessos às partes críticas do sistema, diferente do Semaphore System V é largamente utilizado para a sincronização de Threads. De qualquer forma é uma alternativa ao Semaphore System V.