schedule Prática de Escalonamento

Linux Logo

Escalonamento de Processos no Linux

Universidade Logo

Introdução aos Algoritmos de Escalonamento

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.

Algoritmos Clássicos:

  • FCFS (First-Come, First-Served): Executa os processos na ordem de chegada. No Linux, é mais teórico, pois o CFS não segue esse modelo estrito.
  • SJF (Shortest Job First): Prioriza os processos com menor tempo de execução. Raro em sistemas reais como o Linux devido à dificuldade de prever tempos.
  • SRTF (Shortest Remaining Time First): Versão preemptiva do SJF. Similar a ajustes dinâmicos no Linux, mas não implementado diretamente.
  • Round Robin: Cada processo recebe um quantum de tempo. Base do escalonamento em sistemas preemptivos, influenciando o CFS do Linux.
  • Por Prioridades: Processos com maior prioridade executam primeiro. No Linux, isso é ajustável via nice e prioridades real-time (SCHED_FIFO/SCHED_RR).

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.

Objetivos do Escalonamento

  • Maximizar utilização da CPU: Garantir que a CPU esteja sempre ocupada com processos úteis.
  • Minimizar tempo de resposta: Reduzir o tempo que um processo espera para começar a executar.
  • Garantir justiça na alocação: Distribuir o tempo de CPU equitativamente entre processos (como no CFS do Linux).
  • Balancear carga entre processos: Evitar que um processo monopolize recursos.

Desafios

  • Trade-off entre throughput e latência: Aumentar o número de processos concluídos pode atrasar respostas individuais.
  • Starvation em algoritmos de prioridade: Processos de baixa prioridade podem nunca executar (mitigado no Linux pelo CFS).
  • Dificuldade em estimar tempo de execução: Prever quanto tempo um processo levará é quase impossível em sistemas dinâmicos.
  • Overhead de preempção: Trocar processos frequentemente consome ciclos de CPU.

Práticas que vamos realizar:

  1. Simulação dos algoritmos com scripts Python: Criaremos programas para visualizar como FCFS, SJF e Round Robin funcionam na prática.
  2. Experimentos com comandos Linux (stress, taskset, chrt): Usaremos ferramentas para testar o impacto do escalonamento em CPUs reais.
  3. Análise de desempenho com benchmarks: Compararemos o comportamento dos processos sob diferentes cargas e prioridades.
  4. Visualização do escalonamento em tempo real: Observaremos o escalonador do Linux em ação com ferramentas como htop ou gráficos.

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.

Linux Logo

Simulação com Scripts

Python Logo

Implementando Algoritmos de Escalonamento

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.

First-Come, First-Served (FCFS)


          
         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;
}

Shortest Job First (SJF)


          
          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;
}

Round Robin


          
          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;
}

Escalonamento por Prioridades


        
        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;
}

Para Executar:

  • Python:
    • Salvar o código em um arquivo com extensão .py
    • Executar com: python nome_do_arquivo.py
  • C:
    • Salvar o código em um arquivo com extensão .c
    • Compilar com: gcc nome_do_arquivo.c -o executavel
    • Executar com: ./executavel

Atividade Prática

  1. Baixe os scripts de exemplo
  2. Execute cada algoritmo com diferentes conjuntos de processos
  3. Calcule:
    • Tempo médio de espera (Waiting Time): Tempo de Espera=Tempo de Retorno−Tempo de Execução
    • Tempo médio de retorno (Tempo de Retorno / Turnaround Time = Tempo de Término − Tempo de Chegada)
    • Throughput do sistema (Taxa de Produtividade) Throughput=(número de processos concluídos / tempo total de execução)
  4. Compare os resultados e discuta em qual cenário cada algoritmo performa melhor

Simulador Interativo

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: - -
Linux Logo

Experimentos com Comandos Linux

Universidade Logo

Manipulando Prioridades e Políticas no Linux

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:

1. Criando carga de CPU com 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.

a. Monitorando o uso da CPU em tempo real

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:

  • Durante a execução de stress, observe no htop os processos com alto uso de CPU (normalmente 100% por núcleo).
  • Você verá também os valores de NI (nice) e PR (prioridade), úteis para comparar com o uso de nice e renice.
  • O mpstat mostra se os núcleos estão equilibrados ou se há sobrecarga em alguns.
  • Use pidstat para ver em tempo real quanto tempo um processo passa realmente executando (%CPU) ou esperando (%wait).

2. Modificando prioridades com 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.

