Estados de um Processo
Video da aula estara disponivel em breve
O Modelo de 5 Estados
Durante sua vida, um processo passa por diferentes estados. O modelo classico define 5 estados:
admitido dispatch
+-----+ +-------+ +-------+ +---------+
| New |--->| Ready |---->|Running|---->|Terminated|
+-----+ +-------+ +-------+ +---------+
^ ^ | |
| | | |
timeout/ | | wait | | exit
preempt | | (I/O) | |
| | | |
| | +----v--+
| +----| Wait- |
| | ing |
+--------+------+
I/O complete
Transicoes:
New --[admitido]--> Ready (SO aceita o processo)
Ready --[dispatch]--> Running (escalonador escolhe)
Running --[preempt]---> Ready (quantum expirou)
Running --[I/O wait]--> Waiting (processo pede I/O)
Running --[exit]------> Terminated(processo termina)
Waiting --[I/O done]--> Ready (I/O completou)
Cada transicao corresponde a um evento especifico:
- New → Ready: o SO admitiu o processo (alocou PCB, memoria, recursos iniciais)
- Ready → Running: o escalonador selecionou este processo para usar a CPU
- Running → Ready: o quantum de tempo expirou (preempcao) ou um processo de maior prioridade chegou
- Running → Waiting: o processo pediu uma operacao de I/O ou aguarda um evento
- Waiting → Ready: a operacao de I/O completou ou o evento esperado ocorreu
- Running → Terminated: o processo terminou (exit, erro fatal, killed)
Estados no Linux
O Linux implementa estados mais granulares. O campo state na task_struct pode assumir os seguintes valores:
/* Estados de processo no Linux kernel */
#define TASK_RUNNING 0x0000 // Ready OU Running
#define TASK_INTERRUPTIBLE 0x0001 // Waiting (acordavel por sinal)
#define TASK_UNINTERRUPTIBLE 0x0002 // Waiting (NAO pode ser interrompido)
#define __TASK_STOPPED 0x0004 // Parado por sinal (SIGSTOP)
#define __TASK_TRACED 0x0008 // Sendo rastreado (ptrace/debugger)
#define EXIT_DEAD 0x0010 // Terminado, recursos liberados
#define EXIT_ZOMBIE 0x0020 // Terminado, aguardando wait() do pai
/*
* Nota: TASK_RUNNING cobre tanto "ready" quanto "running".
* O Linux nao diferencia os dois no campo state.
* Se o processo esta na CPU, esta running.
* Se esta na run queue, esta ready.
* Ambos sao TASK_RUNNING.
*/
D no ps/top. Nao podem ser mortos nem com kill -9. Isso acontece tipicamente durante I/O de disco critico. Se um servidor NFS fica inacessivel, processos podem ficar presos em "D" state indefinidamente — um problema classico em ambientes de producao.
Voce pode observar os estados diretamente no terminal:
# Ver estados de todos os processos
$ ps aux
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
root 1 0.0 0.1 1234 456 ? Ss 10:00 0:01 /sbin/init
marco 567 0.0 0.2 5678 890 pts/0 S 10:05 0:00 bash
marco 789 25.0 1.0 9876 1234 pts/0 R+ 10:10 1:23 ./meu_programa
root 101 0.0 0.0 456 78 ? D 10:01 0:00 [kworker/0:1]
# Coluna STAT:
# R = Running / Runnable (TASK_RUNNING)
# S = Sleeping / Interruptible (TASK_INTERRUPTIBLE)
# D = Disk sleep / Uninterruptible (TASK_UNINTERRUPTIBLE)
# T = Stopped (SIGSTOP)
# Z = Zombie (EXIT_ZOMBIE)
# Modificadores: + (foreground), s (session leader), l (multi-threaded)
# Ver info detalhada via /proc
$ cat /proc/789/status | grep State
State: R (running)
Context Switch (Troca de Contexto)
O context switch e o mecanismo pelo qual o SO salva o estado do processo atual e restaura o estado do proximo processo a executar. E o que permite a multitarefa.
Processo A (running) Kernel Processo B (ready)
| | |
| [timer interrupt] | |
|------------------------>| |
| | |
| 1. Salvar estado A | |
| (registradores, PC, | |
| stack pointer, flags) | |
| no PCB_A | |
| | |
| 2. Atualizar estado: | |
| A: running->ready | |
| B: ready->running | |
| | |
| 3. Restaurar estado B | |
| do PCB_B | |
| |--------------------->|
| | |
| | B continua de |
| | onde parou |
Tempo de context switch: ~1-10 microsegundos
Custo indireto: TLB flush, cache misses (muito maior)
// Pseudo-codigo de um context switch
void context_switch(struct pcb *old, struct pcb *new) {
// 1. Salvar registradores do processo atual
save_registers(&old->registers);
old->pc = get_program_counter();
old->stack_ptr = get_stack_pointer();
old->state = READY;
// 2. Trocar espaco de enderecamento (tabela de paginas)
switch_page_table(new->page_table);
// 3. Restaurar registradores do novo processo
restore_registers(&new->registers);
set_program_counter(new->pc);
set_stack_pointer(new->stack_ptr);
new->state = RUNNING;
// 4. Retornar — CPU agora executa codigo de 'new'
}
Filas de Escalonamento
O SO mantem diversas filas para organizar processos por estado:
Ready Queue (fila de prontos):
+-----+ +-----+ +-----+ +-----+
| P3 |--->| P7 |--->| P2 |--->| P9 |---> NULL
+-----+ +-----+ +-----+ +-----+
Device Queues (uma por dispositivo):
Disco: +-----+ +-----+
| P1 |--->| P5 |---> NULL
+-----+ +-----+
Rede: +-----+
| P4 |---> NULL
+-----+
CPU: +-----+
| P11 | (apenas 1 por core)
+-----+
Observando Estados na Pratica
#include <stdio.h>
#include <unistd.h>
#include <sys/wait.h>
int main() {
pid_t pid = fork();
if (pid == 0) {
// Filho: demonstra transicoes de estado
// Estado: RUNNING (executando na CPU)
printf("[R] Filho rodando, PID=%d\n", getpid());
// Transicao: Running -> Waiting (I/O simulado)
printf("[S] Filho vai dormir...\n");
sleep(3); // TASK_INTERRUPTIBLE por 3 segundos
// Transicao: Waiting -> Ready -> Running
printf("[R] Filho acordou e rodando novamente\n");
// CPU-bound: fica em RUNNING
volatile long i;
for (i = 0; i < 500000000L; i++);
printf("[X] Filho terminando\n");
return 42;
} else {
printf("Pai: filho criado (PID=%d)\n", pid);
printf("Execute: watch -n 0.5 cat /proc/%d/status\n", pid);
int status;
waitpid(pid, &status, 0);
if (WIFEXITED(status))
printf("Pai: filho saiu com codigo %d\n", WEXITSTATUS(status));
}
return 0;
}
watch -n 0.5 "ps -o pid,stat,comm -p PID" para ver as transicoes de estado em tempo real.
Processos Zumbi e Orfaos
Dois casos especiais importantes:
Processo zumbi: terminou mas o pai ainda nao chamou wait(). O kernel mantem o PCB ate que o pai colete o exit status.
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
int main() {
pid_t pid = fork();
if (pid == 0) {
printf("Filho (PID=%d) terminando...\n", getpid());
exit(0);
// Agora e um ZOMBIE ate o pai chamar wait()
} else {
printf("Pai dormindo 30s sem wait()...\n");
printf("Execute: ps aux | grep Z\n");
sleep(30);
}
return 0;
}
Processo orfao: o pai terminou antes do filho. O filho e adotado por init/systemd (PID 1), que chama wait() automaticamente quando o orfao terminar.
Custo do Context Switch
O context switch e puro overhead — nenhum trabalho util e feito. Os custos:
- Direto: salvar/restaurar registradores (~1-10 us)
- Indireto: TLB flush, cache misses (frequentemente muito maior que o custo direto)
- Pipeline flush: instrucoes em pipeline descartadas
# Context switches de um processo
$ cat /proc/self/status | grep ctxt
voluntary_ctxt_switches: 150
nonvoluntary_ctxt_switches: 3
# voluntary: processo cedeu CPU (I/O)
# nonvoluntary: quantum expirou (preempcao)
# Context switches do sistema inteiro
$ vmstat 1 5
# Coluna 'cs' = context switches/segundo
No harness.os
O ciclo de vida de uma sessao no harness.os mapeia diretamente para o diagrama de estados de processo:
Estados de Processo (SO) Estados de Sessao (harness.os)
======================== ================================
New ----------------> start_session() chamado
| session_id gerado, handoff carregado
v
Ready --------------> 'active' com contexto carregado
| knowledge, rules, workflows prontos
v
Running ------------> Agente executando trabalho
| log_decision(), log_learning()
|
|-- I/O wait ------> Esperando input do usuario
| (Waiting) ou esperando outro agente
|
|-- preempt -------> Compaction do contexto
| (Ready) agente "perde a CPU" por token limit
|
v
Terminated ---------> end_session(summary, next_steps)
handoff escrito para proxima sessao
Context Switch = Handoff entre sessoes
Salvar: end_session() grava summary + next_steps
Restaurar: start_session() carrega ultimo handoff
class SessionState:
NEW = "new" # session criada, sem contexto
READY = "active" # contexto carregado, pronto
RUNNING = "running" # agente executando trabalho
WAITING = "waiting" # esperando input externo
DONE = "completed" # end_session chamado
class SessionPCB:
"""Equivalente do PCB para uma sessao."""
def __init__(self, project_slug):
self.session_id = uuid4() # PID
self.state = SessionState.NEW # estado
self.project_slug = project_slug # "programa"
self.context_loaded = [] # "registradores"
self.token_usage = 0 # "tempo de CPU"
def context_switch_out(self):
"""Salva estado para handoff."""
return {
"summary": self.generate_summary(),
"next_steps": self.pending_tasks,
"token_usage": self.token_usage,
}
Assim como um zumbi ocupa espaco na tabela de processos, uma sessao sem end_session() fica como "sessao zumbi" — sem handoff, a proxima sessao nao sabe o que aconteceu.
Homework
- Compile e execute o programa de estados. Em outro terminal, monitore as transicoes com
ps. - Crie um programa que gera um processo zumbi por 10 segundos. Verifique com
ps aux | grep Z. - No harness.os, o que seria um "processo orfao"? O que acontece quando um agente orquestrador termina antes dos agentes que ele delegou?
- Meca context switches/segundo com
vmstate compare antes e durante ummake -j$(nproc).
Resumo
- 5 estados: New, Ready, Running, Waiting, Terminated
- Waiting nunca vai direto para Running (sempre passa por Ready)
- Linux: TASK_RUNNING = Ready + Running; estado D = uninterruptible sleep
- Context switch salva/restaura PCB; custo indireto (cache) domina
- Zumbi: terminado sem wait(). Orfao: pai morreu, adotado por init.
Verifique seu entendimento
No Linux, o que significa um processo com estado D na saida do ps?
Verifique seu entendimento
Qual transicao de estado e IMPOSSIVEL no modelo de 5 estados?