College Online
0%

Estados de um Processo

Modulo 2 · Aula 2 ~20 min de leitura Nivel: Fundamental

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:

Diagrama — Maquina de Estados de Processo
                    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:

i
Observacao importante Um processo nunca vai diretamente de Waiting para Running. Quando o I/O completa, o processo volta para a fila Ready e espera o escalonador seleciona-lo novamente. Isso e uma fonte frequente de erro em provas.

Estados no Linux

O Linux implementa estados mais granulares. O campo state na task_struct pode assumir os seguintes valores:

C — linux/sched.h (simplificado)
/* 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.
 */
!
TASK_UNINTERRUPTIBLE (estado D) Processos neste estado aparecem como 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:

Shell
# 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.

Diagrama — Context Switch
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)
C — Context switch simplificado
// 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:

Diagrama — Filas de Processos
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

C
#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;
}
*
Experimento pratico Compile e execute o programa. Em outro terminal, rode 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.

C — Criando um zumbi
#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:

Shell — Medindo context switches
# 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:

Diagrama — Estados de Sessao no harness.os
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
Python — Sessao como processo com estados
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

  1. Compile e execute o programa de estados. Em outro terminal, monitore as transicoes com ps.
  2. Crie um programa que gera um processo zumbi por 10 segundos. Verifique com ps aux | grep Z.
  3. No harness.os, o que seria um "processo orfao"? O que acontece quando um agente orquestrador termina antes dos agentes que ele delegou?
  4. Meca context switches/segundo com vmstat e compare antes e durante um make -j$(nproc).

Resumo

Verifique seu entendimento

No Linux, o que significa um processo com estado D na saida do ps?

  • O processo esta em modo debug (ptrace)
  • O processo esta em sleep ininterruptivel (I/O critico), nao pode receber sinais
  • O processo esta morto (dead) e sera removido em breve
  • O processo esta em segundo plano (daemon)

Verifique seu entendimento

Qual transicao de estado e IMPOSSIVEL no modelo de 5 estados?

  • Running → Ready (preempcao)
  • Waiting → Running (sem passar por Ready)
  • Ready → Running (dispatch)
  • Running → Terminated (exit)