Pular para o conteúdo principal

Aula 11 — Memória Compartilhada e IPC

:::note Ambiente sugerido Todos os programas compilam em Linux com gcc -lrt -lpthread. Execute o produtor em um terminal e o consumidor em outro para observar a comunicação entre processos reais. :::

Objetivos

Ao final desta aula você deve ser capaz de:

  • Distinguir os dois modelos de IPC (memória compartilhada vs. transmissão de mensagens) e apontar os trade-offs de cada um.
  • Usar a API POSIX de memória compartilhada: shm_open, ftruncate, mmap, munmap, shm_unlink.
  • Explicar por que memória compartilhada exige sincronização explícita e como semáforos nomeados resolvem isso entre processos distintos.
  • Implementar o padrão Produtor/Consumidor usando memória compartilhada POSIX + semáforos nomeados (sem_open, sem_wait, sem_post, sem_close, sem_unlink).
  • Comparar semáforos sem nome (sem_init) para threads com semáforos nomeados (sem_open) para processos.

Conteúdo

Modelos de IPC: memória compartilhada vs. mensagens

Processos têm espaços de endereçamento separados por design — o SO protege um processo de acessar memória de outro. Para cooperar, processos precisam de um mecanismo de IPC (Inter-Process Communication).

Nota de desempenho: após o setup inicial (chamadas de sistema shm_open e mmap), acessos à memória compartilhada são simples acessos de RAM — sem syscalls. Transmissão de mensagens requer syscalls a cada envio/recebimento. Para transferências de grandes volumes de dados, memória compartilhada é significativamente mais rápida.


API POSIX de Memória Compartilhada

O POSIX implementa memória compartilhada como objetos de arquivo mapeados em memória — o kernel mapeia o mesmo arquivo físico (ou objeto anônimo) no espaço de endereçamento de dois ou mais processos.

Sequência de syscalls — Criador (Produtor)

/* Somente leitura — fluxo do criador */
// 1. Cria/abre o objeto de memória compartilhada
int shm_fd = shm_open("/meu_shm", O_CREAT | O_RDWR, 0666);

// 2. Define o tamanho do objeto (obrigatório antes de mmap)
ftruncate(shm_fd, SIZE);

// 3. Mapeia na memória virtual do processo
void *ptr = mmap(0, SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, shm_fd, 0);

// 4. Usa normalmente como ponteiro de memória
// ... escreve dados ...

// 5. Desmapeia (não remove o objeto)
munmap(ptr, SIZE);
close(shm_fd);

// 6. Remove o objeto (quando não for mais necessário)
shm_unlink("/meu_shm");

Sequência de syscalls — Consumidor

/* Somente leitura — fluxo do consumidor */
// 1. Abre o objeto existente (sem O_CREAT — já existe)
int shm_fd = shm_open("/meu_shm", O_RDONLY, 0666);

// 2. Mapeia somente para leitura
void *ptr = mmap(0, SIZE, PROT_READ, MAP_SHARED, shm_fd, 0);

// 3. Lê os dados
printf("%s\n", (char *)ptr);

// 4. Desmapeia
munmap(ptr, SIZE);
close(shm_fd);

// 5. Remove o objeto (responsabilidade do último a usar)
shm_unlink("/meu_shm");

Tabela da API POSIX de shm

SyscallPropósito
shm_open(name, flags, mode)Cria/abre objeto shm; retorna fd
ftruncate(fd, size)Define o tamanho do objeto (bytes)
mmap(addr, len, prot, flags, fd, offset)Mapeia na memória virtual; retorna ponteiro
munmap(ptr, len)Remove o mapeamento (não remove o objeto)
close(fd)Fecha o fd (não remove o objeto)
shm_unlink(name)Remove o objeto do sistema de arquivos

Flags de proteção do mmap:

FlagSignificado
PROT_READApenas leitura
PROT_WRITEApenas escrita
PROT_READ | PROT_WRITELeitura e escrita
MAP_SHAREDModificações visíveis para outros processos
MAP_PRIVATECópia privada (COW), modificações não visíveis

