College Online
0%

Escalonamento no Linux (CFS)

Modulo 3 · Aula 3 ~15 min de leitura Nivel: Intermediario

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.

Diagrama — CFS: Red-Black Tree
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.

Shell — Nice values
# 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
i
vruntime ponderado 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).

Shell — cgroups na pratica
# 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
*
Scheduling Classes no Linux O Linux tem 3 classes de escalonamento: SCHED_FIFO e SCHED_RR (tempo real, prioridade estatica 1-99) e SCHED_NORMAL (CFS, prioridade dinamica via nice). Processos de tempo real SEMPRE tem prioridade sobre processos normais. Use 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:

Shell — Observando vruntime
# 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)
C — Definindo prioridade de escalonamento
/* 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, &param) == -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.

Diagrama — CFS vs EEVDF
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:

Diagrama — cgroups para MCP servers
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.
Python — Nice values para prioridade de agentes
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

  1. Execute dois processos CPU-bound simultaneamente (loops infinitos). Use nice para dar um nice=-5 e outro nice=10. Observe com top como o %CPU se distribui.
  2. Crie um cgroup que limita CPU a 25% e coloque um processo nele. Verifique com top.
  3. 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

Verifique seu entendimento

No CFS, qual processo e selecionado para rodar a seguir?

  • O processo com maior prioridade estatica
  • O processo com menor vruntime (que recebeu menos CPU ate agora)
  • O processo que esta esperando ha mais tempo na fila
  • O processo com menor burst estimado (SJF)