3. Definindo políticas de escalonamento com 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).
  • O valor da prioridade vai de 1 a 99 (quanto maior, maior a prioridade).
  • É necessário sudo para usar políticas em tempo real.
  • Use htop para observar como esses processos se comportam no escalonador.

4. Vinculando processos a CPUs específicas com 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.
  • Útil para testes de escalonamento, balanceamento de carga e desempenho em múltiplos núcleos.
  • Você pode usar htop com F2 → "Available columns" → marcar "Processor" para observar qual núcleo está sendo usado.

a. Cenário prático: Vincular múltiplos processos a núcleos distintos

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:

  • Pressione F2 para acessar as configurações.
  • Em "Columns", adicione Processor (mostra o núcleo da CPU).
  • Pressione F10 para sair da configuração.
  • Observe os processos 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.

Atividade Prática: Explorando o Escalonador do Linux

Esta atividade simula diferentes cenários de escalonamento e permite observar os efeitos práticos das ferramentas no comportamento dos processos.

  1. Configuração inicial:
    • Abra 4 terminais para executar e monitorar processos.
    • Execute em cada terminal (anote os PIDs exibidos):
      • Terminal 1: sudo chrt -f 99 stress --cpu 1 & (alta prioridade, FIFO)
      • Terminal 2: nice -n 19 stress --cpu 1 & (baixa prioridade, padrão CFS)
      • Terminal 3: stress --cpu 1 & (prioridade padrão, CFS)
  2. Monitoramento com htop:
    • No Terminal 4, execute: htop
    • Pressione F5 para exibir processos em árvore e F2 para personalizar colunas (adicione POL se necessário).
    • Observe por 30 segundos:
      • Consumo de CPU (%CPU): Qual processo usa mais CPU?
      • Prioridade (PRI): Note valores como -20 (alta) ou 19 (baixa).
      • Política (POL): Verifique FIFO vs. TS (Time-Sharing, padrão).
    • Exemplo de saída esperada:
      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
  3. Alteração dinâmica:
    • Use renice no processo do Terminal 2: renice -n 0 -p [PID]
    • No htop, veja como o %CPU muda após a alteração.
    • Tente vincular o processo do Terminal 3 a uma CPU específica: taskset -p -c 1 [PID]
    • Pergunta: O que mudou no consumo de CPU? Por quê?
  4. Análise guiada:
    • Registre os valores de %CPU antes e depois das alterações.
    • Calcule manualmente o tempo médio de CPU por processo (ex.: %CPU total dividido por 3).
    • Responda:
      • Por que o processo FIFO (Terminal 1) domina a CPU?
      • Como o nice afeta processos CFS em relação ao FIFO?
      • O que acontece quando fixamos um processo em uma CPU com taskset?
  5. Extra (opcional):
    • Execute watch -n 1 'cat /proc/[PID]/sched' para um processo e analise o campo se.avg.util_est (uso estimado de CPU).
    • Use perf stat -p [PID] para medir ciclos de CPU por processo.

Comandos Úteis para Monitoramento

  • top - Visualização geral dos processos
  • htop - Versão interativa com mais detalhes
  • ps -eo pid,pri,ni,policy,cmd - Lista prioridades e políticas
  • watch -n 1 'ps -eo pid,pri,ni,policy,cmd | grep stress' - Atualização em tempo real
  • perf stat - Estatísticas detalhadas de desempenho

Cuidados

  • Alta prioridade (ex.: FIFO 99) pode travar o sistema se não for limitada
  • Comandos como chrt e nice -n -20 exigem sudo
  • Políticas FIFO e RR são reservadas para tarefas críticas em tempo real
  • Teste em ambiente controlado para evitar sobrecarga
Linux Logo

Benchmark Comparativo

Universidade Logo

Medindo o Impacto de Diferentes Políticas

Vamos comparar o desempenho de tarefas sob diferentes configurações de escalonamento, analisando como o Linux aloca CPU em cenários reais.

1. Preparando o Benchmark

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

2. Script de Teste Comparativo

#!/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.

Entendendo o Escalonador CFS (Completely Fair Scheduler)

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.

3. Analisando os Resultados

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.

Atividade Prática: Análise Comparativa Detalhada