O problema: memória compartilhada sem sincronização

A memória compartilhada não oferece sincronização automática. Se dois processos acessam a mesma região simultaneamente, ocorre condição de corrida — exatamente como com threads e variáveis globais.

Produtor escreve dados → buffer[] → Consumidor lê dados

Problemas sem sincronização:
1. Consumidor lê antes do produtor terminar de escrever → dados parciais/corrompidos
2. Produtor sobrescreve buffer antes do consumidor ler → dados perdidos
3. Dois produtores escrevem simultaneamente → corrompido

Solução: semáforos nomeados — semáforos que existem no sistema de arquivos como objetos persistentes, visíveis por qualquer processo que conheça o nome.


Semáforos Nomeados POSIX

Semáforos sem nome (sem_init) são alocados na memória do processo — não são visíveis por processos distintos. Semáforos nomeados (sem_open) são criados no sistema de arquivos virtual (/dev/shm no Linux) e podem ser compartilhados entre processos por nome.

sem_init(): sem_open():
└─ mora na memória do └─ mora em /dev/shm/<nome>
processo (ou shm) visível por qualquer processo
└─ use para threads └─ use entre processos distintos
do mesmo processo

API de semáforos nomeados:

FunçãoPropósito
sem_open(name, O_CREAT, mode, value)Cria/abre semáforo; retorna sem_t *
sem_wait(sem)P() — decrementa; bloqueia se < 0
sem_post(sem)V() — incrementa; acorda processo
sem_close(sem)Fecha o handle (não remove)
sem_unlink(name)Remove do sistema de arquivos
sem_open(name, 0)Abre semáforo existente (sem O_CREAT)

Produtor/Consumidor entre processos distintos

O programa a seguir está dividido em dois executáveis independentes que se comunicam via memória compartilhada e coordenam via semáforos nomeados.

Estrutura compartilhada (header comum)

/* shared.h — incluído por produtor e consumidor */
#ifndef SHARED_H
#define SHARED_H

#define SHM_NAME "/prod_cons_shm"
#define SEM_EMPTY "/prod_cons_empty"
#define SEM_FULL "/prod_cons_full"
#define SEM_MUTEX "/prod_cons_mutex"
#define N 8 /* slots do buffer circular */

typedef struct {
int buffer[N];
int in; /* próxima posição de escrita */
int out; /* próxima posição de leitura */
} SharedBuffer;

#endif

Processo Produtor

/* Executável — produtor.c */
/* Compile: gcc -o produtor produtor.c -lrt -lpthread */
/* Execute: ./produtor */
#include <fcntl.h>
#include <semaphore.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include "shared.h"

int main(void)
{
/* ── cria/abre memória compartilhada ── */
int shm_fd = shm_open(SHM_NAME, O_CREAT | O_RDWR, 0666);
ftruncate(shm_fd, sizeof(SharedBuffer));
SharedBuffer *shm = mmap(NULL, sizeof(SharedBuffer),
PROT_READ | PROT_WRITE,
MAP_SHARED, shm_fd, 0);
shm->in = 0;
shm->out = 0;

/* ── cria semáforos nomeados ── */
sem_t *sem_empty = sem_open(SEM_EMPTY, O_CREAT, 0666, N); /* N slots vazios */
sem_t *sem_full = sem_open(SEM_FULL, O_CREAT, 0666, 0); /* 0 itens cheios */
sem_t *sem_mutex = sem_open(SEM_MUTEX, O_CREAT, 0666, 1); /* mutex binário */

printf("Produtor iniciado. Produzindo %d itens...\n", N * 2);

for (int i = 0; i < N * 2; i++) {
int item = i * 10;

sem_wait(sem_empty); /* espera slot vazio */
sem_wait(sem_mutex); /* entra na seção crítica */

shm->buffer[shm->in] = item;
printf("[P] buffer[%d] = %d\n", shm->in, item);
shm->in = (shm->in + 1) % N;

sem_post(sem_mutex); /* sai da seção crítica */
sem_post(sem_full); /* sinaliza: há novo item */

usleep(200000); /* simula produção (200ms) */
}

/* ── limpeza ── */
sem_close(sem_empty);
sem_close(sem_full);
sem_close(sem_mutex);
munmap(shm, sizeof(SharedBuffer));
close(shm_fd);
printf("Produtor encerrado.\n");
return 0;
}

