Trilha 08 · Sistema operacional

Escalonamento de CPU

Com múltiplos processos prontos e uma CPU, quem executa? O escalonador decide — e a escolha afeta profundamente a responsividade do sistema e o tempo médio de espera de todos os processos.

① Intuição

O problema do caixa único

Imagine uma fila de banco com 5 clientes, cada um com tempo de atendimento diferente, e apenas um caixa. Em que ordem atender? FCFS (ordem de chegada) é justo mas pode ser lento. SJF (mais rápido primeiro) minimiza a espera média mas pode ser injusto. Round Robin dá vez a todos em turnos — ninguém espera muito.

CPUs modernas têm múltiplos núcleos, mas há sempre mais processos prontos que núcleos disponíveis. O escalonador decide quem usa a CPU e por quanto tempo (quantum), com preempção — pode interromper um processo para dar CPU a outro.

Preemptivo vs. cooperativo: em sistemas cooperativos (Windows 3.x, macOS clássico), um processo só larga a CPU voluntariamente — um processo mal-comportado poderia travar o sistema. Em sistemas preemptivos (Linux, Windows NT+, macOS moderno), um timer de hardware interrompe o processo após o quantum e força a troca — garantindo que nenhum processo monopolize a CPU.
② Visualização interativa

Simulador de escalonamento

Ajuste os burst times dos processos, selecione o algoritmo e veja o diagrama de Gantt e as métricas de espera e turnaround. Para Round Robin, defina o quantum.

First Come First Served: Processa na ordem de chegada. Simples mas pode causar convoy effect (processos curtos esperam processos longos).

P1
espera: 0u
turnaround: 5u
P2
espera: 4u
turnaround: 7u
P3
espera: 6u
turnaround: 14u
P4
espera: 13u
turnaround: 15u
P5
espera: 14u
turnaround: 20u
DIAGRAMA DE GANTT
P1
P2
P3
P4
P5
0
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
Espera média
7.4u
Turnaround médio
12.2u
Tempo total
24u

Altere os burst times e troque de algoritmo para comparar. "u" = unidade de tempo. Chegada (arrival): P1=0, P2=1, P3=2, P4=3, P5=4.

③ Explicação técnica

Linux CFS — Completely Fair Scheduler

// Linux CFS — Completely Fair Scheduler (simplificado)
// Mantém uma red-black tree de processos prontos, ordenada por vruntime.

struct sched_entity {
  u64 vruntime;   // tempo virtual de execução (ponderado por prioridade)
  int nice;       // -20 (maior prioridade) a +19 (menor)
};

// O CFS sempre escala o processo com menor vruntime
// (o que recebeu menos CPU proporcionalmente)

// nice -20: processo recebe 10× mais CPU que nice +19
// peso = prio_to_weight[nice + 20]  (tabela no kernel)

// Quanto o processo executa por vez? O "target latency"
const TARGET_LATENCY = 6ms;  // todos os procs rodam em 6ms
const MIN_GRANULARITY = 0.75ms; // mínimo para evitar thrashing

// quantum_i = TARGET_LATENCY × (peso_i / Σpesos)
// Processo de alta prioridade ganha fatia maior do latency target

// Quando um novo processo fica pronto:
if (novo.vruntime < atual.vruntime - TARGET_LATENCY) {
  preempt();  // preempção: novo processo merece CPU agora
}

Métricas de avaliação

// Métricas de escalonamento

// Para um processo P com:
//   arrival_time = 2  (chegou no instante 2)
//   burst_time   = 5  (precisa de 5 unidades de CPU)
//   finish_time  = 10 (terminou no instante 10)

const turnaround = finish_time - arrival_time;  // 10 - 2 = 8
// turnaround = tempo total desde chegada até conclusão

const waiting = turnaround - burst_time;        // 8 - 5 = 3
// waiting = tempo que o processo ficou esperando (sem usar CPU)

const response = first_cpu_time - arrival_time; // tempo até primeira execução
// response time é crítico para sistemas interativos

// Convoy effect (FCFS): processo longo atrasa todos os curtos
// P1(burst=20), P2(burst=1), P3(burst=1), todos chegam t=0
// FCFS: P2 e P3 esperam 20 unidades → waiting médio = (0+20+21)/3 = ~14
// SJF:  P2, P3, P1 → waiting médio = (0+1+2)/3 = 1
Inversão de prioridade: um processo de baixa prioridade segura um mutex que um processo de alta prioridade precisa. O processo de alta fica bloqueado enquanto o de baixa executa. Solução: priority inheritance — o processo de baixa prioridade temporariamente herda a prioridade do processo que está esperando. O bug clássico aconteceu no Mars Pathfinder em 1997 — resolvido reiniciando o rover com priority inheritance ativado.
④ Projeto para programar

Implementando um escalonador

Mini projeto: implemente FCFS e Round Robin (quantum=2) em Python. Receba uma lista de processos [(id, arrival, burst)] e retorne o Gantt chart como lista de (pid, start, end), mais as métricas de waiting time e turnaround médios. Valide com o simulador acima.

Projeto principal: implemente o algoritmo do Banqueiro (Banker's Algorithm) em Python: dado um estado de alocação de recursos (allocated, max, available), implemente is_safe_state() que retorna se o estado atual é seguro (existe sequência de execução que permite todos os processos terminarem). Teste com os cenários do simulador de deadlock.

Desafio extra: use chrt (Linux) para rodar um programa com política de escalonamento de tempo real: chrt -f 50 ./meu_prog (SCHED_FIFO, prioridade 50). Compare a jitter de timing (variação no tempo de execução de um loop de 1ms) entre SCHED_FIFO e o CFS padrão com clock_gettime(CLOCK_MONOTONIC).

⑤ Exercícios rápidos

Teste sua intuição

Qual é o principal problema do algoritmo FCFS?
Como o tamanho do quantum afeta o Round Robin?
Por que SJF raramente é implementado puro em SOs reais?
⑥ Aplicações no mundo real

Onde você encontra isso

🐧

Linux CFS e grupos de controle

O CFS usa cgroups para limitar quanto de CPU um grupo de processos pode usar. Docker e Kubernetes usam isso para garantir que um container não monopolize a CPU: cpu.shares define peso relativo, cpu.cfs_quota_us define o limite absoluto em microssegundos por período. Um pod com limits.cpu: 0.5 recebe no máximo 50% de um núcleo.

🎮

Sistemas de tempo real

Sistemas embarcados como airbags, controle de voo e equipamentos médicos usam RTOS (Real-Time OS) com SCHED_FIFO ou EDF (Earliest Deadline First). Garantem que tarefas críticas executem em prazo determinístico — latência máxima garantida, não apenas média. Linux com patch PREEMPT_RT pode atingir latências <50μs.

☁️

Escalonamento de VMs na nuvem

Hypervisors como KVM e Xen implementam um segundo nível de escalonamento: além do escalonador do SO guest, o hypervisor decide quando cada VM recebe ciclos de CPU do host. Em períodos de alta carga, VMs "roubam" (steal time) tempo umas das outras — visível com top na coluna %st.

← Anterior: Processos e estados Próxima: Memória virtual →