Esta atividade explora como o escalonador do Linux reage a diferentes políticas e prioridades.

  1. Executando o benchmark:
    • Salve o script como benchmark.sh, dê permissão: chmod +x benchmark.sh.
    • Execute: ./benchmark.sh (use sudo se necessário).
    • Verifique os resultados: cat benchmark_results.txt.
    • Exemplo de saída:
      [CFS]
      real 10.23
      user 10.12
      sys 0.01
      ----------------
      [FIFO prio 99]
      real 9.87
      user 9.85
      sys 0.01
      ----------------
  2. Coletando dados adicionais:
    • Rode o script em um sistema com carga: stress --cpu 2 & antes de executar.
    • Salve os resultados em outro arquivo (ex.: edite o script para benchmark_results_loaded.txt).
    • Compare os tempos com e sem carga.
  3. Visualizando os resultados:
    • Use Python com 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()
    • Alternativa: Use gnuplot ou planilhas (ex.: LibreOffice Calc).
  4. Análise guiada:
    • Extraia os tempos reais de benchmark_results.txt e preencha uma tabela como a acima.
    • Responda com base nos dados:
      • Qual política foi mais rápida? Por quê? (Dica: Considere monopolização da CPU.)
      • Por que o Nice 19 foi mais lento? Como o CFS lida com prioridades?
      • Como a carga extra afetou cada política? O FIFO ainda foi mais rápido?
      • Quais os riscos de usar FIFO em um sistema multiusuário? (Ex.: Starvation de outros processos.)
  5. Extra (opcional):
    • Meça o throughput: Adicione --time=10 ao sysbench e compare eventos por segundo.
    • Use perf stat para detalhes: perf stat -p [PID do sysbench].

Questão para Reflexão

O escalonador padrão do Linux (CFS - Completely Fair Scheduler) é uma abordagem única. Pesquise e discuta:

  • Como o CFS funciona? (Dica: Usa "tempo virtual" para justiça.)
  • Quais vantagens ele oferece sobre Round Robin ou prioridades fixas?
  • Por que o Linux adotou o CFS em 2007?

Sugestão: Veja man sched ou artigos como "The Linux Scheduler: a Decade of Wasted Cores".

Linux Logo

Visualização do Escalonamento

Universidade Logo

Monitorando o Escalonador em Tempo Real

Vamos usar ferramentas do Linux para observar como o escalonador gerencia processos em tempo real, analisando prioridades, políticas e preempção.

1. Usando 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:

  • CPU%: Percentual de uso de CPU por processo.
  • PRI: Prioridade interna (menor = maior prioridade).
  • POL: Política (TS = CFS, FF = FIFO, RR = Round Robin).
  • NI: Valor nice (-20 a 19, ajusta prioridade no CFS).

2. Cenário Experimental

Teste como o escalonador reage a diferentes configurações:

  1. Abra 4 terminais.
  2. Terminal 1: sudo chrt -f 99 yes > /dev/null & (FIFO, alta prioridade).
  3. Terminal 2: nice -n 19 yes > /dev/null & (CFS, baixa prioridade).
  4. Terminal 3: yes > /dev/null & (CFS, prioridade padrão).
  5. Terminal 4: Execute htop e observe por 30 segundos:
    • CPU%: O processo FIFO domina (ex.: 99%)?
    • PRI/POL: Compare os valores (ex.: -20/FF vs. 39/TS).
    • Exemplo de saída:
      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

Observação sobre a Coluna POL no htop

Ao utilizar o htop para observar a política de escalonamento dos processos, esteja atento:

  • Às vezes a coluna aparece como "Scheduling Policy" (não diretamente como "POL").
  • Pode aparecer também com outros nomes, como "Sched", "Policy" ou apenas "Scheduling".

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>
  

Exemplo de saída do comando chrt -p:

pid 1234's current scheduling policy: SCHED_FIFO
pid 1234's current scheduling priority: 99
  

3. Visualizando Estatísticas Detalhadas

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

4. Atividade: Trace de Escalonamento em Detalhe

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:

  • Filtre eventos específicos: cat trace | grep yes.
  • Salve o trace: cat trace > sched_trace.txt.
  • Use 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:

  1. Identifique trocas de processos (sched_switch):
    • prev: Processo que saiu da CPU.
    • next: Processo que entrou.
    • [120]: Prioridade ajustada.
  2. Conte quantas vezes o processo "yes" (FIFO) foi preemptado vs. o "yes" (Nice 19).
  3. Responda:
    • Por que o FIFO raramente é preemptado?
    • Como o CFS lida com o Nice 19 em relação ao padrão?

O que Observar

  • Mudanças de contexto: Frequência de trocas (alta em CFS, baixa em FIFO).
  • Preempção: Processos interrompidos por outros.
  • Distribuição de CPU: Como o tempo é dividido.
  • Efeito das prioridades: Impacto de FIFO vs. Nice.

Dicas

  • Use stress --cpu 2 para carga controlada.
  • Compare FIFO vs. CFS com htop e ftrace.
  • Ajuste prioridades com renice e veja mudanças.
  • Execute em um sistema limpo para resultados claros.

