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).
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 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_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 */
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)
permite criar uma região de memória partilhada privada ao processo invocador;
o idenficador resultante nunca vai ser retornado a outros processos que invoquem shmget (seja qual for o seu parâmetro chave);
o identificador pode ser partilhado com outros processos (ao serem efectuados sucessivos fork(), as variáveis do processo pai são herdadas pelas correspondentes variáveis dos processos filho);
permite criar ou abrir uma região de memória partilhada, de acordo com o conteúdo do parâmetro flags;
a região de memória partilhada pode ser partilhada com qualquer outro processo, desde que o parâmetro chave seja igual.
Parâmetro flags:
Flag IPC_CREAT activa e IPC_EXCL activa
cria a região de memória partilhada se a chave não existir (caso contrário retorna erro);
permite verificar se uma dada região existe ou não.
Flag IPC_CREAT e IPC_EXCL não activas
retorna o identificador associado à região de memória partilhada chave (se chave existir, caso contrário retorna erro);
se tamanho > tamanho_da_região então retorna erro.
Flag IPC_CREAT activa e IPC_EXCL não activa
cria a região de memória partilhada se chave não existir (se chave existe, retorna o identificador associado à região com essa chave);
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:
identifica o endereço virtual (pertencente ao espaço de endereçamento do processo) onde deve ser feita a correspondência;
0 - o endereço é escolhido pelo sistema operativo;
>0 - a correspondência é feita a partir do endereço especificado;
Parâmetro flags
permite escolher a permissão leitura/escrita ou somente de leitura.
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.
O uid do processo invocador deve ser igual ao campo shm_perm.uid, ou igual ao campo shm_perm.cuid (ou deve ser o superuser);
Se não ocorreu um erro o campo shm_ctime é actualizado.
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.
a região só é removida quando todos os processos que estiverem ligados a ela (shmat) se desligarem (shmdt). De qualquer modo, a partir do momento em que é invocado shmctl + IPC_RMID, mais nenhum processo consegue fazer o shmat dessa região;
o uid do processo invocador deve ser igual ao campo shm_perm.uid, ou igual ao campo shm_perm.cuid (ou então deve ser o superuser).
Escritor.c - este programa escreve um número na região de memória partilhada
void *memoria_partilhada = (void *) 0; /* apontador para o segmento
de memoria partilhada */
int *aponta;
/* 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);
printf("Erro !!!\n");
/* "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");
printf("A memoria esta' 'ligada' em %X\n", (int)memoria_partilhada);
aponta = (int *) memoria_partilhada;
/* 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");
Leitor.c - este programa lê um número da região de memória partilhada
/* cria ou utiliza o segmento de memória partilhada */
printf("O valor que esta' na regiao de memoria partilhada e' %d\n", *aponta);
/* 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");
1 - Faça os seguintes programas:
Escritor - este programa escreve uma string numa região de memória partilhada.
Leitor - este programa lê uma string de uma região de memória partilhada.
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.