Processo Consumidor

/* Executável — consumidor.c */
/* Compile: gcc -o consumidor consumidor.c -lrt -lpthread */
/* Execute: ./consumidor (após ./produtor em outro terminal) */
#include <fcntl.h>
#include <semaphore.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include "shared.h"

int main(void)
{
/* ── abre objetos já criados pelo produtor ── */
int shm_fd = shm_open(SHM_NAME, O_RDWR, 0666);
SharedBuffer *shm = mmap(NULL, sizeof(SharedBuffer),
PROT_READ | PROT_WRITE,
MAP_SHARED, shm_fd, 0);

sem_t *sem_empty = sem_open(SEM_EMPTY, 0); /* sem O_CREAT — já existe */
sem_t *sem_full = sem_open(SEM_FULL, 0);
sem_t *sem_mutex = sem_open(SEM_MUTEX, 0);

printf("Consumidor iniciado. Consumindo %d itens...\n", N * 2);

for (int i = 0; i < N * 2; i++) {
sem_wait(sem_full); /* espera item disponível */
sem_wait(sem_mutex); /* entra na seção crítica */

int item = shm->buffer[shm->out];
printf("[C] buffer[%d] = %d\n", shm->out, item);
shm->out = (shm->out + 1) % N;

sem_post(sem_mutex); /* sai da seção crítica */
sem_post(sem_empty); /* sinaliza: slot liberado */

usleep(350000); /* simula consumo (350ms) */
}

/* ── limpeza: consumidor remove os objetos ── */
sem_close(sem_empty); sem_unlink(SEM_EMPTY);
sem_close(sem_full); sem_unlink(SEM_FULL);
sem_close(sem_mutex); sem_unlink(SEM_MUTEX);
munmap(shm, sizeof(SharedBuffer));
close(shm_fd); shm_unlink(SHM_NAME);
printf("Consumidor encerrado — objetos removidos.\n");
return 0;
}

Atividade prática rápida: compile ambos, execute ./produtor & em background e ./consumidor em foreground. Observe a coordenação. Depois tente executar o consumidor antes do produtor — o shm_open falhará (objeto não existe). Como você trataria isso?


Visão geral: threads vs. processos — que mecanismo usar?

Dados compartilhados entre THREADS do mesmo processo:
→ Variável global / heap diretamente acessível
→ Sincronização: pthread_mutex_t, sem_t (sem_init, shared=0)
→ Sem setup especial

Dados compartilhados entre PROCESSOS distintos:
→ Precisam de IPC explícito:
- Memória compartilhada POSIX (shm_open + mmap)
- Pipes (anônimos ou nomeados)
- Sockets (local AF_UNIX ou rede)
- Filas de mensagens POSIX (mq_open)
→ Sincronização inter-processo: sem_t (sem_open, nomeados)

Exercícios

Questões dissertativas

Q1

Compare memória compartilhada e transmissão de mensagens como mecanismos de IPC. Para cada um, indique: velocidade relativa, overhead de sincronização, facilidade de implementação e cenário de uso ideal.

Q2

Explique o papel de cada parâmetro da chamada mmap(0, SIZE, PROT_READ|PROT_WRITE, MAP_SHARED, shm_fd, 0). O que mudaria se MAP_PRIVATE fosse usado no lugar de MAP_SHARED?

Q3

Por que um semáforo criado com sem_init(sem, 0, value) não pode ser usado para sincronização entre dois processos criados com fork()? Quando ele pode e quando não pode?

Q4