Questão para Discussão

Execute dois processos CPU-bound com diferentes prioridades (ex.: FIFO 99 e Nice 19) e analise no htop e ftrace:

  • O processo FIFO monopoliza a CPU? Por quê?
  • Como o CFS garante "justiça" entre processos com Nice diferente?
  • O que limita aumentar prioridades de processos comuns no Linux?

Sugestão: Veja man 7 sched para entender limites de prioridade.

Linux Logo

Simuladores Online

Universidade Logo

Simulador Interativo de Escalonamento

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

  • Implementa FCFS, SJF, SRTF, Round Robin e Prioridade.
  • Exibe diagrama de Gantt, métricas de tempo e simulação passo a passo.
  • Explica cada algoritmo com exemplos.

Prática Guiada

  1. Insira os seguintes processos no simulador:
    ProcessoChegadaExecuçãoPrioridade
    P1052
    P2131
    P3283
    P4324
    P5465
  2. Execute os algoritmos: FCFS, SJF, SRTF, Round Robin (q=2) e Prioridade.
  3. Anote os resultados:
    • Tempo médio de espera
    • Tempo médio de retorno (turnaround)
    • Throughput (nº de processos finalizados / tempo total)
  4. Experimentos extras:
    • Altere o quantum do Round Robin (ex: 1, 4) e observe mudanças.
    • Adicione um processo tardio (P6, chegada 10, execução 4).

Análise e Discussão

  • Qual algoritmo minimiza o tempo de espera? Por quê?
  • Como o quantum influencia o comportamento do Round Robin?
  • Como a prioridade afeta o tempo de resposta?
  • O CFS do Linux busca equilíbrio entre espera e justiça. Em que se assemelha ao Round Robin?

Sobre o CFS (Completely Fair Scheduler)

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).

  • Não utiliza filas tradicionais, mas sim uma árvore balanceada.
  • Equilibra uso da CPU entre processos CPU-bound e I/O-bound.
  • Objetiva justiça no longo prazo, evitando starvation.

Fonte: Documentação oficial do CFS

Linux Logo

Atividade Final Integradora

Universidade Logo

Relatório Comparativo de Algoritmos de Escalonamento

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.

Parte 1: Simulação Teórica

Use Python (scripts do slide 1) ou o CPU Scheduling Simulator.

  1. Implementação e cenários:
    • Teste os algoritmos: FCFS, SJF, SRTF, Round Robin (quantum 1 e 4), Prioridades.
    • Cenário 1: Processos curtos primeiro
      PIDChegadaExecuçãoPrioridade
      P1023
      P2132
      P3281
    • Cenário 2: Processos longos primeiro
      PIDChegadaExecuçãoPrioridade
      P1081
      P2132
      P3223
    • Cenário 3: Mistura de curtos e longos
      PIDChegadaExecuçãoPrioridade
      P1052
      P2123
      P32101
  2. Cálculo de métricas:
    • Para cada cenário e algoritmo, registre:
      • Tempo médio de espera: (Σ espera) / n
      • Tempo médio de retorno: (Σ retorno) / n
      • Throughput: Processos concluídos / tempo total
    • Exemplo (Cenário 1, fictício):
      AlgoritmoEspera MédiaRetorno MédioThroughput
      FCFS3.336.330.30
      SJF1.674.670.30
      RR (q=1)2.675.670.30

Parte 2: Experimentos Práticos no Linux

Agora, vamos simular cargas reais e analisar como o escalonador do Linux gerencia processos com diferentes políticas de agendamento.

  1. Criação de processos com diferentes prioridades:

    Abra três terminais diferentes e execute:

    • Terminal 1 (prioridade alta, política FIFO):
      sudo chrt -f 99 sysbench cpu --cpu-max-prime=10000 run
    • Terminal 2 (prioridade baixa, nice +19):
      nice -n 19 sysbench cpu --cpu-max-prime=10000 run
    • Terminal 3 (prioridade padrão):
      sysbench cpu --cpu-max-prime=10000 run
  2. Monitoramento dos processos:

    Abra um quarto terminal e execute:

    • htop

    Tente visualizar as políticas de escalonamento:

    • Pressione F2 no htop para entrar em "Setup".
    • Vá em "Available Columns" → adicione a coluna POL.
    • Se não encontrar "POL": use o comando alternativo abaixo para identificar a política diretamente:
    • ps -eo pid,comm,cls,pri,ni,pcpu
    • No resultado, "CLS" mostra a política:
      • FF → FIFO
      • TS → 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.

  3. Benchmark dos processos:

    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.
  4. Teste com carga:
    • Adicione no Terminal: stress --cpu 2 & para gerar carga extra.
    • Repita os testes de benchmark.
    • Compare os tempos de execução com e sem carga.

