O escalonamento de processos é uma função fundamental de qualquer sistema operacional, determinando a ordem e a alocação de tempo de CPU para cada processo. No Linux, o escalonador (scheduler) é parte do kernel e evoluiu ao longo do tempo, com destaque para o Completely Fair Scheduler (CFS) introduzido na versão 2.6.23, que busca equilibrar justiça e desempenho.
Exemplo: No Linux, podemos simular prioridades com o comando nice -n 10 comando
para reduzir a prioridade de um processo, influenciando sua execução no escalonador.
Nota: Para ilustrar, um diagrama simples mostrando a ordem de execução de processos no FCFS ou a divisão de tempo no Round Robin será explorado nas simulações.
Vamos criar simulações dos algoritmos em Python (ou C, opcionalmente) para entender seu comportamento com diferentes cargas de trabalho, simulando o escalonamento de processos no Linux.
def fcfs(processes):
# Ordena por tempo de chegada
processes.sort(key=lambda x: x['arrival'])
current_time = 0
for p in processes:
if current_time < p['arrival']:
current_time = p['arrival']
p['start'] = current_time
p['finish'] = current_time + p['burst']
p['wait'] = current_time - p['arrival']
p['turnaround'] = p['finish'] - p['arrival']
current_time = p['finish']
return processes
def print_results(processes):
total_wait = 0
total_turnaround = 0
print("\n{:^7} {:^10} {:^12} {:^10} {:^10} {:^10} {:^10} {:^10}".format(
"PID", "Chegada", "Execução", "Prioridade", "Início", "Término", "Espera", "Retorno"
))
print("-" * 80)
for p in processes:
print("{:^7} {:^10} {:^12} {:^10} {:^10} {:^10} {:^10} {:^10}".format(
p['pid'],
p['arrival'],
p['burst'],
"-", # Prioridade não se aplica ao FCFS
p['start'],
p['finish'],
p['wait'],
p['turnaround']
))
total_wait += p['wait']
total_turnaround += p['turnaround']
print("\nMédia Espera: {:.2f}".format(total_wait / len(processes)))
print("Média Retorno: {:.2f}".format(total_turnaround / len(processes)))
# Exemplo de uso
if __name__ == "__main__":
processes = [
{'pid': 'P1', 'arrival': 0, 'burst': 5},
{'pid': 'P2', 'arrival': 1, 'burst': 3},
{'pid': 'P3', 'arrival': 2, 'burst': 8}
]
result = fcfs(processes)
print_results(result)
Explicação: O FCFS executa processos na ordem de chegada (arrival). Para cada processo, calculamos o início (start), fim (finish), espera (wait = início - chegada) e retorno (turnaround = fim - chegada). O tempo avança conforme os processos terminam, sem preempção.
#include <stdio.h>
typedef struct { char pid[3]; int arrival; int burst; int start; int finish; int wait; int turnaround; } Process;
void fcfs(Process *processes, int n) {
int current_time = 0;
for (int i = 0; i < n; i++) {
if (current_time < processes[i].arrival) current_time = processes[i].arrival;
processes[i].start = current_time;
processes[i].finish = current_time + processes[i].burst;
processes[i].wait = current_time - processes[i].arrival;
processes[i].turnaround = processes[i].finish - processes[i].arrival;
current_time = processes[i].finish;
}
}
int main() {
Process processes[] = { {"P1", 0, 5, 0, 0, 0, 0}, {"P2", 1, 3, 0, 0, 0, 0}, {"P3", 2, 8, 0, 0, 0, 0} };
int n = sizeof(processes) / sizeof(processes[0]);
fcfs(processes, n);
printf("PID\tEspera\tRetorno\n");
for (int i = 0; i < n; i++) printf("%s\t%d\t%d\n", processes[i].pid, processes[i].wait, processes[i].turnaround);
return 0;
}
def sjf(processes):
processes.sort(key=lambda x: x['arrival'])
current_time = 0
completed = []
ready_queue = []
while processes or ready_queue:
while processes and processes[0]['arrival'] <= current_time:
ready_queue.append(processes.pop(0))
if ready_queue:
ready_queue.sort(key=lambda x: x['burst'])
current = ready_queue.pop(0)
current['start'] = current_time
current['finish'] = current_time + current['burst']
current['wait'] = current_time - current['arrival']
current['turnaround'] = current['finish'] - current['arrival']
current_time = current['finish']
completed.append(current)
else:
current_time += 1
return completed
def print_results(processes):
total_wait = 0
total_turnaround = 0
print("\n{:^7} {:^10} {:^12} {:^10} {:^10} {:^10} {:^10} {:^10}".format(
"PID", "Chegada", "Execução", "Prioridade", "Início", "Término", "Espera", "Retorno"
))
print("-" * 80)
for p in processes:
print("{:^7} {:^10} {:^12} {:^10} {:^10} {:^10} {:^10} {:^10}".format(
p['pid'],
p['arrival'],
p['burst'],
"-", # Prioridade não se aplica ao SJF
p['start'],
p['finish'],
p['wait'],
p['turnaround']
))
total_wait += p['wait']
total_turnaround += p['turnaround']
print("\nMédia Espera: {:.2f}".format(total_wait / len(processes)))
print("Média Retorno: {:.2f}".format(total_turnaround / len(processes)))
# Exemplo de uso
if __name__ == "__main__":
processes = [
{'pid': 'P1', 'arrival': 0, 'burst': 5},
{'pid': 'P2', 'arrival': 1, 'burst': 3},
{'pid': 'P3', 'arrival': 2, 'burst': 8}
]
result = sjf(processes)
print_results(result)
Explicação: O SJF seleciona o processo com menor tempo de execução (burst) entre os que chegaram até o momento. Usa uma fila de prontos (ready_queue) para gerenciar processos disponíveis e calcula os tempos de espera e retorno após cada execução.
#include <stdio.h>
#include <stdlib.h>
typedef struct { char pid[3]; int arrival; int burst; int start; int finish; int wait; int turnaround; } Process;
void swap(Process *a, Process *b) { Process temp = *a; *a = *b; *b = temp; }
void sjf(Process *processes, int n) {
int current_time = 0, completed = 0;
while (completed < n) {
int shortest = -1;
for (int i = 0; i < n; i++) {
if (processes[i].arrival <= current_time && processes[i].finish == 0) {
if (shortest == -1 || processes[i].burst < processes[shortest].burst) shortest = i;
}
}
if (shortest == -1) current_time++;
else {
processes[shortest].start = current_time;
processes[shortest].finish = current_time + processes[shortest].burst;
processes[shortest].wait = current_time - processes[shortest].arrival;
processes[shortest].turnaround = processes[shortest].finish - processes[shortest].arrival;
current_time = processes[shortest].finish;
completed++;
}
}
}
int main() {
Process processes[] = { {"P1", 0, 5, 0, 0, 0, 0}, {"P2", 1, 3, 0, 0, 0, 0}, {"P3", 2, 8, 0, 0, 0, 0} };
int n = sizeof(processes) / sizeof(processes[0]);
sjf(processes, n);
printf("PID\tEspera\tRetorno\n");
for (int i = 0; i < n; i++) printf("%s\t%d\t%d\n", processes[i].pid, processes[i].wait, processes[i].turnaround);
return 0;
}
def round_robin(processes, quantum):
for p in processes:
p['remaining'] = p['burst']
p['start'] = None
queue = processes.copy()
current_time = 0
completed = []
while queue:
current = queue.pop(0)
if current['start'] is None:
current['start'] = current_time # Primeira vez que executa
if current['remaining'] > quantum:
current_time += quantum
current['remaining'] -= quantum
queue.append(current)
else:
current_time += current['remaining']
current['finish'] = current_time
current['turnaround'] = current_time - current['arrival']
current['wait'] = current['turnaround'] - current['burst']
completed.append(current)
return completed
def print_results(processes):
total_wait = 0
total_turnaround = 0
print("\n{:^7} {:^10} {:^12} {:^10} {:^10} {:^10} {:^10}".format(
"PID", "Chegada", "Execução", "Prioridade", "Término", "Espera", "Retorno"
))
print("-" * 70)
for p in processes:
print("{:^7} {:^10} {:^12} {:^10} {:^10} {:^10} {:^10}".format(
p['pid'],
p['arrival'],
p['burst'],
"-", # Prioridade não se aplica ao RR
p['finish'],
p['wait'],
p['turnaround']
))
total_wait += p['wait']
total_turnaround += p['turnaround']
print("\nMédia Espera: {:.2f}".format(total_wait / len(processes)))
print("Média Retorno: {:.2f}".format(total_turnaround / len(processes)))
# Exemplo de uso
if __name__ == "__main__":
processes = [
{'pid': 'P1', 'arrival': 0, 'burst': 5},
{'pid': 'P2', 'arrival': 1, 'burst': 3},
{'pid': 'P3', 'arrival': 2, 'burst': 8}
]
result = round_robin(processes, quantum=2)
print_results(result)
Explicação: O Round Robin usa um quantum fixo (ex.: 2). Cada processo executa por esse tempo; se não terminar, volta à fila. O tempo restante (remaining) é rastreado até completar o burst original.
#include <stdio.h>
#include <stdlib.h>
typedef struct {
char pid[3];
int arrival;
int burst;
int remaining;
int finish;
int wait;
int turnaround;
} Process;
void round_robin(Process *processes, int n, int quantum) {
int current_time = 0, completed = 0;
while (completed < n) {
for (int i = 0; i < n; i++) {
if (processes[i].arrival <= current_time && processes[i].remaining > 0) {
if (processes[i].remaining > quantum) {
current_time += quantum;
processes[i].remaining -= quantum;
} else {
current_time += processes[i].remaining;
processes[i].finish = current_time;
processes[i].turnaround = processes[i].finish - processes[i].arrival;
processes[i].wait = processes[i].turnaround - processes[i].burst;
processes[i].remaining = 0;
completed++;
}
}
}
}
}
int main() {
Process processes[] = {
{"P1", 0, 5, 5, 0, 0, 0},
{"P2", 1, 3, 3, 0, 0, 0},
{"P3", 2, 8, 8, 0, 0, 0}
};
int n = sizeof(processes) / sizeof(processes[0]);
int quantum = 2;
round_robin(processes, n, quantum);
printf("PID\tEspera\tRetorno\n");
for (int i = 0; i < n; i++) {
printf("%s\t%d\t%d\n", processes[i].pid, processes[i].wait, processes[i].turnaround);
}
return 0;
}
def priority_scheduling(processes):
processes.sort(key=lambda x: x['arrival'])
current_time = 0
completed = []
ready_queue = []
while processes or ready_queue:
while processes and processes[0]['arrival'] <= current_time:
ready_queue.append(processes.pop(0))
if ready_queue:
ready_queue.sort(key=lambda x: x['priority']) # Menor prioridade = maior prioridade
current = ready_queue.pop(0)
current['start'] = current_time
current['finish'] = current_time + current['burst']
current['wait'] = current_time - current['arrival']
current['turnaround'] = current['finish'] - current['arrival']
current_time = current['finish']
completed.append(current)
else:
current_time += 1
return completed
def print_results(processes):
total_wait = 0
total_turnaround = 0
print("\n{:^7} {:^10} {:^12} {:^10} {:^10} {:^10} {:^10}".format(
"PID", "Chegada", "Execução", "Prioridade", "Término", "Espera", "Retorno"
))
print("-" * 70)
for p in processes:
print("{:^7} {:^10} {:^12} {:^10} {:^10} {:^10} {:^10}".format(
p['pid'],
p['arrival'],
p['burst'],
p['priority'],
p['finish'],
p['wait'],
p['turnaround']
))
total_wait += p['wait']
total_turnaround += p['turnaround']
print("\nMédia Espera: {:.2f}".format(total_wait / len(processes)))
print("Média Retorno: {:.2f}".format(total_turnaround / len(processes)))
# Exemplo de uso
if __name__ == "__main__":
processes = [
{'pid': 'P1', 'arrival': 0, 'burst': 5, 'priority': 2},
{'pid': 'P2', 'arrival': 1, 'burst': 3, 'priority': 1},
{'pid': 'P3', 'arrival': 2, 'burst': 8, 'priority': 3}
]
result = priority_scheduling(processes)
print_results(result)
Explicação: Escolhe o processo com maior prioridade (menor valor de 'priority') entre os prontos. Sem preempção, executa até o fim antes de passar ao próximo.
#include <stdio.h>
#include <stdlib.h>
typedef struct {
char pid[3];
int arrival;
int burst;
int priority;
int finish;
int wait;
int turnaround;
} Process;
void priority_scheduling(Process *processes, int n) {
int current_time = 0, completed = 0;
while (completed < n) {
int highest_priority = -1;
for (int i = 0; i < n; i++) {
if (processes[i].arrival <= current_time && processes[i].finish == 0) {
if (highest_priority == -1 || processes[i].priority < processes[highest_priority].priority) {
highest_priority = i;
}
}
}
if (highest_priority == -1) {
current_time++;
} else {
processes[highest_priority].finish = current_time + processes[highest_priority].burst;
processes[highest_priority].turnaround = processes[highest_priority].finish - processes[highest_priority].arrival;
processes[highest_priority].wait = processes[highest_priority].turnaround - processes[highest_priority].burst;
current_time = processes[highest_priority].finish;
completed++;
}
}
}
int main() {
Process processes[] = {
{"P1", 0, 5, 2, 0, 0, 0},
{"P2", 1, 3, 1, 0, 0, 0},
{"P3", 2, 8, 3, 0, 0, 0}
};
int n = sizeof(processes) / sizeof(processes[0]);
priority_scheduling(processes, n);
printf("PID\tEspera\tRetorno\n");
for (int i = 0; i < n; i++) {
printf("%s\t%d\t%d\n", processes[i].pid, processes[i].wait, processes[i].turnaround);
}
return 0;
}
python nome_do_arquivo.py
gcc nome_do_arquivo.c -o executavel
./executavel
Use os scripts acima para alimentar um gráfico de Gantt ou integre com bibliotecas como matplotlib
(Python) para visualização.
Processo | Tempo Chegada | Tempo Execução | Prioridade | Tempo Espera | Tempo Retorno |
---|---|---|---|---|---|
Médias: | - | - |
O Linux oferece várias ferramentas para manipular o escalonamento de processos, permitindo testar como prioridades e políticas afetam o desempenho em tempo real:
stress
O utilitário stress
é uma ferramenta útil para gerar carga artificial em componentes do sistema, como CPU, memória e disco, permitindo observar o comportamento do sistema sob diferentes níveis de estresse. Ele é muito usado para testes de desempenho, monitoramento e estudo do escalonamento de processos.
No exemplo abaixo, estamos gerando carga apenas na CPU. O parâmetro --cpu 4
indica que queremos criar 4 processos que utilizam intensamente a CPU (CPU-bound), enquanto --timeout 30s
limita a execução por 30 segundos.
# Instalar o stress (Ubuntu/Debian)
sudo apt install stress
# Criar 4 processos CPU-bound por 30 segundos
stress --cpu 4 --timeout 30s
Esse teste é útil para visualizar como o sistema operacional distribui a carga entre os núcleos e como os processos competem por tempo de CPU. Você pode acompanhar o uso da CPU com comandos como top
, htop
ou mpstat
durante a execução do stress
.
Para observar o impacto dos testes com stress
, podemos utilizar ferramentas de monitoramento em tempo real no Linux. Elas ajudam a entender como o sistema operacional está distribuindo os processos entre os núcleos, como está o uso da CPU, e como as prioridades afetam a execução.
# Visualização básica com top (mostra uso de CPU, memória e prioridades)
top
# Versão melhorada com interface colorida e mais detalhes
sudo apt install htop
htop
# Ver o uso por núcleo da CPU a cada segundo
sudo apt install sysstat
mpstat -P ALL 1
# Acompanhar estatísticas detalhadas de um ou mais processos (substitua [PID])
pidstat -p [PID] 1
Dicas:
stress
, observe no htop
os processos com alto uso de CPU (normalmente 100% por núcleo).NI
(nice) e PR
(prioridade), úteis para comparar com o uso de nice
e renice
.mpstat
mostra se os núcleos estão equilibrados ou se há sobrecarga em alguns.pidstat
para ver em tempo real quanto tempo um processo passa realmente executando (%CPU
) ou esperando (%wait
).nice
e renice
O comando nice
permite iniciar um processo com uma prioridade diferente da padrão. A prioridade é definida por um valor chamado "nível de nice", que varia de -20 (maior prioridade) a +19 (menor prioridade). O comando renice
permite alterar a prioridade de um processo que já está em execução.
Quanto maior o valor de nice
, mais o processo "cede" CPU para outros. Um processo com nice
-20 tem prioridade máxima e será preferido pelo escalonador da CPU, enquanto um com +19 terá menor prioridade.
# Executar processo com baixa prioridade (19)
nice -n 19 stress --cpu 1 &
# Executar processo com alta prioridade (-20)
sudo nice -n -20 stress --cpu 1 &
# Alterar prioridade de processo em execução (substitua [PID])
renice -n 10 -p [PID]
Para visualizar a prioridade dos processos em execução, você pode usar comandos como top
, htop
ou ps -eo pid,ni,cmd
. A coluna NI representa o nível de nice atual de cada processo.
chrt
O comando chrt
permite visualizar e definir políticas de escalonamento em tempo real para processos no Linux. Isso é útil para entender como diferentes políticas (como FIFO ou Round Robin) impactam a execução dos processos.
# Ver o número máximo de prioridade para cada política
chrt --max
# Executar um processo com política FIFO (tempo real, prioridade fixa)
# FIFO (First In, First Out) executa o processo até que ele termine ou seja bloqueado
sudo chrt -f 99 stress --cpu 1 &
# Executar com política Round Robin (tempo real, com alternância entre processos)
# RR funciona com fatias de tempo entre processos com mesma prioridade
sudo chrt -r 1 stress --cpu 1 &
# Verificar qual política e prioridade está sendo usada por um processo (substitua [PID])
chrt -p [PID]
Explicações importantes:
-f
ativa a política FIFO (tempo real com prioridade fixa).-r
ativa a política Round Robin (tempo real com fatias de tempo).sudo
para usar políticas em tempo real.htop
para observar como esses processos se comportam no escalonador.taskset
O comando taskset
permite definir ou visualizar a afinidade de CPU de um processo, ou seja, especificar em quais núcleos o processo pode ser executado. Isso é útil para experimentos de desempenho, testes de paralelismo e controle fino da carga do sistema.
# Executar um processo restrito à CPU 0
taskset -c 0 stress --cpu 1 &
# Executar um processo nas CPUs 0 e 2
taskset -c 0,2 stress --cpu 1 &
# Verificar em quais CPUs um processo pode rodar (substitua [PID])
taskset -p [PID]
Explicações importantes:
-c
indica a(s) CPU(s) permitidas para o processo.stress --cpu 1
cria uma carga em uma única thread, que será vinculada apenas às CPUs especificadas.taskset -p
mostra a máscara de afinidade atual de um processo em execução.htop
com F2 → "Available columns" → marcar "Processor" para observar qual núcleo está sendo usado.
Neste exemplo, vamos simular carga de CPU distribuída em diferentes núcleos, usando o comando stress
com taskset
. Isso nos ajuda a entender como os processos se comportam quando fixados a CPUs específicas.
# Instale o stress e o htop, se ainda não tiver
sudo apt install stress htop -y
# Inicie três processos, cada um vinculado a uma CPU diferente:
taskset -c 0 stress --cpu 1 --timeout 60s & # Processo fixado na CPU 0
taskset -c 1 stress --cpu 1 --timeout 60s & # Processo fixado na CPU 1
taskset -c 2 stress --cpu 1 --timeout 60s & # Processo fixado na CPU 2
# Verifique a afinidade de CPU dos processos (substitua pelos PIDs reais)
taskset -p [PID]
# Execute o htop para visualizar em tempo real
htop
Como analisar no htop
:
Processor
(mostra o núcleo da CPU).stress
sendo executados nos núcleos esperados (0, 1 e 2).
Dica: Você também pode usar o comando mpstat -P ALL 1
para ver a utilização de cada núcleo da CPU em tempo real.
Esta atividade simula diferentes cenários de escalonamento e permite observar os efeitos práticos das ferramentas no comportamento dos processos.
sudo chrt -f 99 stress --cpu 1 &
(alta prioridade, FIFO)nice -n 19 stress --cpu 1 &
(baixa prioridade, padrão CFS)stress --cpu 1 &
(prioridade padrão, CFS)htop
:
htop
F5
para exibir processos em árvore e F2
para personalizar colunas (adicione POL se necessário).PID USER PRI NI %CPU POL COMMAND
1234 root -20 - 99.9 FF stress
1235 user 39 19 5.2 TS stress
1236 user 20 0 50.1 TS stress
renice
no processo do Terminal 2: renice -n 0 -p [PID]
htop
, veja como o %CPU muda após a alteração.taskset -p -c 1 [PID]
nice
afeta processos CFS em relação ao FIFO?taskset
?watch -n 1 'cat /proc/[PID]/sched'
para um processo e analise o campo se.avg.util_est
(uso estimado de CPU).perf stat -p [PID]
para medir ciclos de CPU por processo.top
- Visualização geral dos processoshtop
- Versão interativa com mais detalhesps -eo pid,pri,ni,policy,cmd
- Lista prioridades e políticaswatch -n 1 'ps -eo pid,pri,ni,policy,cmd | grep stress'
- Atualização em tempo realperf stat
- Estatísticas detalhadas de desempenhochrt
e nice -n -20
exigem sudo
Vamos comparar o desempenho de tarefas sob diferentes configurações de escalonamento, analisando como o Linux aloca CPU em cenários reais.
# Instalar o sysbench (Ubuntu/Debian)
sudo apt install sysbench
# Teste básico de CPU (números primos até 20000)
sysbench cpu --cpu-max-prime=20000 --threads=1 run
Nota: Usamos --threads=1
para isolar o impacto do escalonamento em um único processo.
#!/bin/bash
echo "Benchmark com diferentes políticas de escalonamento"
echo "Resultados salvos em benchmark_results.txt"
echo "" > benchmark_results.txt
# Função para executar e capturar tempo real
run_test() {
echo "$1" >> benchmark_results.txt
{ time -p $2 2>/dev/null; } 2>> benchmark_results.txt
echo "----------------" >> benchmark_results.txt
}
# Teste padrão (CFS do Linux)
echo -e "\n[1] Política padrão (CFS)"
run_test "[CFS]" "sysbench cpu --cpu-max-prime=20000 --threads=1 run"
# Teste com política FIFO
echo -e "\n[2] Política FIFO (prioridade máxima)"
run_test "[FIFO prio 99]" "sudo chrt -f 99 sysbench cpu --cpu-max-prime=20000 --threads=1 run"
# Teste com política Round Robin
echo -e "\n[3] Política Round Robin (quantum pequeno)"
run_test "[RR prio 1]" "sudo chrt -r 1 sysbench cpu --cpu-max-prime=20000 --threads=1 run"
# Teste com baixa prioridade
echo -e "\n[4] Prioridade baixa (nice 19)"
run_test "[Nice 19]" "nice -n 19 sysbench cpu --cpu-max-prime=20000 --threads=1 run"
echo -e "\nResultados salvos em benchmark_results.txt. Use 'cat benchmark_results.txt' para visualizar."
Explicação: O script usa time -p
para medir o tempo real (real), usuário (user) e sistema (sys), salvando tudo em um arquivo para análise posterior.
O CFS (Completely Fair Scheduler) é o escalonador padrão de CPU utilizado pelo kernel Linux para processos com política SCHED_OTHER
(ou seja, processos normais, não em tempo real). Ele foi projetado para ser "completamente justo", alocando tempo de CPU de forma proporcional ao "peso" (prioridade) dos processos.
Ao contrário dos escalonadores baseados em filas ou prioridades fixas, o CFS mantém uma árvore vermelha-preta de processos, onde os que receberam menos tempo de CPU recentemente são priorizados. Isso garante uma distribuição mais equilibrada da CPU entre todos os processos interativos e batch, reduzindo a possibilidade de starvation.
Embora o CFS não permita controle de prioridades tão rigoroso quanto os escalonadores FIFO
ou RR
(tempo real), ele é altamente eficiente para sistemas multitarefa típicos e pode ser influenciado por ferramentas como nice
e cgroups
para ajustar prioridades relativas.
Nos testes com stress
e chrt
, se o processo não for explicitamente executado com política -f
(FIFO) ou -r
(RR), o Linux utilizará o CFS como padrão.
Compare os tempos de execução extraídos de benchmark_results.txt
. Exemplo fictício:
Política | Tempo Real | Tempo Usuário | Tempo Sistema | Observações |
---|---|---|---|---|
Padrão (CFS) | 10.234s | 10.123s | 0.011s | Balanceamento justo, depende da carga do sistema |
FIFO (prio 99) | 9.876s | 9.854s | 0.012s | Mais rápido, monopoliza CPU |
Round Robin (prio 1) | 10.112s | 10.091s | 0.011s | Justiça garantida, leve overhead |
Nice 19 | 12.543s | 10.234s | 0.013s | Mais lento, cede CPU a outros processos |
Tempo Real: Duração total. Tempo Usuário: CPU em modo usuário. Tempo Sistema: CPU em chamadas do kernel.
Esta atividade explora como o escalonador do Linux reage a diferentes políticas e prioridades.
benchmark.sh
, dê permissão: chmod +x benchmark.sh
../benchmark.sh
(use sudo se necessário).cat benchmark_results.txt
.[CFS]
real 10.23
user 10.12
sys 0.01
----------------
[FIFO prio 99]
real 9.87
user 9.85
sys 0.01
----------------
stress --cpu 2 &
antes de executar.benchmark_results_loaded.txt
).matplotlib
para criar um gráfico de barras:
import matplotlib.pyplot as plt
policies = ['CFS', 'FIFO', 'RR', 'Nice 19']
times = [10.234, 9.876, 10.112, 12.543] # Substitua pelos seus tempos reais
plt.bar(policies, times)
plt.xlabel('Política')
plt.ylabel('Tempo Real (s)')
plt.title('Comparação de Políticas de Escalonamento')
plt.savefig('benchmark.png')
plt.show()
gnuplot
ou planilhas (ex.: LibreOffice Calc).benchmark_results.txt
e preencha uma tabela como a acima.--time=10
ao sysbench
e compare eventos por segundo.perf stat
para detalhes: perf stat -p [PID do sysbench]
.O escalonador padrão do Linux (CFS - Completely Fair Scheduler) é uma abordagem única. Pesquise e discuta:
Sugestão: Veja man sched
ou artigos como "The Linux Scheduler: a Decade of Wasted Cores".
Vamos usar ferramentas do Linux para observar como o escalonador gerencia processos em tempo real, analisando prioridades, políticas e preempção.
htop
para Visualização# Instalar o htop (se necessário)
sudo apt install htop
# Executar o htop
htop
No htop
, configure (tecla F2
) e observe:
Teste como o escalonador reage a diferentes configurações:
sudo chrt -f 99 yes > /dev/null &
(FIFO, alta prioridade).nice -n 19 yes > /dev/null &
(CFS, baixa prioridade).yes > /dev/null &
(CFS, prioridade padrão).htop
e observe por 30 segundos:
PID USER PRI NI %CPU POL COMMAND
1234 root -20 - 99.8 FF yes
1235 user 39 19 0.1 TS yes
1236 user 20 0 50.2 TS yes
Ao utilizar o htop
para observar a política de escalonamento dos processos, esteja atento:
Importante: A coluna POL
(Policy) não aparece por padrão no htop
.
Se não encontrar a coluna diretamente, utilize o seguinte comando para verificar a política de escalonamento de um processo específico:
chrt -p <PID>
chrt -p
:pid 1234's current scheduling policy: SCHED_FIFO pid 1234's current scheduling priority: 99
# Listar processos com prioridades e políticas
ps -eo pid,pri,ni,policy,cmd | grep yes
# Monitorar mudanças de contexto em tempo real
watch -n 1 'grep "ctxt" /proc/stat'
# Total de mudanças de contexto desde o boot
grep "ctxt" /proc/stat
Explicação: /proc/stat
mostra ctxt
(context switches), que aumentam com preempção frequente.
Use o ftrace
para capturar decisões do escalonador em tempo real:
# Acessar o sistema de tracing (requer root)
sudo -i
cd /sys/kernel/debug/tracing
# Limpar o buffer anterior
echo > trace
# Habilitar eventos do escalonador
echo 1 > events/sched/sched_switch/enable
# Gerar carga em outro terminal (ex.: yes ou stress)
stress --cpu 2 &
# Capturar trace por 5 segundos
sleep 5
# Desativar tracing
echo 0 > events/sched/sched_switch/enable
# Visualizar o trace
cat trace | less
# Exemplo de saída:
# yes-1234 [001] 12345.678: sched_switch: prev=yes:1234 [120] next=stress:5678 [120]
Passos adicionais:
cat trace | grep yes
.cat trace > sched_trace.txt
.trace-cmd
para uma interface mais amigável:
sudo apt install trace-cmd
trace-cmd record -e sched_switch sleep 5
trace-cmd report
Análise do Trace:
sched_switch
):
stress --cpu 2
para carga controlada.htop
e ftrace
.renice
e veja mudanças.Execute dois processos CPU-bound com diferentes prioridades (ex.: FIFO 99 e Nice 19) e analise no htop
e ftrace
:
Sugestão: Veja man 7 sched
para entender limites de prioridade.
Para melhor visualização dos algoritmos de escalonamento, utilize nosso simulador interativo desenvolvido especialmente para a disciplina:
https://docs.ufpr.br/~jefer/simulador-escalonamento.html
Processo | Chegada | Execução | Prioridade |
---|---|---|---|
P1 | 0 | 5 | 2 |
P2 | 1 | 3 | 1 |
P3 | 2 | 8 | 3 |
P4 | 3 | 2 | 4 |
P5 | 4 | 6 | 5 |
O CFS é o escalonador padrão do Linux. Ele utiliza um sistema de tempo virtual baseado em árvore Red-Black para garantir que todos os processos recebam fatias justas de tempo de CPU, proporcional à sua prioridade (nice).
Fonte: Documentação oficial do CFS
Nesta atividade final, você irá integrar simulações teóricas e experimentos práticos no Linux para comparar algoritmos de escalonamento e entender o comportamento do CFS.
Use Python (scripts do slide 1) ou o CPU Scheduling Simulator.
PID | Chegada | Execução | Prioridade |
---|---|---|---|
P1 | 0 | 2 | 3 |
P2 | 1 | 3 | 2 |
P3 | 2 | 8 | 1 |
PID | Chegada | Execução | Prioridade |
---|---|---|---|
P1 | 0 | 8 | 1 |
P2 | 1 | 3 | 2 |
P3 | 2 | 2 | 3 |
PID | Chegada | Execução | Prioridade |
---|---|---|---|
P1 | 0 | 5 | 2 |
P2 | 1 | 2 | 3 |
P3 | 2 | 10 | 1 |
Algoritmo | Espera Média | Retorno Médio | Throughput |
---|---|---|---|
FCFS | 3.33 | 6.33 | 0.30 |
SJF | 1.67 | 4.67 | 0.30 |
RR (q=1) | 2.67 | 5.67 | 0.30 |
Agora, vamos simular cargas reais e analisar como o escalonador do Linux gerencia processos com diferentes políticas de agendamento.
Abra três terminais diferentes e execute:
sudo chrt -f 99 sysbench cpu --cpu-max-prime=10000 run
nice -n 19 sysbench cpu --cpu-max-prime=10000 run
sysbench cpu --cpu-max-prime=10000 run
Abra um quarto terminal e execute:
htop
Tente visualizar as políticas de escalonamento:
htop
para entrar em "Setup".ps -eo pid,comm,cls,pri,ni,pcpu
FF
→ FIFOTS
→ Timeshare (CFS)Exemplo de saída esperada:
PID COMMAND CLS PRI NI %CPU
3210 sysbench FF 99 - 98.7
3211 sysbench TS 19 19 4.8
3212 sysbench TS 20 0 50.2
Interpretação: Processos FIFO (CLS=FF) têm preferência absoluta e não sofrem preempção pelo CFS.
Utilize o seguinte script para medir o tempo de execução para cada política:
# Script para testar tempos de CPU (modificado para 10 segundos)
#!/bin/bash
# Script de benchmark para teste de políticas de escalonamento no Linux
# Executa o sysbench 5 vezes consecutivas, com carga de CPU por 10 segundos em cada rodada
# Ideal para comparar políticas FIFO, Nice e padrão
echo "Iniciando o benchmark de CPU com sysbench..."
echo "Executando 5 rodadas, cada uma com 10 segundos de duração."
for i in {1..5}
do
echo "Rodada $i..."
sysbench cpu --cpu-max-prime=10000 --time=10 run
sleep 1
done
echo "Benchmark finalizado."
Exemplo de execução:
Iniciando o benchmark de CPU com sysbench...
Executando 5 rodadas, cada uma com 10 segundos de duração.
Rodada 1...
[resultado do sysbench...]
Rodada 2...
[resultado do sysbench...]
...
Benchmark finalizado.
stress --cpu 2 &
para gerar carga extra.Execute esse script em cada política (FIFO, nice, padrão) e anote os tempos médios de execução.
Em outro terminal, adicione carga extra no sistema com:
stress --cpu 2 &
Repita o benchmark anterior e compare os resultados com e sem carga extra.
chrt
precisa ser executado com sudo
para alterar políticas em tempo real (FIFO).stress
pode consumir muita CPU. Se o sistema ficar muito lento, encerre o processo usando htop
ou killall stress
.htop
não mostrar a coluna de política ("POL"), use ps -eo pid,comm,cls,pri,ni,pcpu
para identificar as políticas.Aspecto | FIFO (First In, First Out) | Timeshare (CFS - Padrão) |
---|---|---|
Tipo de política | Tempo real (real-time) | Compartilhamento justo (fair-share) |
Prioridade | Prioridade absoluta (não preemptível pelo CFS) | Prioridade dinâmica baseada no uso de CPU |
Preempção | Não preemptível (só se bloquear ou terminar) | Preemptível a qualquer momento |
Uso típico | Processos críticos de tempo real (controle industrial, mídia) | Processos normais de usuário e servidores |
Comportamento observado | Consome toda a CPU disponível | Divide tempo de CPU proporcionalmente |
Elabore um relatório (máx. 5 páginas) com:
import matplotlib.pyplot as plt
algoritmos = ['FCFS', 'SJF', 'RR q=1', 'Prioridades']
espera = [3.33, 1.67, 2.67, 2.0] # Seus valores
plt.bar(algoritmos, espera)
plt.xlabel('Algoritmo')
plt.ylabel('Tempo Médio de Espera')
plt.title('Cenário 1: Processos Curtos Primeiro')
plt.show()
htop
e simuladores.Teste o que você aprendeu com 10 questões de múltipla escolha. Clique em uma opção para ver o feedback imediato!
taskset
faz no Linux?