No programa produtor/consumidor com shm, o produtor criou os semáforos nomeados e terminou. O consumidor ainda não fez sem_unlink(). O que acontece com os semáforos? E se o consumidor travar antes de chamar sem_unlink()?

Q5

Qual a diferença entre shm_unlink() e munmap()? Quais são as consequências de chamar shm_unlink() enquanto outro processo ainda tem o objeto mapeado?

Q6

Por que a sequência correta é ftruncate() ANTES de mmap(), e não depois? O que acontece se mmap() for chamado antes de ftruncate() em um objeto recém-criado?

Q7

Descreva como um bug de 'double free' pode ocorrer com shm_unlink() quando dois processos tentam limpar os mesmos recursos. Como preveni-lo?

Q8Difícil

Explique os objetos criados em /dev/shm após executar o programa produtor. Liste os nomes esperados e o que cada um representa.

Q9

Como adaptar o programa produtor/consumidor para funcionar com N produtores e M consumidores? O código precisaria de alterações nos semáforos?

Q10Difícil

Por que os objetos de memória compartilhada são implementados como arquivos mapeados em memória no POSIX, em vez de simplesmente alocar memória diretamente? Que vantagens essa implementação oferece?


Quiz de múltipla escolha

Quiz10 questões

1. Qual a ordem correta das chamadas de sistema para criar e usar memória compartilhada POSIX?

  • a)mmap → shm_open → ftruncate → usar → munmap → shm_unlink
  • b)shm_open → mmap → ftruncate → usar → munmap → shm_unlink
  • c)shm_open → ftruncate → mmap → usar → munmap → shm_unlink
  • d)shm_open → ftruncate → mmap → usar → shm_unlink → munmap
  • e)ftruncate → shm_open → mmap → usar → munmap → shm_unlink

2. Um processo usa sem_init(&sem, 0, 1). Outro processo separado tenta usar o mesmo semáforo. O que acontece?

  • a)Funciona normalmente — sem_init com pshared=0 é visível para todos os processos
  • b)O segundo processo recebe EEXIST ao tentar abrir
  • c)O segundo processo tem uma cópia independente — operações num não afetam o outro
  • d)O sistema operacional sincroniza automaticamente as duas cópias
  • e)O segundo processo trava esperando a sincronização

3. Qual a diferença entre munmap() e shm_unlink()?

  • a)munmap remove o objeto do sistema; shm_unlink apenas fecha o fd
  • b)São equivalentes — qualquer um pode ser usado
  • c)munmap remove o mapeamento do processo atual; shm_unlink remove o nome do objeto do sistema de arquivos
  • d)munmap deve sempre ser chamado antes de shm_unlink
  • e)shm_unlink destrói imediatamente o objeto mesmo com outros processos mapeados

4. O flag MAP_SHARED é essencial para IPC com shm. O que acontece se MAP_PRIVATE for usado?

  • a)Os processos compartilham a memória normalmente, mas com proteção contra escrita simultânea
  • b)Cada processo recebe uma cópia privada (COW) — escritas de um processo NÃO são visíveis para o outro
  • c)O mmap retorna erro EPERM
  • d)A escrita é bufferizada e sincronizada automaticamente pelo kernel
  • e)Apenas o processo criador pode escrever; outros podem somente ler

5. Qual a vantagem principal de semáforos nomeados (sem_open) sobre semáforos sem nome (sem_init) para IPC entre processos independentes?

  • a)Semáforos nomeados têm menor overhead de operação
  • b)Semáforos nomeados são mais rápidos que sem nome
  • c)Semáforos nomeados existem no sistema de arquivos e podem ser abertos por qualquer processo que conheça o nome, independentemente de relação de parentesco
  • d)Semáforos sem nome só funcionam em sistemas monoprocessador
  • e)Semáforos nomeados suportam mais de 255 processos simultâneos

