Memória Partilhada

Pedaço de memória virtual que dois ou mais processos podem incluir no seu espaço de endereçamento, permitindo a comunicação entre eles. È o mecanismo mais rápido de comunicação entre processos (não existem trocas de dados entre os processos). É necessário garantir a sincronização no acesso à região de memória partilhada (para isso podem-se utilizar semáforos).

 

Modo de Armazenamento

As várias regiões de memória partilhada são armazenadas pelo kernel num local especial de memória - Region Table. O kernel mantém um array (Shared Memory Table) em que cada entrada possui informação referente ao nome, permissões e tamanho da região correspondente, assim como o apontador para a Region Table.

Uma entrada no array shared memory table possui:

{

struct ipc_perm shm_perm;

struct anon_map *shm_amp; ; /* apontador para o kernel */

int shm_segsz; /* Tamanho do segmento (em bytes) */

ushort shm_lpid; /* pid da última operação */

ushort shm_cpid; /* pid do criador */

ushort shm_nattach; /* nº de processos que fizeram shmat */

time_t shm_atime; /* hora do último shmat /*

time_t shm_dtime; /* hora do último shmdt */

time_t shm_ctime; /* hora da última alteração de atributod da MP através de shmctl +

IPC_SET */

}

 

System Calls

int shmget(int chave, int tamanho, int flags) - permite criar ou utilizar uma região de memória partilhada, de acordo com as flags flags. O identificador vai ser utilizado nas outras operações de memória partilhada.

Parâmetro chave:

0 (IPC_PRIVATE)

 

>0

Parâmetro flags:

Flag IPC_CREAT activa e IPC_EXCL activa

Flag IPC_CREAT e IPC_EXCL não activas

Flag IPC_CREAT activa e IPC_EXCL não activa

 

Nota: Ao fazer shmget de uma região de memória partilhada, devem ser respeitadas as permissões de acesso, isto é, as flags de permissão presentes no parâmetro flags (escritas na forma 0xxx), devem ser as mesmas da região de memória partilhada (aquando da sua criação).

 

void *shmat(int shmid, void *shmaddr, int flags) - permite ligar a região de memória partilhada associada ao identificador shmid ao espaço virtual de endereçamento do processo invocador.

Parâmetro shmaddr:

0 - o endereço é escolhido pelo sistema operativo;

>0 - a correspondência é feita a partir do endereço especificado;

Parâmetro flags

 

 

Nota 1: Ao executar um attach com sucesso, shm_nattach é incrementado 1 unidade; shm_lpid é associado ao pid do processo invocador e shm_atime é actualizado.

Nota 2: Existe um limite (estabelecido pelo sistema operativo) de segmentos partilhados por processo (o valor típico é 6)

