Aula 2: Multiprogramação e Gerenciamento de Processos
Objetivos
- Explicar por que a multiprogramação requer suporte específico de hardware
- Descrever os quatro mecanismos de hardware que viabilizam a multiprogramação protegida
- Distinguir programa de processo e identificar as seções de memória de um processo
- Traçar as transições de estado de um processo nos modelos de 5 e 7 estados
- Explicar o que é o PCB, o que ele contém e por que cada campo importa
- Descrever o que acontece durante uma mudança de contexto e por que ela é considerada overhead
- Identificar processos zumbi e órfão e explicar como o SO trata cada caso
Conteúdo
1. O Desafio e o Suporte de Hardware à Multiprogramação (Ref: Cap. 1.4, 1.5 e 1.9)
O objetivo central da multiprogramação é maximizar a utilização da CPU. Como as operações de Entrada e Saída (E/S) – como ler um disco – são ordens de grandeza mais lentas que o processador, manter um único programa rodando por vez resultaria em um desperdício colossal de tempo de CPU. A solução é manter vários programas na memória simultaneamente: quando o Programa A pausa para esperar o disco, o Sistema Operacional (SO) entrega a CPU para o Programa B.
Contudo, colocar múltiplos programas na mesma memória cria um risco severo: um programa mal escrito (com um loop infinito) ou malicioso poderia sobrescrever a memória do próprio SO ou de outros programas. Por isso, a multiprogramação moderna é impossível sem suporte estrito de hardware, que atua em quatro frentes:
- Operação em Modalidade Dual (Modos do Processador): O hardware do processador fornece um "bit de modalidade" (0 ou 1) que divide a execução em pelo menos dois níveis de privilégio. O Modo Usuário é onde rodam as aplicações comuns; nele, o hardware proíbe a execução de "instruções privilegiadas" (como acessar diretamente o disco ou alterar os limites de memória). O Modo Kernel (Supervisor) é onde o SO opera, tendo controle absoluto sobre a máquina. O livro cita que o MS-DOS original não possuía essa proteção (pois o processador Intel 8088 não tinha modo dual), o que permitia que qualquer aplicativo de usuário acidentado causasse a queda do sistema inteiro.
- Chamadas de Sistema e Interrupções: Se um programa em Modo Usuário precisa de algo restrito (como gravar um arquivo), ele invoca uma Chamada de Sistema. Isso gera uma interrupção por software (chamada de trap ou exceção), que força o processador a mudar a chave para o Modo Kernel de forma controlada, executar o serviço de forma segura pelo SO, e depois retornar os resultados em Modo Usuário.
- Proteção de Memória: O hardware do processador utiliza dois registradores exclusivos para proteção: o Registrador Base (que guarda o endereço físico inicial onde o programa está na RAM) e o Registrador Limite (que define o tamanho exato do espaço daquele programa). A cada instrução executada, o processador verifica se o endereço acessado está dentro dessa faixa. Se um processo tentar invadir a memória fora de seus limites, o hardware dispara uma interrupção de acesso ilegal e o SO encerra o programa imediatamente (o famoso erro de Segmentation Fault).
- Proteção do Processador via Timer: Para impedir que um usuário egoísta ou um erro de programação (como um laço
while (true)) retenha a CPU para sempre, o SO utiliza um Timer físico na placa-mãe. O SO programa esse timer para gerar uma interrupção a cada fração de milissegundo. Quando o relógio zera, a interrupção arranca o controle da CPU das mãos do usuário e o devolve ao SO, garantindo que o escalonador possa passar a vez para o próximo processo.
2. A Anatomia de um Processo: Processo x Programa (Ref: Cap. 3.1.1)
Para gerenciar toda essa execução simultânea, o SO cria sua abstração mais importante: o Processo. É fundamental entender a separação entre programa e processo. O livro define um programa como uma entidade passiva — um arquivo binário armazenado no disco (como um .exe ou a.out). Um processo é uma entidade ativa — é o programa no momento em que está sendo executado, possuindo um Contador de Programa (Program Counter), que indica a próxima instrução, e um estado atual nos registradores da CPU.
Quando um programa é carregado na memória para virar processo, ele não é jogado de qualquer jeito. Ele é rigidamente organizado em áreas (seções):
- Texto (Código): Onde ficam as instruções de máquina a serem executadas.
- Dados (Data): Onde ficam as variáveis globais inicializadas pelo programador.
- Heap: Uma área de memória para alocação dinâmica durante a execução (como o uso de
mallocem C ounewem Java). O heap cresce de baixo para cima na memória. - Pilha (Stack): Usada para dados temporários, como parâmetros de funções, endereços de retorno (para saber para onde voltar após terminar uma sub-rotina) e variáveis locais. A pilha cresce de cima para baixo.
Nota do livro: Dois usuários rodando o mesmo navegador web estão usando o mesmo "programa", mas o SO cria dois "processos" completamente distintos e isolados, cada um com sua própria Pilha e Heap. Outro exemplo citado é a Máquina Virtual Java (JVM), que em si é um processo rodando no SO, mas que serve como ambiente de execução para interpretar o código Java (agindo como um emulador).
3. O Ciclo de Vida e os Estados do Processo (Ref: Cap. 3.1.2 e 3.2.2)
À medida que um processo é executado, ele muda de Estado. O estado de um processo define sua atividade exata naquele instante. Os sistemas teóricos evoluíram a forma de classificar esses estados:
- Modelo de 2 Estados (Rudimentar): Basicamente define se o processo está "Executando" ou "Não Executando" (Apto/Pausa). É falho porque na fila de "Não Executando" misturam-se processos que estão prontinhos para usar a CPU com processos que estão travados esperando o disco, o que confunde o escalonador.
- Modelo de 5 Estados (O Padrão Clássico): É a base de quase todo SO moderno:
- Novo: O processo está sendo criado e suas estruturas alocadas.
- Apto (Ready): O processo tem tudo o que precisa. Está na fila aguardando que o SO lhe conceda a CPU.
- Executando (Running): As instruções do processo estão passando pela CPU agora. (Em máquinas single-core, apenas UM processo pode estar neste estado por vez).
- Bloqueado / Em Espera (Waiting): O processo invocou uma E/S (como ler o disco rígido) e não pode prosseguir. Ele é retirado da CPU para não gastar tempo e fica esperando o evento terminar.
- Terminado: O processo executou sua última instrução.
- Modelo de 7 Estados (A necessidade do Swapping): Operações de E/S são lentas. Em servidores carregados, pode acontecer de todos os processos da RAM estarem no estado Bloqueado esperando discos ou rede. A CPU ficaria ociosa e a RAM desperdiçada. Para resolver isso, introduziu-se o Escalonador de Médio Prazo, que realiza o Swapping: ele expulsa temporariamente um processo da RAM copiando-o inteiro para o disco. Isso cria os estados Apto-Suspenso (pronto para rodar, mas está guardado no disco) e Bloqueado-Suspenso. Quando a RAM é liberada, o SO traz o processo do disco de volta para a memória.
4. Estruturas de Gerenciamento: O PCB e as Filas (Ref: Cap. 3.1.3 e 3.2.1)
Para controlar quem está em qual estado, o SO precisa de uma gigantesca "ficha cadastral" para cada processo, chamada de Bloco de Controle de Processo (PCB - Process Control Block).
No núcleo do Linux, isso é mapeado pela estrutura em C chamada task_struct. O PCB salva tudo: o ID do processo (PID), o estado atual, os limites de memória, a lista de arquivos abertos e, criticamente, o conteúdo de todos os registradores da CPU no momento em que o processo foi interrompido.
Gerenciamento de Filas: O SO amarra esses PCBs em listas encadeadas (filas). Existe a Fila de Prontos (processos no estado Apto) e diversas Filas de Dispositivos (uma fila para quem espera o disco 1, outra para quem espera a placa de rede). Quando ocorre uma interrupção de E/S, o hardware avisa o SO, que pega o PCB da Fila de Dispositivos e o devolve para a Fila de Prontos.
5. Escalonamento e a Mudança de Contexto (Ref: Cap. 3.2.2 e 3.2.3)
Um processo oscila entre picos de cálculo (CPU Bound - processos limitados por processamento, como renderizar um vídeo) e picos de espera por periféricos (I/O Bound - limitados por E/S, como um editor de texto aguardando o teclado). O SO deve fazer um mix inteligente desses processos para que nem a CPU e nem os discos fiquem parados.
Quando o SO decide tirar a CPU do Processo A e dar ao Processo B, ocorre a Mudança de Contexto (Context Switch). Este é um dos eventos mais críticos da Computação:
- A CPU suspende o Processo A.
- O SO salva minuciosamente o estado (todos os registradores, program counter) do Processo A em seu PCB respectivo.
- O SO busca o PCB do Processo B.
- O SO carrega (restaura) o estado de B para os registradores físicos do hardware e o processo B acorda exatamente de onde havia parado. Nota técnica importante do livro: O tempo gasto na mudança de contexto é puro overhead. O sistema não está fazendo nenhum trabalho útil (não está rodando código do usuário) enquanto salva e carrega contextos. Por isso, fabricantes de hardware investem pesado para otimizar isso (como os processadores Sun UltraSPARC, que possuíam vários conjuntos físicos de registradores para evitar ter que gravar esses dados na RAM a cada troca, bastando apenas mudar um ponteiro de hardware).
6. Criação, Hierarquia e Término de Processos (Ref: Cap. 3.3)
A gerência de processos não lida apenas com execução, mas com sua genealogia.
- A Árvore de Processos: Todo processo é criado por outro. Forma-se uma relação Pai-Filho. Em sistemas UNIX/Linux, o processo de PID 1 (chamado
initou, hoje,systemd) é a raiz absoluta da árvore. Ele cria os daemons do sistema e os processos de login dos usuários. - O Mecanismo
forkeexec: No UNIX, o processo Pai cria o Filho invocando a chamadafork(). Curiosamente, isso cria um clone exato do pai, copiando toda a sua memória. Se o objetivo do filho for rodar um programa diferente, logo após nascer ele invoca a chamadaexec(), que destrói a memória clonada e carrega o novo arquivo binário naquele espaço de endereçamento. - Término e Anomalias: Um processo encerra-se voluntariamente chamando
exit(), ou é abortado pelo SO (por erro matemático, invasão de memória, ou intervenção do usuário, como o comandokill). Em alguns sistemas, matar o pai gera o "encerramento em cascata" (todos os filhos são mortos pelo SO). - Zumbis e Órfãos:
- Zumbi: Quando um filho morre, ele envia seu status de saída (como um testamento) ao pai. Se o filho termina a execução, mas o pai (ocupado com outra coisa) ainda não invocou a função
wait()para ler esse status, o SO desaloca a memória do filho, mas é obrigado a manter o PCB dele registrado na tabela. Esse processo que já morreu, mas cujo "fantasma" ainda ocupa um espaço na tabela do sistema, é chamado de Processo Zumbi. - Órfão: Se o processo pai é encerrado abruptamente sem esperar por seus filhos (e o SO não usa a política de cascata), os filhos ficam soltos no sistema. No UNIX, eles são classificados como Órfãos. O SO resolve isso ordenando que o processo raiz (
init) "adote" os órfãos e aplique a funçãowait()neles periodicamente para garantir que eles possam morrer em paz sem virar zumbis permanentes.
- Zumbi: Quando um filho morre, ele envia seu status de saída (como um testamento) ao pai. Se o filho termina a execução, mas o pai (ocupado com outra coisa) ainda não invocou a função
Exercícios (Checkpoints)
- Por que o MS-DOS era instável comparado a sistemas modernos? Que mecanismo de hardware lhe faltava?
- Explique a diferença entre Modo Usuário e Modo Kernel. Por que essa distinção é essencial para a segurança do sistema?
- Um processo está aguardando a leitura de um arquivo em disco. Em qual estado ele se encontra? Justifique com base nos modelos de 5 estados.
- O que acontece, passo a passo, durante uma mudança de contexto? Por que esse evento é chamado de overhead puro?
- Um programa em C declara uma variável local dentro de uma função e aloca memória com
malloc(). Em quais áreas de memória do processo cada uma é armazenada? - No UNIX, qual a diferença entre
fork()eexec()? Por que eles são implementados como chamadas separadas? - Explique o que são processos zumbi e processos órfãos e descreva como o SO lida com cada situação.
Referências
Principais
- SILBERSCHATZ, Abraham; GALVIN, Peter Baer; GAGNE, Greg. Operating System Concepts. 10. ed. Wiley, 2018. Cap. 1 (seções 1.4, 1.5, 1.9) e Cap. 3.
Aprofundamento
- TANENBAUM, Andrew S. Modern Operating Systems. 4. ed. Pearson, 2014. Cap. 2.
- Linux
task_struct— Documentação do Kernel