6. Por que memória compartilhada é considerada mais rápida que transmissão de mensagens para grandes volumes de dados?

  • a)Memória compartilhada usa hardware especial de DMA
  • b)Após o setup via syscall, acessos são simples operações de RAM sem syscalls adicionais; pipes/sockets requerem syscall + cópia de dados pelo kernel a cada operação
  • c)Transmissão de mensagens usa protocolo de rede mesmo entre processos locais
  • d)Memória compartilhada comprime os dados automaticamente
  • e)O kernel prioriza processos que usam memória compartilhada no scheduling

7. sem_open("/sem_test", O_CREAT, 0666, 3) é chamado por dois processos P1 e P2 quase simultaneamente. Qual o resultado?

  • a)Ambos criam semáforos separados com valor 3 cada
  • b)P2 recebe EEXIST e falha
  • c)O primeiro a executar cria o semáforo com valor 3; o segundo abre o existente — ambos recebem um handle válido para o mesmo semáforo
  • d)O kernel realiza uma operação de fusão dos dois semáforos
  • e)Um comportamento indefinido — resultado depende do scheduler

8. Um processo P1 faz shm_unlink("/dados") mas P2 ainda tem o objeto mapeado. O que acontece com P2?

  • a)P2 recebe um sinal SIGBUS e termina
  • b)P2 continua acessando normalmente — o objeto existe enquanto P2 tiver mapeamento ativo
  • c)P2 passa a ler zeros (memória zerada após unlink)
  • d)P2 recebe SIGSEGV na próxima escrita
  • e)O kernel força P2 a desmapar o objeto

9. Para transmissão de mensagens com shm, quem é responsável por garantir que o receptor não lê dados antes do produtor terminar de escrever?

  • a)O kernel — mmap garante atomicidade das escritas
  • b)O hardware — a CPU garante coerência de cache automaticamente
  • c)O programador — via mecanismo de sincronização explícito como semáforos ou mutexes
  • d)MAP_SHARED garante a sincronização automaticamente
  • e)O sistema de arquivos virtual (/dev/shm) implementa locking interno

10. Qual dos seguintes mecanismos é mais adequado para passar 10MB de dados de um processo servidor para um processo cliente no mesmo sistema Linux?

  • a)Pipe anônimo — simples e eficiente para grandes volumes
  • b)Socket TCP localhost — mais confiável que outros mecanismos
  • c)Memória compartilhada POSIX (shm_open + mmap) — transferência de RAM sem cópias via kernel
  • d)Fila de mensagens POSIX (mq_open) — projetada para grandes mensagens
  • e)Variáveis de ambiente — acessíveis para todos os processos filhos

Referências

Principais (essenciais)

  • SILBERSCHATZ, A.; GALVIN, P. B.; GAGNE, G. Fundamentos de Sistemas Operacionais. 9. ed. LTC, 2015.
    • §3.5.1 — Memória Compartilhada POSIX
    • §5.6 — Semáforos
    • §5.9 — Exemplos de Sincronização
  • man 3 shm_open, man 2 mmap, man 3 sem_open — páginas de manual POSIX.

Aprofundamento (opcionais)

  • KERRISK, M. The Linux Programming Interface. No Starch Press, 2010. Cap. 48–54 (shm POSIX e System V, semáforos nomeados).

Recursos Complementares

Opcional — Vídeos para reforçar os conceitos desta aula.

Thumbnail do vídeo: Shared Memory Systems — IPC11:22
ENOpcional

Shared Memory Systems — IPC

Neso Academy

Modelo de memória compartilhada para IPC com exemplo do Produtor/Consumidor. Cobre §3.4.1 do Silberschatz.

Thumbnail do vídeo: Producer Consumer Problem Using Semaphores18:40
ENRevisão

Producer Consumer Problem Using Semaphores

Neso Academy

Revisão da solução com semáforos — base para o Produtor/Consumidor entre processos desta aula.

Thumbnail do vídeo: Operating Systems — Full Course (25h)25:00:00
ENAprofundamento

Operating Systems — Full Course (25h)

freeCodeCamp.org

Para esta aula, foque no módulo Shared Memory e IPC.