Nota 3: No caso da system call originar um erro, o valor retornado é -1, sendo associado à variável global errono o erro correspondente (caso contrário, retorna o endereço virtual inicial do espaço de endereçamento em que foi feito o shmat.

 

int shmdt(void *shmaddr) - permite desligar uma região de memória partilhada do espaço de endereçamento virtual do processo invocador. O parâmetro shmaddr é o endereço virtual retornado por shmat.

Nota: ao executar um dattach com sucesso, shm_nattach é decrementado 1 unidade, shm_lpid é associado ao pid do processo invocador e shm_dtime é actualizado.

 

int shmctl(int shmid, int cmd, struct shmid_ds *buf) - permite alterar/consultar os atributos da região de memória partilhada identificada por shmid, ou destruir a região de memória partilhada. O comando cmd define se a operação vai ler/escrever atributos ou se vai destruir a região.

Parâmetro cmd:

IPC_STAT - retorna para o buffer apontado por buf a estrutura visível ao utilizador da região de memória partilhada - shmid_ds (o utilizador necessita de ter permissão de leitura);

IPC_SET - são alterados os seguintes campos da região, para os valores encontrados em buf:

shm_perm.uid, shm_perm.gid, shm_perm.mode e shm_ctime.

IPC_RMID - a região de memória partilhada é destruída, isto é, é removida da Region Table, assim como também é removido o identificador da shared memory table.

 

Exemplo

Escritor.c - este programa escreve um número na região de memória partilhada

#include <unistd.h>

#include <stdlib.h>

#include <stdio.h>

#include <string.h>

#include <sys/types.h>

#include <sys/ipc.h>

#include <sys/shm.h>

 

 

main()

{

void *memoria_partilhada = (void *) 0; /* apontador para o segmento

de memoria partilhada */

int *aponta;

int shmid;

int valor;

/* cria ou utiliza o segmento de memória partilhada - neste caso

cria uma região com o tamanho de um inteiro */

shmid = shmget((key_t)1245, sizeof(int), 0666|IPC_CREAT);

if (shmid == -1) {

printf("Erro !!!\n");

exit(-1);

}

/* "Ligação" 'a região de memória partilhada */

memoria_partilhada = shmat(shmid, (void *)0,0);

if (memoria_partilhada == (void *) -1) {

printf("Erro ao fazer shmat\n");

exit(-1);

}

printf("A memoria esta' 'ligada' em %X\n", (int)memoria_partilhada);

aponta = (int *) memoria_partilhada;

printf("Valor: ");

scanf("%d",&valor);

/* altera o valor da região de memória partilhada */

*aponta=valor;

/* "Desliga-se" da região de memória partilhada */

if (shmdt(memoria_partilhada) == -1) {

printf("Erro ao fazer shmdt\n");

exit(-1);

}

}

 

 

Leitor.c - este programa lê um número da região de memória partilhada

#include <unistd.h>

#include <stdlib.h>

#include <stdio.h>

#include <string.h>

#include <sys/types.h>

#include <sys/ipc.h>

#include <sys/shm.h>

 

main()

{

void *memoria_partilhada = (void *) 0; /* apontador para o segmento

de memoria partilhada */

int *aponta;

int shmid;

/* cria ou utiliza o segmento de memória partilhada */

shmid = shmget((key_t)1245, sizeof(int), 0666|IPC_CREAT);

if (shmid == -1) {

printf("Erro !!!\n");

exit(-1);

}

memoria_partilhada = shmat(shmid, (void *)0,0);

if (memoria_partilhada == (void *) -1) {

printf("Erro ao fazer shmat\n");

exit(-1);

}

printf("A memoria esta' 'ligada' em %X\n", (int)memoria_partilhada);

aponta = (int *) memoria_partilhada;

printf("O valor que esta' na regiao de memoria partilhada e' %d\n", *aponta);

/* "Desliga-se" da região de memória partilhada */

if (shmdt(memoria_partilhada) == -1) {

printf("Erro ao fazer shmdt\n");

exit(-1);

}

/* para remover a região de memória partilhada, tire o de comentário as seguintes linhas */

/* if (shmctl(shmid, IPC_RMID, 0) == -1) {

printf("Erro ao remover a regiao de memoria partilhada\n");

exit(-1);

} */

 

}

 

Exercícios

 

1 - Faça os seguintes programas:

 

2 - Faça um programa que calcule o máximo de um vector de inteiros com 1000 posições. Deverão ser criados 10 filhos, sendo cada um destes responsável por calcular o máximo da parte respectiva do vector. O processo pai imprime o valor máximo de todo o vector. Utilize memória partilhada como mecanismo de comunicação entre processos.

 

3 - Considere um balcão de venda de bilhetes. Pretende-se simular o atendimento a clientes.

O comportamento de cada cliente como do atendedor vai ser modelizado por processos. Apenas o atendedor tem acesso aos bilhetes, isto é, só ele sabe o número do próximo bilhete. Os clientes devem ser atendidos pela ordem de chegada, ficando bloqueados até obterem o seu bilhete, imprimindo de seguida o número respectivo.

Escreva o código do atendedor e de um cliente. Na resolução do exercício utilize semáforos e memória partilhada.

 

4- Uma empresa do sector financeiro pretende desenvolver uma aplicação que permita efectuar consultas e levantamentos de contas bancárias. Para isso necessita de desenvolver os seguintes programas:

 

levanta - este programa permite levantar uma determinada quantia (se existir saldo disponível!);

 

consulta - este programa permite consultar o saldo de uma conta;

 

servidor – este programa atende os clientes de uma forma ordenada. Apenas ele tem acesso às contas dos clientes.

 

Utilize memória partilhada e semáforos para resolver o problema.

Nota: a aplicação deve suportar vários clientes em simultâneo.

 

  

5- Uma empresa pretende simular o comportamento de um posto de atendimento de clientes.

 

Cliente – este programa simula o comportamento de um cliente ao chegar ao posto atendimento. Se estiverem menos de 10 pessoas na sala de espera, então o programa apresenta no monitor “Cliente chegou à sala de espera”, aguarda que seja atendido e depois abandona a sala de espera. Se o número de clientes na sala já é 10, então deve apresentar no monitor “Cliente não atendido por falta de espaço” e deve incrementar o número de pessoas não atendidas.

Atendedor – este programa simula o comportamento de um funcionário a atender os clientes.  Considere que o tempo que o atendedor ocupa com cada cliente é obtido através da função tempo_cliente() – que devolve o tempo em segundos. Este programa também é responsável por apresentar no monitor  o  número de clientes que se foram embora por não ter lugar na sala de espera, sempre que termina de atender um cliente.

Novos_Clientes – este programa é responsável por “lançar” novos clientes, isto é, o programa simula a chegada de novos clientes através da execução do programa cliente (sempre que é necessário um novo cliente na simulação). Considere que o programa  Novos_clientes nunca termina e está sempre a “lançar” novos clientes (sendo o tempo de lançamento entre clientes obtido através da função tempo_novo_cliente(), que devolve o número de segundos que o programa espera até “lançar” um novo cliente).

Utilize as primitivas de semáforos e memória partilhada.

  

 

6 - Para tornar as funções de manusemento de semáforos mais “amigáveis”, de modo a ser possível utilizar nomes em substituição de chaves numéricas, implemente as seguintes funções:

      - int cria_semaforo_nome(char* nome, int n) – esta função cria um semáforo com o nome nome e com n recursos. Retorna 1 se foi possível criar esse semáforo; retorna 0 se esse semáforo já existe.

      - void ocupa_semaforo_nome(char* nome, int n) – ocupa n recursos do semáforo nome. Se não existirem recursos suficientes, o processo deve ficar bloqueado até existirem recursos.

- void liberta_semaforo_nome(char* nome, int n) – liberta n recursos do semáforo nome.

 Utilize as primitivas de semáforos e memória partilhada para resolver o problema.

 Nota: Assuma que existirão no máximo 20 semáforos.