Escalonamento no Linux (CFS)
Video da aula estara disponivel em breve
Completely Fair Scheduler (CFS)
O CFS e o escalonador padrao do Linux desde o kernel 2.6.23 (2007), criado por Ingo Molnar. A ideia central e simples: dividir o tempo de CPU igualmente entre todos os processos. O processo que recebeu menos CPU ate agora e o proximo a rodar.
O CFS rastreia o vruntime (virtual runtime) de cada processo — o tempo "virtual" de CPU que ele consumiu, ponderado pela prioridade. O processo com menor vruntime sempre roda primeiro.
CFS usa uma Red-Black Tree ordenada por vruntime:
[P3: vruntime=15]
/ \
[P1: vruntime=10] [P5: vruntime=20]
/ \ \
[P4: vr=5] [P2: vr=12] [P6: vr=25]
Proximo a rodar: P4 (menor vruntime = mais a esquerda)
Operacoes:
- Selecionar proximo: O(1) — elemento mais a esquerda, cached
- Inserir/remover: O(log n) — red-black tree balanceada
- Nenhuma fila, nenhum quantum fixo
Quando P4 roda, seu vruntime aumenta.
Eventualmente, outro processo tera o menor vruntime.
Nice Values e Pesos
O CFS usa nice values (-20 a +19) para ponderar o vruntime. Nice menor = maior prioridade = vruntime cresce mais devagar = recebe mais CPU.
# Executar com nice value especifico
$ nice -n 10 ./meu_programa # menor prioridade
$ nice -n -5 ./meu_programa # maior prioridade (requer root)
# Alterar nice de um processo rodando
$ renice -n 5 -p 1234
# Ver nice de todos os processos
$ ps -eo pid,ni,comm | head
PID NI COMMAND
1 0 systemd
567 0 bash
789 10 backup_script
# Nice value -> peso (tabela do kernel):
# nice -20 = peso 88761 (mais CPU)
# nice 0 = peso 1024 (default)
# nice +19 = peso 15 (menos CPU)
# Cada nivel de nice muda ~10% de CPU relativa
vruntime += tempo_real * (peso_nice_0 / peso_processo). Um processo com nice -5 (peso maior) tem seu vruntime incrementado mais devagar, entao fica "a esquerda" na arvore por mais tempo, recebendo mais CPU. Um processo com nice 15 (peso menor) tem vruntime que cresce rapido, recebendo menos CPU.
cgroups: Limites de Recursos
cgroups (control groups) permitem limitar, contabilizar, e isolar recursos (CPU, memoria, I/O) de grupos de processos. Sao a base da containerizacao (Docker, Kubernetes).
# Ver cgroups do processo atual
$ cat /proc/self/cgroup
0::/user.slice/user-1000.slice/session-1.scope
# Limitar CPU de um processo a 50% de um core (cgroups v2)
$ sudo mkdir /sys/fs/cgroup/limited
$ echo "50000 100000" | sudo tee /sys/fs/cgroup/limited/cpu.max
# 50000us de cada 100000us = 50% de CPU
# Mover um processo para o cgroup
$ echo 1234 | sudo tee /sys/fs/cgroup/limited/cgroup.procs
# Limitar memoria a 256MB
$ echo "268435456" | sudo tee /sys/fs/cgroup/limited/memory.max
# Docker usa cgroups automaticamente:
$ docker run --cpus="0.5" --memory="256m" nginx
# Isso cria um cgroup com cpu.max e memory.max configurados
chrt -p PID para ver a classe de um processo.
Observando o CFS na Pratica
Voce pode observar o comportamento do CFS diretamente em /proc e com ferramentas do sistema:
# Ver estatisticas do escalonador
$ cat /proc/sched_debug | head -30
# Ver vruntime de um processo especifico
$ cat /proc/1234/sched | grep vruntime
se.vruntime : 1284567.890123
# Ver a granularidade minima (latency target)
$ cat /proc/sys/kernel/sched_min_granularity_ns
3000000 # 3ms — tempo minimo que um processo roda antes de preempcao
# Ver o periodo de escalonamento (latency)
$ cat /proc/sys/kernel/sched_latency_ns
24000000 # 24ms — periodo alvo para todos os processos rodarem
# Se ha 8 processos: 24ms / 8 = 3ms cada (= min_granularity)
# Se ha 4 processos: 24ms / 4 = 6ms cada (acima do minimo)
/* Exemplo de como definir politica e prioridade de escalonamento */
#include <sched.h>
#include <stdio.h>
#include <stdlib.h>
int main() {
struct sched_param param;
int policy;
/* Verificar politica atual */
policy = sched_getscheduler(0); /* 0 = processo atual */
printf("Politica atual: %s\n",
policy == SCHED_OTHER ? "SCHED_OTHER (CFS)" :
policy == SCHED_FIFO ? "SCHED_FIFO (RT)" :
policy == SCHED_RR ? "SCHED_RR (RT)" : "desconhecida");
/* Mudar para SCHED_FIFO com prioridade 50 (requer root) */
param.sched_priority = 50;
if (sched_setscheduler(0, SCHED_FIFO, ¶m) == -1) {
perror("sched_setscheduler");
return 1;
}
printf("Agora rodando com SCHED_FIFO, prioridade %d\n",
param.sched_priority);
/* Este processo agora tem prioridade sobre TODOS os processos CFS */
return 0;
}
EEVDF: O Futuro do Linux
A partir do kernel 6.6 (2023), o Linux esta migrando do CFS para o EEVDF (Earliest Eligible Virtual Deadline First). O EEVDF adiciona o conceito de deadline virtual ao vruntime, melhorando a latencia para processos interativos sem sacrificar fairness.
CFS: Seleciona por menor vruntime apenas
Processo interativo pode ter que esperar
se muitos processos estao na arvore.
EEVDF: Cada processo recebe uma "virtual deadline"
Seleciona o processo elegivel com menor deadline
Processos interativos (curtos bursts de CPU)
recebem deadlines mais proximas = menor latencia
Elegivel = vruntime <= vruntime medio do sistema
Deadline = vruntime + (latency_target / peso)
Resultado: audio/video/input responsivos mesmo sob carga.
No harness.os
Os cgroups sao diretamente relevantes para o harness.os, pois MCP servers rodam como processos no sistema operacional:
Sistema Operacional
+------------------------------------------------------+
| cgroup: /harness |
| cpu.max: 200000 100000 (2 cores max) |
| memory.max: 1G |
| |
| +------------------+ +-------------------+ |
| | cgroup: /harness | | cgroup: /harness | |
| | /mcp-server | | /neon-proxy | |
| | cpu: 100% 1 core | | cpu: 50% 1 core | |
| | mem: 512M | | mem: 256M | |
| | | | | |
| | MCP Server | | Neon Proxy | |
| | (Node.js) | | (connection pool) | |
| +------------------+ +-------------------+ |
| |
| +------------------+ |
| | cgroup: /harness | |
| | /agents | |
| | cpu: ilimitado | |
| | mem: 256M | |
| | | |
| | Claude CLI | |
| | (agente ativo) | |
| +------------------+ |
+------------------------------------------------------+
CFS + cgroups = fairness + limites de recurso
Mesmo modelo que Docker/K8s usa para containers.
import os
def run_agent_with_priority(task_type, command):
"""Ajusta nice value do agente baseado no tipo de tarefa."""
nice_values = {
"hotfix": -10, # alta prioridade (mais CPU)
"feature": 0, # prioridade normal
"refactor": 10, # baixa prioridade (background)
"docs": 15, # prioridade minima
}
nice = nice_values.get(task_type, 0)
os.nice(nice)
os.execvp(command[0], command)
Homework
- Execute dois processos CPU-bound simultaneamente (loops infinitos). Use
nicepara dar um nice=-5 e outro nice=10. Observe comtopcomo o %CPU se distribui. - Crie um cgroup que limita CPU a 25% e coloque um processo nele. Verifique com
top. - No harness.os, se voce tivesse 3 MCP servers rodando (harness, build.ai, cortex.ai), como distribuiria CPU entre eles usando cgroups? Justifique os pesos.
Resumo
- CFS: escalonador padrao do Linux, baseado em vruntime e red-black tree
- Nice values (-20 a +19) ponderam o vruntime: menor nice = mais CPU
- cgroups limitam CPU, memoria, I/O para grupos de processos
- cgroups sao a base de Docker e Kubernetes
- EEVDF e o sucessor do CFS a partir do kernel 6.6
Verifique seu entendimento
No CFS, qual processo e selecionado para rodar a seguir?