Execute esse script em cada política (FIFO, nice, padrão) e anote os tempos médios de execução.

  • Teste com carga adicional:

    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.

  • Dica Importante:
    • O comando chrt precisa ser executado com sudo para alterar políticas em tempo real (FIFO).
    • O stress pode consumir muita CPU. Se o sistema ficar muito lento, encerre o processo usando htop ou killall stress.
    • Se o htop não mostrar a coluna de política ("POL"), use ps -eo pid,comm,cls,pri,ni,pcpu para identificar as políticas.

    Resumo Visual: Comparativo FIFO vs Timeshare

    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

    Parte 3: Análise e Relatório

    Elabore um relatório (máx. 5 páginas) com:

    1. Introdução: Explique o objetivo e os algoritmos testados.
    2. Metodologia: Descreva os cenários teóricos e práticos (tabelas acima).
    3. Resultados:
      • Tabelas com métricas de cada cenário e experimento.
      • Gráficos comparativos (ex.: use Python/matplotlib):
        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()
    4. Análise: Responda com base nos dados:
      • Qual algoritmo teve melhor tempo de espera em cada cenário? Por quê? (Ex.: SJF favorece curtos.)
      • Como o quantum do Round Robin afeta o desempenho? (Pequeno = mais preempção, grande = menos trocas.)
      • Quais os riscos do SJF em sistemas reais? (Ex.: Starvation de processos longos.)
      • Como o Linux (CFS) equilibra CPU-bound e I/O-bound? (Dica: Tempo virtual e latência.)
      • Por que o CFS não é um algoritmo clássico? (Dica: Justiça dinâmica vs. regras fixas.)
    5. Conclusão: Resuma os aprendizados e relacione com o CFS.

    Critérios de Avaliação

    • Completude: Todos os cenários e experimentos realizados.
    • Análise crítica: Interpretação dos dados com justificativas.
    • Clareza: Relatório bem estruturado e legível.
    • Profundidade: Conexão com conceitos teóricos e práticos.

    Dicas

    • Inclua gráficos para cada cenário (ex.: espera e retorno).
    • Capture telas do htop e simuladores.
    • Cite referências (ex.: manuais, slides anteriores).
    • Teste em um sistema limpo para resultados consistentes.

    Material de Apoio

    Linux Logo

    Quiz: Teste seus Conhecimentos

    Universidade Logo

    Escalonamento de Processos

    Teste o que você aprendeu com 10 questões de múltipla escolha. Clique em uma opção para ver o feedback imediato!

    1. Qual algoritmo executa processos na ordem de chegada?

    • a) SJF
    • b) FCFS
    • c) Round Robin
    • d) Prioridades

    2. O que o Completely Fair Scheduler (CFS) do Linux usa para garantir justiça?

    • a) Tempo virtual
    • b) Prioridades fixas
    • c) Quantum constante
    • d) Ordem de chegada

    3. Qual comando ajusta a prioridade de um processo em execução no Linux?

    • a) nice
    • b) renice
    • c) chrt
    • d) taskset

    4. Qual é uma desvantagem do SJF em sistemas reais?

    • a) Overhead alto
    • b) Starvation de processos longos
    • c) Injustiça entre processos
    • d) Tempo de resposta longo

    5. No Round Robin, o que acontece se o quantum for muito pequeno?

    • a) Menor tempo de espera
    • b) Maior overhead de troca
    • c) Maior throughput
    • d) Menor preempção

    6. Qual política de escalonamento é usada para tarefas em tempo real no Linux?

    • a) CFS
    • b) FIFO
    • c) TS
    • d) Nice

    7. O que o comando taskset faz no Linux?

    • a) Define a política de escalonamento
    • b) Ajusta prioridades
    • c) Vincula processos a CPUs
    • d) Monitora uso de CPU

    8. Qual métrica mede o tempo entre a chegada e o fim de um processo?

    • a) Tempo de espera
    • b) Throughput
    • c) Tempo de retorno
    • d) Latência

    9. Por que o CFS favorece processos I/O-bound?

    • a) Usa prioridades fixas
    • b) Reduz tempo virtual ao dormir
    • c) Aumenta o quantum
    • d) Ignora CPU-bound

    10. Qual ferramenta rastreia decisões do escalonador em tempo real?

    • a) htop
    • b) sysbench
    • c) ftrace
    • d) nice