POSIX Shared Memory

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

Introdução

POSIX Shared Memory é uma padronização desse recurso para que fosse altamente portável entre os sistemas. Não difere tanto da Shared Memory System V, porém utiliza uma forma de implementação totalmente nova(não abordado nesse artigo). Diferente dos outros mecanismos(Semaphore e Queue) necessita de outro recurso para que a memória alocada seja compartilhada entre os outros processos.

Systemcalls

Para utilizar a API referente a Shared Memory é necessário realizar a linkagem com a biblioteca rt.

Esta função cria ou obtém uma Shared Memory

#include <sys/mman.h>
#include <sys/stat.h>        
#include <fcntl.h>           

int shm_open(const char *name, int oflag, mode_t mode);

Esta função remove uma Shared Memory criada pela shm_open

#include <sys/mman.h>

int shm_unlink(const char *name);

Implementação

Para facilitar o uso desse mecanismo, o uso da API referente a Shared Memory POSIX é feita através de uma abstração na forma de uma biblioteca.

posix_shm.h

Para o seu uso é criada uma enumeração que determina o modo de abertura dessa shared memory e uma estrutura que vai realizar a configuração dessa shared memory, como seu nome, tamanho e o modo de operação.

typedef enum 
{
    write_mode,
    read_mode
} Mode;

typedef struct 
{
    int fd;
    const char *name;
    char *buffer;
    int buffer_size;
    Mode mode;
} POSIX_SHM;

As funções pertinentes para criar e remover a shared memory

bool POSIX_SHM_Init(POSIX_SHM *posix_shm);
bool POSIX_SHM_Cleanup(POSIX_SHM *posix_shm);

posix_shm.c

Aqui em POSIX_SHM_Init criamos a shared memory baseada no nome e seu handle é guardado. A shared memory não possui um mecanismo próprio como a Shared Memory System V, para isso ser possível é necessário o uso do mmap para que a shared memory seja visível por outros processos.

bool POSIX_SHM_Init(POSIX_SHM *posix_shm)
{
    int mode;
    posix_shm->fd = shm_open(posix_shm->name, O_RDWR | O_CREAT, S_IRWXU | S_IRWXG | S_IRWXO);
    if(posix_shm->fd < 0)
        return false;

    if(ftruncate(posix_shm->fd , posix_shm->buffer_size) < 0)
        return false;
    

    if(posix_shm->mode == write_mode)
        mode = PROT_WRITE;
    else if(posix_shm->mode == read_mode)
        mode = PROT_READ;

    posix_shm->buffer = mmap(NULL, posix_shm->buffer_size, mode, MAP_SHARED, posix_shm->fd, 0);
    if(posix_shm->buffer < 0)
        return false;

    return true;
}

Aqui em POSIX_SHM_Cleanup é realizada a remoção da shared memory baseado no seu handle:

bool POSIX_SHM_Cleanup(POSIX_SHM *posix_shm)
{
    if(posix_shm->buffer > 0)
        munmap(posix_shm->buffer, posix_shm->buffer_size);
    if(posix_shm->fd > 0)
        shm_unlink(posix_shm->name);

    return true;
}

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

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.

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.h

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 da Shared Memory, e a interface do botão devidamente preenchida.

bool Button_Run(void *object, POSIX_SHM *posix_shm, Button_Interface *button);

button_interface.c

A implementação da interface baseia-se em inicializar o botão, inicializar a Shared Memory, e no loop alterar o conteúdo da shared memory mediante o pressionamento do botão.

bool Button_Run(void *object, POSIX_SHM *posix_shm, Button_Interface *button)
{
    int state = 0;
    if(button->Init(object) == false)
        return false;

    if(POSIX_SHM_Init(posix_shm) == false)
        return false;

    while(true)
    {
        wait_press(object, button);

        state ^= 0x01;
        snprintf(posix_shm->buffer, posix_shm->buffer_size, "state = %d", state);
    }

    POSIX_SHM_Cleanup(posix_shm);
   
    return false;
}

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.

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 da Shared Memory, e a interface do LED devidamente preenchida.

bool LED_Run(void *object, POSIX_SHM *posix_shm, LED_Interface *led);

led_interface.c

A implementação da interface baseia-se em inicializar o LED, inicializar a Shared Memory, e no loop verifica se houve alteração no conteúdo da shared memory para poder alterar o seu estado interno.

bool LED_Run(void *object, POSIX_SHM *posix_shm, LED_Interface *led)
{
	int state_current = 0;
	int state_old = -1;

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

	if(POSIX_SHM_Init(posix_shm) == false)
		return false;

	while(true)
	{
		sscanf(posix_shm->buffer, "state = %d", &state_current);
		if(state_current != state_old)
		{
			led->Set(object, state_current);
			state_old = state_current;
		}

		usleep(_1ms);
	}

	POSIX_SHM_Cleanup(posix_shm);
	return false;	
}

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_SharedMemory_POSIX
$ cd Raspberry_IPC_SharedMemory_POSIX
$ 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_memory_posix_fifo

Output do LOG quando enviado o comando algumas vezez

Apr 15 22:42:08 cssouza-Latitude-5490 LED SHARED MEMORY POSIX[1054]: LED Status: On
Apr 15 22:42:09 cssouza-Latitude-5490 LED SHARED MEMORY POSIX[1054]: LED Status: Off
Apr 15 22:42:09 cssouza-Latitude-5490 LED SHARED MEMORY POSIX[1054]: LED Status: On
Apr 15 22:42:10 cssouza-Latitude-5490 LED SHARED MEMORY POSIX[1054]: LED Status: Off
Apr 15 22:42:10 cssouza-Latitude-5490 LED SHARED MEMORY POSIX[1054]: LED Status: On
Apr 15 22:42:10 cssouza-Latitude-5490 LED SHARED MEMORY POSIX[1054]: 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

POSIX Shared Memory permite que dois processos não relacionados se comuniquem compartilhando uma região de memória, de forma similar a Shared Memory System V, porém realiza esse procedimento através de mmap que não é relacionada a sua API igual aos outros mecanismos. Normalmente é usada em conjunto com o POSIX Semaphore.

Referência

IPC(Inter process communication)

POSIX Semaphore D-Bus
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 » POSIX Shared Memory

EM DESTAQUE

WEBINARS

VEJA TAMBÉM

JUNTE-SE HOJE À COMUNIDADE EMBARCADOS

Talvez você goste: