Multitarefa
Multitarefa é a ilusão, mantida por um sistema operacional, de que mais de um programa está rodando ao mesmo tempo em hardware que, para qualquer núcleo de CPU individual, só consegue executar um único fluxo de instruções em um dado instante. A ilusão é produzida trocando rapidamente qual tarefa um núcleo está de fato executando, com frequência suficiente para que nenhuma tarefa individual consiga distinguir paralelismo genuíno de ser pausada e retomada muitas vezes por segundo.
Cooperativa versus preemptiva
Seção intitulada “Cooperativa versus preemptiva”Dois modelos fundamentalmente diferentes existem para decidir quando uma troca entre tarefas acontece. Sob multitarefa cooperativa, uma tarefa em execução mantém o controle da CPU até que voluntariamente o ceda, fazendo uma chamada de sistema que bloqueia ou cedendo explicitamente, o que significa que uma única tarefa que nunca cede o controle voluntariamente pode congelar o sistema inteiro indefinidamente. Sob multitarefa preemptiva, o próprio sistema operacional pode interromper à força uma tarefa em execução, mais comumente através de uma interrupção periódica de timer, sem a cooperação ou o conhecimento daquela tarefa, garantindo que toda tarefa eventualmente tenha sua vez independentemente de como qualquer tarefa individual se comporte. Praticamente todo sistema operacional de propósito geral em uso atual é preemptivo; multitarefa cooperativa sobrevive principalmente em contextos específicos e restritos: alguns sistemas embarcados, ou bibliotecas de threading em nível de usuário construídas sobre um kernel preemptivo subjacente.
Processos versus threads
Seção intitulada “Processos versus threads”Um processo é uma unidade independente de execução com seu próprio espaço de endereçamento virtual privado: dois processos não conseguem ver a memória um do outro, exceto através de mecanismos que o kernel fornece explicitamente para esse propósito (memória compartilhada, pipes). Uma thread é uma unidade de execução que compartilha seu espaço de endereçamento com uma ou mais threads irmãs pertencentes ao mesmo processo, cada uma com seu próprio estado de execução (registradores, stack) mas sem isolamento de memória das outras. Ambos são, da perspectiva de um escalonador, simplesmente coisas que ganham uma vez na CPU; a distinção importa principalmente para o que acontece em uma troca de contexto entre eles: trocar entre duas threads do mesmo processo pode pular o passo (comparativamente caro) de trocar as tabelas de página ativas, já que ambas as threads já compartilham o mesmo espaço de endereçamento, enquanto trocar entre processos não pode.
Rastreando o estado de uma tarefa
Seção intitulada “Rastreando o estado de uma tarefa”Seja lá o que um kernel esteja trocando entre, processos ou threads, ele precisa de algum lugar para registrar o estado de cada tarefa enquanto ela não é a que está atualmente rodando: seus valores de registrador salvos, o estado de sua stack, seu espaço de endereçamento virtual (para um processo), sua prioridade de escalonamento, e contabilidade como se ela está atualmente pronta para rodar, bloqueada esperando por algo, ou já terminou. Esse registro é convencionalmente chamado de Process Control Block (PCB), ou uma task struct, e a população completa desses blocos, junto com qualquer estrutura de dados que o escalonador use para selecionar entre eles, constitui a visão completa de um kernel sobre tudo que está atualmente rodando ou esperando para rodar.
struct task { uint64_t saved_rsp; // stack pointer no momento em que esta tarefa foi trocada por último uint64_t cr3; // raiz da tabela de página desta tarefa, se ela tiver a própria enum { RUNNING, READY, BLOCKED, TERMINATED } state; int priority; struct task *next; // ligação na estrutura de escalonamento em uso};Disparando uma troca
Seção intitulada “Disparando uma troca”Um kernel preemptivo mais comumente orienta decisões de troca a partir de uma interrupção periódica de timer: um timer de hardware disparando em um intervalo fixo, com cada disparo dando ao kernel a oportunidade de decidir se a tarefa atualmente em execução já usou tempo suficiente de sua cota e deveria ser trocada por outra. Uma troca também pode ser disparada fora desse tick periódico: uma tarefa bloqueando em I/O ou um lock cede a CPU voluntariamente de imediato, em vez de esperar pelo próximo tick de timer, já que não há razão para deixá-la continuar ocupando uma CPU na qual atualmente não consegue progredir. Decidir qual tarefa rodar em seguida, uma vez que uma troca tenha sido disparada por qualquer um dos caminhos, é responsabilidade do escalonador, não do próprio mecanismo de multitarefa: multitarefa é a capacidade de trocar, enquanto escalonamento é a política que governa para o quê trocar.
Notas de implementação
Seção intitulada “Notas de implementação”A primeiríssima tarefa de um kernel, antes de qualquer processo de usuário existir, muitas vezes não é criada pelo mesmo caminho que tarefas posteriores são; ela é simplesmente qualquer código que já esteja rodando quando a infraestrutura de multitarefa é inicializada, recebendo retroativamente um PCB descrevendo seu estado atual para que o escalonador possa tratá-la uniformemente junto com tarefas criadas depois. Fazer esse bootstrap corretamente, de modo que o código “já em execução” e uma tarefa recém-criada pareçam idênticos ao escalonador uma vez que ambos existam, é um detalhe que pega muitas primeiras implementações de surpresa, já que toda tarefa criada depois da primeira é explicitamente construída com uma stack e ponto de entrada novos, enquanto a primeira é adaptada retroativamente, não criada.
Referências
Seção intitulada “Referências”- ^ A. Silberschatz, P. Galvin, e G. Gagne, Operating System Concepts: o tratamento clássico de livro-texto das abstrações de processo e thread referenciadas acima.
Ver também
Seção intitulada “Ver também”- Context Switching: o mecanismo que de fato realiza uma troca entre as tarefas descritas aqui.
- Schedulers: a política que decide qual tarefa roda em seguida.
- IPC: como dois processos de fato se comunicam uma vez que os dois estão rodando.
- Término de Processo, Zumbis, e wait(): o que acontece ao estado de uma tarefa, rastreado aqui, uma vez que ela para de rodar.