Signal – O que é e como usar?

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

Introdução

Signal é uma notificação que avisa um determinado processo que um evento ocorreu. Signal é considerado uma interrupção por software, similar a interrupção via hardware, onde quando há um evento, o fluxo do programa é alterado, normalmente chamando uma função que foi registrada para ser invocada quando esse sinal acontecer. Signal pode ser considerado um IPC porém não transmite dados, e são assíncronos, mas quando um processo o recebe, interrompe o processamento atual para atender o evento, ou seja, assim que um evento é recebido, o processamento é imediato, como boa prática os handlers registrados para os sinais devem possui uma rotina muito pequena para o tratamento desse sinal, para que possa retornar rapidamente para o ponto onde foi interrompido. Existem mais de 31 sinais sendo que alguns deles podem ser gerados através do teclado como o SIGINT, os sinais existentes estão definidos em /usr/include/bits/signum.h para 32bits, /usr/include/x86_64-linux-gnu/bits/signum.h para 64 bits e /usr/include/arm-linux-gnueabihf/bits/signum.h para ARM.

Registrando uma Callback para um Signal

Para realizar um registro de uma callback faz-se o uso da system call signal que recebe dois argumentos o SIG[TIPO] que é o evento, e a callback que será executado quando houver o evento, onde a callback deve respeitar a assinatura do sighandler, que recebe um argumento do tipo int e não retorna nada

#include <signal.h>

typedef void (*sighandler_t)(int);

sighandler_t signal(int signum, sighandler_t handler);

Para uma explicação mais detalhada dos tipos de Signals, pesquise no manual do Linux

man 7 signal

Emitindo um Signal

Com Signal é possível emitir o evento para si mesmo através da system call

#include <signal.h>

int raise(int sig);

ou para um processo externo, nesse caso é necessário conhecer o pid do processo no qual se quer enviar

#include <sys/types.h>
#include <signal.h>

int kill(pid_t pid, int sig);

Aguardando um Signal

Para fazer com que o programa somente processe mediante a um evento, pode-se usar a system call

#include <unistd.h>

int pause(void);

que permite que o programa entre em sleep até que um Signal seja recebido para assim acordar e processar.

Funcionamento da recepção de um Signal

Para exemplificar melhor é apresentado uma imagem animada que demonstra o fluxo de um programa em execução, e como é realizado o tratamento do evento:

Pode-se se notar no programa que, em nenhum momento é chamado o handler, mas mediante o registro prévio do callback, quando o evento é gerado, o callback é invocado, trata o sinal e retoma o fluxo normal após o tratamento do evento.

Implementação

Para demonstrar o uso desse IPC, iremos utilizar um esquema de notifição, onde o processo Notificador (button_process) vai notificar o processo Consumidor(led_process) que está em sleep aguardando a notificação para alterar seu estado, essa aplicação é composta por 3 executáveis:

  • 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 enviar um evento Signal
  • led_interface – é responsável por aguardar um Signal e mudar o estado do 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 um buffer que vai ser formatado para enviar argumentos para o processo button_process

int pidLed;
int pidButton;
char args[BUFSIZ + 1];

Em seguida lançamos o processo led_process

pidLed = fork();
if(pidLed == 0)
{        
    memset(args, 0, sizeof(args));        
    (void)execl("led_process", "led_process", NULL, (char *)0);
    exit(EXIT_FAILURE);
} 

Nesse ponto temos o pid do processo led_process, que será passado para o processo button_process como argumento para que seja possível notificá-lo

else if(pidLed > 0)
{
    pidButton = fork();
    if(pidButton == 0)
    {            
        memset(args, 0, sizeof(args));
        sprintf(args, "%d", pidLed);
        (void)execl("button_process", "button_process", args, (char *)0);
        exit(EXIT_FAILURE);
    }
}

button_interface

Definimos a variável que vai receber o argumento recebido via exec

int pidLed;

Verificamos se o argumento é válido

if(argv[1][0] == '\0')
  return false;

Inicializamos a interface de botão

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

Recuperamos o pid do processo led_process

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

Aguardamos o pressionamento do botão e por fim notificamos o processo led_process através do kill

wait_press(object, button);
kill(pidLed, SIGUSR1);

led_interface

Definimos uma variável para controlar o estado interno do LED

int state = 0

Registramos o sinal que desejamos receber

signal(SIGUSR1, recv_sig);

Inicializamos a interface de LED

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

Aplicamos o estado inicial do LED, alteramos o estado interno e colocamos o processo em modo sleep, que aguarda um evento para aplicar o novo estado

led->Set(object, (uint8_t)state);
state ^= 0x01;
pause();

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:

$ git clone https://github.com/NakedSolidSnake/Raspberry_IPC_Signal
$ cd Raspberry_IPC_Signal
$ 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 através do comando

$ ps -ef | grep _process

O output

pi        2773     1  0 10:25 pts/0    00:00:00 led_process
pi        2774     1  1 10:25 pts/0    00:00:00 button_process 2773

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

Output do LOG quando enviado o comando algumas vezes

Apr  6 06:22:37 cssouza-Latitude-5490 LED SIGNAL[4277]: LED Status: On
Apr  6 06:22:39 cssouza-Latitude-5490 LED SIGNAL[4277]: LED Status: Off
Apr  6 06:22:40 cssouza-Latitude-5490 LED SIGNAL[4277]: LED Status: On
Apr  6 06:22:40 cssouza-Latitude-5490 LED SIGNAL[4277]: LED Status: Off
Apr  6 06:22:41 cssouza-Latitude-5490 LED SIGNAL[4277]: LED Status: On
Apr  6 06:22:42 cssouza-Latitude-5490 LED SIGNAL[4277]: 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

Signal é um IPC bastante versátil, apesar de não trafegar dados, permite de forma rápida e simples a sincronização entre os processos, como no Shared File, que é usado o polling para verificar se o arquivo está em uso, podemos remover o polling e usar a notificação para que o processo leia quando e somente for atualizado, reduzindo assim processamento desnecessário.

Referência

IPC(Inter process communication)

Shared File MMAP
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 » Linux Embarcado » Signal – O que é e como usar?

EM DESTAQUE

WEBINARS

VEJA TAMBÉM

JUNTE-SE HOJE À COMUNIDADE EMBARCADOS

Talvez você goste: