Pesquisar conteúdos

Encontre uma trilha ou aula pelo assunto.

Experimente pesquisar por “governança”, “ITIL” ou “planejamento”.

Programa, processo, estados e contexto de execução

Diferencie programa e processo e compreenda criação, estados, identificadores, recursos e troca de contexto.
Programa armazenado passa por uma estação de ativação e origina processos que circulam entre fila, CPU, espera e término com contextos próprios

Programa é descrição; processo é execução

Um programa é uma descrição de trabalho: código, dados iniciais e metadados armazenados em um arquivo ou distribuídos em componentes. Um processo é uma ocorrência desse programa em execução, acompanhada pelo sistema operacional com identidade, memória, recursos, credenciais e pelo menos uma unidade capaz de avançar instruções.

Abrir duas vezes o mesmo aplicativo normalmente cria duas execuções distinguíveis. Elas podem partir dos mesmos bytes do programa, mas possuir identificadores, dados em memória, arquivos abertos e momentos de vida diferentes. Encerrar uma não apaga o arquivo executável nem obriga a outra a terminar.

Na aula anterior, vimos como o shell resolve um comando e prepara ambiente, argumentos e fluxos. Agora acompanharemos o que acontece depois que o sistema admite uma execução. As operações privilegiadas continuam atravessando as interfaces entre espaço de usuário e kernel.

O mesmo programa pode originar muitos processos

Imagine um serviço que gera o índice de busca da plataforma de estudos. O arquivo do indexador contém instruções relativamente estáveis. Às 10h, uma execução pode indexar a trilha de Redes; às 10h05, outra pode indexar Sistemas Operacionais. Ambas usam o mesmo programa, mas recebem argumentos distintos e mantêm progresso, memória e arquivos próprios.

O programa se parece com uma receita guardada. O processo se parece com uma preparação concreta: possui ingredientes separados, utensílios em uso, uma etapa atual e efeitos já produzidos. A analogia tem limite — uma execução real também inclui mecanismos de proteção e unidades escalonáveis —, mas evita chamar o arquivo parado de processo.

Um programa persistente é carregado em dois processos, cada um com PID, memória, recursos e contexto próprios
Código de origem comum não torna as execuções uma só. Cada processo recebe identidade e contexto próprios, ainda que algumas páginas ou bibliotecas possam ser compartilhadas de maneira controlada.

O sistema pode compartilhar internamente páginas de código somente leitura, bibliotecas ou outros objetos para economizar recursos. Isso não desfaz a separação lógica. Cada processo continua observando seu espaço virtual e seus atributos conforme as regras da plataforma.

Um processo reúne mais do que código

Quando a execução é criada, o sistema precisa representar um conjunto de informações. Os detalhes variam, mas normalmente encontramos:

  • imagem do programa: código executável e dados que foram carregados ou mapeados;
  • espaço de endereçamento virtual: regiões para código, dados, heap, pilhas e mapeamentos;
  • recursos abertos: arquivos, pipes, sockets, dispositivos e outros objetos acessados por descritores ou handles;
  • identidade e proteção: usuário, grupos, token ou credenciais, privilégios e políticas aplicáveis;
  • ambiente de execução: argumentos, variáveis, diretório atual e limites;
  • informações de relacionamento: identificador, processo pai, grupo, sessão ou objeto equivalente;
  • unidades de execução: uma ou mais threads, com registradores, pilha e estado escalonável.

No Windows, a documentação descreve o processo com espaço virtual, código, handles, contexto de segurança, identificador, ambiente e ao menos uma thread. Essa última distinção é importante: em sistemas modernos, o escalonador costuma escolher threads para usar o processador, embora materiais introdutórios representem “o processo executando”. A próxima aula aprofundará essa relação.

Um socket aberto, por exemplo, pode fazer parte dos recursos acessíveis ao processo, mas o número da porta não identifica sozinho uma aplicação. A aula de TCP, UDP, portas e sockets explica a associação de comunicação; aqui interessa perceber que o sistema mantém a referência necessária para a execução usar esse objeto.

PID identifica uma ocorrência, não uma identidade eterna

O PID (process identifier) é um número usado pelo sistema e por ferramentas para distinguir processos existentes em determinado contexto. Em sistemas Unix-like, o PPID indica o PID do processo pai. No Windows, APIs e ferramentas também expõem identificadores de processo e relacionamento de criação.

O PID não deve ser tratado como nome permanente. Depois que um processo termina e sua informação é liberada, o número pode ser reutilizado. Entre a observação e uma ação posterior, outro processo pode ocupar aquele identificador. Namespaces, contêineres e diferentes visões do sistema também podem apresentar identificadores distintos para a mesma tarefa subjacente.

Por isso, uma ferramenta confiável combina PID com contexto adicional: instante de criação, objeto ou handle mantido aberto, usuário, executável esperado e escopo de observação. Um PID isolado em um registro antigo não prova que o processo atual é a mesma execução.

Criar e carregar são operações relacionadas, mas diferentes

Em sistemas POSIX, um padrão tradicional separa duas operações. fork() cria um processo filho a partir do chamador. Pai e filho passam a ter espaços de memória separados, inicialmente com conteúdo equivalente segundo as regras da implementação. Depois, uma função da família exec pode substituir a imagem do processo chamador por outro programa.

Um exec bem-sucedido não cria outro PID. A imagem anterior é trocada, enquanto atributos definidos pelo padrão permanecem, entre eles o identificador do processo. A frase “o exec cria um processo” é, portanto, imprecisa. Outras interfaces, como posix_spawn(), podem oferecer um contrato de criação mais direto.

No Windows, CreateProcess cria um novo objeto de processo e sua thread primária, carregando o módulo executável indicado. A decomposição interna não precisa copiar o modelo fork mais exec. O princípio comum é mais amplo: o sistema valida a solicitação, cria estruturas de acompanhamento, prepara recursos e torna uma unidade elegível para execução.

O relacionamento pai-filho registra origem de criação e auxilia controle, espera ou agrupamento, mas não equivale necessariamente a propriedade humana. Serviços, shells, gerenciadores e aplicações podem criar processos; cada plataforma define quais atributos são herdados e como descendentes são acompanhados.

Estados condensam a situação da execução

Um modelo didático frequente usa cinco estados:

  1. novo: estruturas estão sendo criadas e a execução ainda não foi admitida;
  2. pronto: possui condições para avançar, mas aguarda tempo de processador;
  3. executando: uma unidade de execução está usando uma CPU;
  4. esperando ou bloqueado: aguarda evento, dado, temporizador ou recurso antes de poder continuar;
  5. terminado: a execução encerrou, embora o sistema ainda possa conservar informações para coleta.

O estado descreve uma condição, não um lugar físico. Um item “pronto” não está necessariamente em uma fila única, e uma máquina multiprocessada pode ter várias unidades executando ao mesmo tempo. Além disso, em um processo com várias threads, algumas podem executar, outras esperar e outras estar prontas; atribuir um único estado ao processo inteiro perde detalhes.

Ciclo com os estados novo, pronto, executando, esperando e terminado e suas transições
O modelo de cinco estados ajuda a raciocinar. Implementações reais podem dividir esperas, distinguir suspensão, rastreamento e término, ou manter estado por thread.

As principais transições possuem causas diferentes. Admitir leva de novo a pronto. O escalonador seleciona trabalho pronto para executar. Uma interrupção, preempção ou cessão pode devolver a unidade à condição de pronta. Solicitar entrada e saída ou aguardar um evento leva a uma espera. Quando o evento ocorre, a execução volta a ser elegível; ela não salta obrigatoriamente direto para a CPU.

Esperar não é o mesmo que executar lentamente

Considere o indexador solicitando um bloco ao armazenamento. Depois de iniciar a operação, continuar ocupando a CPU apenas para verificar repetidamente se o bloco chegou seria desperdício em muitos cenários. O sistema pode marcar a unidade como aguardando e permitir que outro trabalho use o processador.

Quando o dispositivo, temporizador ou outro produtor sinaliza conclusão, o sistema atualiza a condição para pronta. Ainda pode haver espera na fila até que a política de escalonamento a selecione. Assim, “o evento terminou” e “o processo voltou a executar” são momentos diferentes.

Algumas esperas podem ser interrompidas por sinais ou cancelamentos; outras possuem regras mais restritas porque o kernel precisa concluir uma operação. Os nomes concretos importam ao diagnosticar. No Linux, por exemplo, /proc/<pid>/stat pode exibir R para executando ou executável, S para espera interrompível, D para espera não interrompível, T para parada e Z para zumbi, entre outros estados dependentes de versão.

Isso não é uma tradução individual do modelo de cinco caixas. R reúne “executando” e “pronto” na visão exposta; Z representa uma etapa de término ainda não coletada. Já no Windows, estados como Running, Ready e Waiting pertencem de forma central às threads que o dispatcher agenda.

Contexto é o necessário para continuar corretamente

Uma CPU mantém registradores usados pela instrução atual: contador de programa, ponteiro de pilha, resultados intermediários, flags e outros dados definidos pela arquitetura. Quando o sistema interrompe uma unidade para executar outra, precisa preservar informações suficientes para que a primeira continue mais tarde como se a pausa tivesse acontecido no ponto correto.

Esse conjunto é o contexto de execução. Dependendo da transição, ele pode envolver:

  • registradores gerais e contador da próxima instrução;
  • ponteiro e conteúdo relevante da pilha;
  • modo e estado do processador;
  • referências ao espaço de endereçamento e às tabelas necessárias;
  • metadados do escalonador, sinais ou exceções pendentes;
  • estado estendido, como registradores vetoriais, quando aplicável.
Linha de troca de contexto na qual a execução A é interrompida, seu contexto é salvo, o contexto B é restaurado e B continua
Salvar e restaurar preserva a continuidade lógica. O conjunto exato depende da arquitetura, do sistema e de a troca ocorrer entre threads do mesmo processo ou de processos diferentes.

Uma troca de contexto ocorre quando a execução ativa dá lugar a outra. O sistema salva o contexto A em estruturas protegidas, escolhe uma unidade pronta, restaura o contexto B e devolve o controle no ponto associado a B. A escolha é assunto da aula de escalonamento; aqui importa a preservação.

Trocar entre threads do mesmo processo pode conservar o mesmo espaço virtual; trocar entre processos pode exigir mudar o contexto de endereçamento. Em ambos os casos existe trabalho administrativo. Efeitos indiretos em caches, TLB e preditores podem acrescentar custo, mas não existe um valor universal: arquitetura, carga, afinidade e tipo de troca influenciam o resultado.

Nem toda passagem ao kernel é uma troca para outro processo. Uma chamada de sistema pode entrar em modo privilegiado e retornar à mesma thread. Da mesma forma, uma interrupção pode ser tratada sem que o escalonador escolha outra unidade. Mudança de modo e troca de contexto são conceitos relacionados, mas não sinônimos.

Término ainda produz informação

Um processo pode terminar ao retornar de sua função principal, chamar uma operação de saída, sofrer uma falha ou receber uma solicitação cuja ação seja encerrar. O sistema fecha ou libera recursos segundo os contratos aplicáveis e registra um status de término para quem precisa acompanhar o resultado.

Em sistemas Unix-like, o pai pode usar wait() ou waitpid() para obter mudanças de estado e coletar o término de um filho. Se o filho encerrou, mas essa informação ainda não foi coletada, permanece uma entrada mínima chamada zumbi, contendo PID, status e dados necessários. Zumbi não é um processo continuando a executar código nem uma cópia completa ocupando toda a memória anterior.

Se o pai não coleta filhos terminados, entradas podem se acumular. Quando o pai encerra, mecanismos do sistema adotam ou encaminham esses descendentes conforme a implementação. Evite decorar “sempre vai para o PID 1” como regra universal: sub-reapers, namespaces e gerenciadores podem participar.

No Windows, handles mantidos por outros componentes podem conservar o objeto de processo depois que sua execução termina. Assim como no modelo Unix, “não está mais executando” e “toda informação já desapareceu” não são necessariamente o mesmo instante.

Observe sem confundir fotografia com história

No PowerShell, esta consulta somente leitura mostra uma amostra de processos e seus relacionamentos de criação:

Get-CimInstance Win32_Process |
  Select-Object -First 5 ProcessId, ParentProcessId, Name

Os valores mudam a cada execução. A listagem é uma fotografia: o processo pode terminar logo depois e o PID pode ser reutilizado. A ordem sem classificação explícita também não comunica cronologia.

Em um Linux com a implementação usual de ps, uma observação equivalente pode começar com:

ps -eo pid,ppid,state,comm | head

state representa o estado exposto pela ferramenta naquele instante, e comm é um nome curto, não uma prova da origem do executável. Permissões e políticas podem ocultar informações. Antes de automatizar, consulte o manual da plataforma e escolha formato estável em vez de analisar colunas feitas para leitura humana.

Erros comuns

  • “Programa e processo são a mesma coisa.” Um é descrição persistente; o outro é uma ocorrência acompanhada em execução.
  • “Dois processos do mesmo programa compartilham todos os dados.” Cada processo possui contexto próprio; compartilhamento exige mecanismos e regras específicos.
  • “PID identifica para sempre a mesma aplicação.” Identificadores podem ser reutilizados e ter escopo diferente em namespaces.
  • exec sempre cria outro processo.” Em POSIX, um exec bem-sucedido substitui a imagem do processo chamador e preserva seu PID.
  • “Pronto significa executando.” Pronto significa elegível e aguardando seleção.
  • “Esperando consome uma CPU para ficar parado.” Uma espera bloqueante normalmente libera o processador para outro trabalho.
  • “Estado de processo é idêntico em todo sistema.” O modelo didático e as categorias expostas por cada implementação não têm correspondência perfeita.
  • “Toda interrupção troca o processo.” O sistema pode tratar o evento e retomar a mesma execução.
  • “Troca de contexto é carregar outro programa.” Ela restaura outra continuidade; as duas unidades podem pertencer ao mesmo programa ou processo.
  • “Zumbi ainda está executando.” Ele já terminou; resta informação mínima aguardando coleta.
  • “O nome na lista prova qual arquivo está executando.” Nome curto, caminho, credenciais, horário e objeto observado precisam ser correlacionados.

O que você deve guardar

Programa é uma descrição; processo é uma ocorrência com identidade temporária, imagem, memória, recursos, proteção e unidades de execução. O mesmo programa pode originar vários processos independentes. PID localiza uma ocorrência no contexto atual, mas pode ser reutilizado.

Novo, pronto, executando, esperando e terminado formam um modelo útil para acompanhar o ciclo de vida. Sistemas reais refinam esse modelo e frequentemente escalonam threads. Esperar por um evento retira a unidade da disputa imediata pela CPU; concluir o evento a torna pronta, não necessariamente executando.

Troca de contexto preserva o ponto da execução interrompida e restaura o de outra. Ela custa trabalho e pode afetar estruturas do processador, mas não deve ser confundida com mudança de modo ou carregamento de outro programa. Ao término, o sistema ainda pode manter status e identidade mínima até a coleta apropriada.

Na próxima aula, abriremos o processo para entender como múltiplas threads compartilham recursos e por que concorrência, paralelismo e assincronia descrevem relações diferentes.

Referências