# Comunicação entre processos

Sinais, pipes, FIFOs, sockets e memória partilhada, com um pipe pai-filho explicado.

Página: https://resumos.rgo.pt/cadeiras/so/comunicacao-processos/

O `fork` dá ao filho uma cópia da memória do pai, por isso as variáveis não atravessam a fronteira entre processos. Quando dois processos precisam de cooperar, usam um mecanismo de **comunicação entre processos** (_IPC_). Cada um tem um compromisso diferente entre simplicidade, volume e alcance.

## Sinais: interromper para avisar

Um **sinal** é uma notificação curta que um processo envia a outro: terminar (`SIGTERM`), interromper do teclado (`SIGINT`, o Ctrl+C), filho terminou (`SIGCHLD`). O processo destino pode ignorar, tratar com uma função própria ou sofrer a ação por defeito. Sinais transportam quase nenhuma informação (só o número do sinal), por isso servem para avisar, não para conversar. O `kill -TERM <pid>` da shell é o exemplo que já usaste sem saber o nome.

## Pipes: conversa entre parentes

Um **pipe** é um canal unidirecional com uma ponta de escrita e uma de leitura. Cria-se com `pipe`, que devolve dois descritores, e tipicamente usa-se entre pai e filho: o pai cria o pipe antes do `fork`, por isso ambos herdam as duas pontas, e depois cada um fecha a ponta que não usa.[1](https://resumos.rgo.pt/cadeiras/so/comunicacao-processos/#user-content-fn-pipe)

![Pai e filho ligados por um pipe: o filho fecha a leitura e escreve, o pai fecha a escrita e lê a mensagem.](https://resumos.rgo.pt/cadeiras/so/comunicacao-processos/figura-1.svg)

O filho fecha a leitura e escreve; o pai fecha a escrita e lê. O programa abaixo corre no teu terminal e imprime `Pai leu: ola pai`:

```
#include <stdio.h>
#include <string.h>
#include <unistd.h>

int main(void) {
    int pontas[2];
    pipe(pontas);
    pid_t pid = fork();
    if (pid == 0) {
        close(pontas[0]);
        const char *msg = "ola pai";
        write(pontas[1], msg, strlen(msg));
        close(pontas[1]);
    } else {
        close(pontas[1]);
        char buf[32];
        ssize_t n = read(pontas[0], buf, sizeof(buf) - 1);
        buf[n] = '\0';
        printf("Pai leu: %s\n", buf);
        close(pontas[0]);
    }
    return 0;
}
```

Segue o protocolo com atenção. Fechar as pontas não usadas não é arrumação, é correção: o `read` só devolve fim de ficheiro quando **todas** as pontas de escrita estiverem fechadas, por isso um pai que se esqueça de fechar a sua cópia da escrita fica bloqueado para sempre à espera de dados que nunca chegam. A leitura também bloqueia até haver dados, o que aqui é exatamente o que queremos: o pai espera que o filho escreva.

## FIFOs, sockets e memória partilhada

*   Um **FIFO** (_named pipe_) é um pipe com nome no sistema de ficheiros. Qualquer processo com permissões o abre pelo nome, por isso serve para processos sem parentesco. De resto comporta-se como o pipe: unidirecional e com bloqueio na leitura.
*   Um **socket** é um canal bidirecional que tanto liga processos na mesma máquina como máquinas diferentes pela rede. É a base de quase toda a comunicação em rede, mas paga-se em complexidade: endereços, ligações e protocolos.
*   A **memória partilhada** com `mmap` mapeia a mesma zona física no espaço dos dois processos, que passam a ler e escrever nela diretamente. É o mecanismo mais rápido para grandes volumes, porque não copia dados pelo núcleo. O preço é a sincronização: sem [mutexes e semáforos](https://resumos.rgo.pt/cadeiras/so/comunicacao-processos/programacao-concorrente/) para ordenar os acessos, os dois processos pisam-se e o resultado varia de execução para execução.

Experimenta o FIFO em dois terminais. No primeiro crias o canal e escreves; no segundo lês. O bloqueio aparece ao vivo: o primeiro terminal só avança quando o segundo abrir o canal para leitura.

```
mkfifo /tmp/canal
cat > /tmp/canal
```

Escreve uma linha e carrega em Enter. Repara que o `cat` fica à espera: sem leitor do outro lado, a escrita bloqueia.

```
cat /tmp/canal
```

A linha que escreveste no outro terminal aparece aqui, e o primeiro terminal desbloqueia. Apaga o canal no fim com `rm /tmp/canal`.

A memória partilhada dispensa o núcleo depois da configuração. Este programa mapeia um inteiro partilhado entre pai e filho: o filho soma 1000, o pai espera e soma 2000, e o resultado é sempre 3000. Sem o `wait`, o pai podia ler antes de o filho escrever. Corre no teu terminal:

```
#include <stdio.h>
#include <sys/mman.h>
#include <sys/wait.h>
#include <unistd.h>

int main(void) {
    int *p = mmap(NULL, sizeof(int), PROT_READ | PROT_WRITE,
                  MAP_SHARED | MAP_ANONYMOUS, -1, 0);
    *p = 0;
    if (fork() == 0) {
        *p += 1000;
        return 0;
    }
    wait(NULL);
    *p += 2000;
    printf("%d\n", *p);
    return 0;
}
```

| Mecanismo | Sentido | Entre quem | Quando usar |
| --- | --- | --- | --- |
| Sinais | aviso | qualquer processo | notificar eventos simples |
| Pipes | unidirecional | pai e filho | conversa simples entre parentes |
| FIFOs | unidirecional | qualquer processo | pipe sem parentesco |
| Sockets | bidirecional | qualquer processo | rede ou protocolo completo |
| Memória partilhada | direto | qualquer processo | grandes volumes, com sincronização |

[Vídeo: Interprocess Communication](https://www.youtube.com/watch?v=dJuYKfR8vec)

A miniatura vem do YouTube. O vídeo só carrega quando clicas. [Abrir no YouTube](https://www.youtube.com/watch?v=dJuYKfR8vec)

## Para saber mais

*   O manual oficial da chamada `pipe`, com a semântica de bloqueio e os erros: [`pipe(2)`](https://man7.org/linux/man-pages/man2/pipe.2.html). A [vista geral de pipes e FIFOs](https://man7.org/linux/man-pages/man7/pipe.7.html) completa as regras de leitura e escrita.

## Para levar para a próxima página

Pipes e FIFOs copiam bytes pelo núcleo e impõem ordem; a memória partilhada é rápida mas deixa os acessos desordenados. Ordenar acessos concorrentes é o trabalho da [programação concorrente](https://resumos.rgo.pt/cadeiras/so/comunicacao-processos/programacao-concorrente/).

## Notas de rodapé

1.  O manual oficial fixa as regras que o exemplo segue, incluindo o bloqueio até ambas as pontas estarem abertas: [`pipe(7)`](https://man7.org/linux/man-pages/man7/pipe.7.html). [Voltar](https://resumos.rgo.pt/cadeiras/so/comunicacao-processos/#user-content-fnref-pipe)
