Durante nossas aulas de sistemas operacionais, começamos a explorar o conceito de processos e threads, dois pilares essenciais para entender como o sistema gerencia a execução de programas. Neste artigo, vou compartilhar as anotações que fiz durante a aula, com explicações sobre o ciclo de vida de um processo, chamadas de sistema para criação de processos, diferenças entre processos e threads, e mecanismos de comunicação entre processos (IPC).

O que é um processo?

Um processo pode ser definido como um programa em execução. Cada processo possui seu próprio espaço de endereçamento, contexto de hardware e informações de estado gerenciadas pelo sistema operacional. O kernel mantém uma estrutura de dados chamada Process Control Block (PCB) que armazena todas as informações necessárias para gerenciar o processo: identificador (PID), estado, contador de programa, registradores, limites de memória, lista de arquivos abertos, entre outros.

Os estados clássicos de um processo são:

  • Novo: o processo está sendo criado.
  • Pronto: está na fila de processos prontos para executar, aguardando o escalonador.
  • Executando: está sendo executado pela CPU.
  • Bloqueado: aguarda algum evento externo (E/S, sinal, etc.).
  • Terminado: finalizou sua execução.

O escalonador de processos decide qual processo pronto será executado, utilizando algoritmos como Round Robin, FCFS (First-Come, First-Served) ou Prioridade. A troca de contexto (context switch) é a operação de salvar o estado do processo atual e carregar o estado do próximo, algo que tem custo e deve ser otimizado.

Criação de processos: fork e exec

Nos sistemas Unix, a principal chamada de sistema para criar processos é a fork(). Ela cria um novo processo (filho) que é uma cópia exata do processo pai, incluindo o espaço de memória, descritores de arquivos e contexto. A partir do ponto da chamada, ambos os processos continuam executando concorrentemente, e o valor de retorno de fork() diferencia o pai (recebe o PID do filho) do filho (recebe 0).

Geralmente, o processo filho utiliza a família de funções exec() para substituir seu espaço de endereçamento por um novo programa. Isso permite que o sistema execute um binário diferente dentro do processo recém-criado. Por exemplo:

pid_t pid = fork();
if (pid == 0) {
    // código do filho
    execlp("/bin/ls", "ls", NULL);
} else if (pid > 0) {
    // código do pai
    wait(NULL);
}

Outras chamadas importantes incluem wait() (sincronização entre pai e filho) e exit() (encerramento). O gerenciamento correto de processos é fundamental para evitar processos zumbis ou órfãos.

Threads: leveza e concorrência

Uma thread (ou linha de execução) é a menor unidade de execução que pode ser escalonada pelo sistema operacional. Diferentemente de processos, threads de um mesmo processo compartilham o mesmo espaço de endereçamento, o que facilita a comunicação e a troca de dados, mas exige cuidado com exclusão mútua e sincronização.

As principais vantagens do uso de threads incluem:

  • Menor custo de criação e troca de contexto em comparação com processos.
  • Comunicação eficiente: uso de variáveis compartilhadas (com proteção de semáforos/mutexes).
  • Paralelismo real em sistemas multiprocessadores.

Existem dois tipos principais: threads de usuário (gerenciadas por bibliotecas em espaço de usuário) e threads de kernel (gerenciadas diretamente pelo núcleo). Modelos como N:1, 1:1 e M:N mapeiam threads de usuário para threads de kernel.

Comunicação entre processos (IPC)

Quando processos precisam trocar dados ou sincronizar suas execuções, o sistema operacional oferece mecanismos de Interprocess Communication (IPC). Os mais comuns são:

  • Pipes: canais unidirecionais que conectam a saída de um processo à entrada de outro. Pipes anônimos são usados entre processos relacionados; pipes nomeados (FIFOs) permitem comunicação entre processos quaisquer.
  • Memória compartilhada: uma região de memória acessível a múltiplos processos. É o mecanismo mais rápido, mas requer controle de concorrência (semáforos).
  • Troca de mensagens: filas de mensagens e sockets permitem comunicação estruturada, útil em sistemas distribuídos.
  • Sinais: notificações assíncronas enviadas a um processo para indicar eventos (ex.: SIGINT, SIGKILL).

A escolha do mecanismo depende da necessidade de desempenho, complexidade e do ambiente (local ou rede).

Perguntas frequentes

Qual a diferença entre processo e thread?

Um processo possui seu próprio espaço de endereçamento independente, enquanto threads de um mesmo processo compartilham o mesmo espaço de endereçamento e recursos. A criação de threads é mais leve e a comunicação entre threads é mais rápida, mas exige sincronização. Processos oferecem maior isolamento e segurança.

O que é escalonamento preemptivo?

No escalonamento preemptivo, o sistema operacional pode interromper um processo em execução a qualquer momento para dar lugar a outro, baseado em um critério de tempo (time slice) ou prioridade. Isso garante maior responsividade e justiça na alocação da CPU, ao contrário do escalonamento não preemptivo (cooperativo).

Como funciona fork() em sistemas Unix?

A chamada fork() duplica o processo atual, criando um filho que é uma cópia exata do pai. Ambos continuam a execução a partir da instrução seguinte ao fork(). O valor de retorno diferencia os dois: no pai, retorna o PID do filho (positivo); no filho, retorna 0. Em caso de erro, retorna -1. O filho pode então usar exec() para carregar um novo programa.

Resumo

Nesta aula vimos os fundamentos de processos e threads, desde o conceito de PCB e estados até a criação com fork/exec e os principais mecanismos de IPC. Entender esses tópicos é essencial para programação concorrente e para compreender o funcionamento interno dos sistemas operacionais. Nas próximas aulas avançaremos para problemas clássicos de sincronização e gerenciamento de memória.