Pular para o conteúdo

Timers

Um kernel precisa medir tempo por razões que vão do escalonamento preemptivo a simples loops de espera, e o hardware x86 não oferece uma resposta única e óbvia para como: quatro facilidades de temporização diferentes coexistem em qualquer máquina moderna, cada uma com um tradeoff diferente entre precisão, independência por CPU, e quanta configuração precisa antes de sequer ser utilizável.

O PIT (Intel 8253, depois 8254) é o mais antigo dos quatro, presente em alguma forma em toda máquina compatível com PC por compatibilidade retroativa, mesmo que seu próprio hardware seja obsoleto há muito tempo. É programado através de quatro portas de I/O: 0x40, 0x41, e 0x42 acessam seus três canais de contagem independentes, e 0x43 é um registrador de modo/comando usado para configurá-los. O canal 0 é o relevante para temporização de kernel, convencionalmente cabeado à IRQ0 legada; o canal 1 historicamente atualizava a RAM dinâmica e está sem uso em qualquer chipset moderno, e o canal 2 aciona o alto-falante do PC. O oscilador interno do PIT roda a aproximadamente 1.193182 MHz, uma frequência de outra forma arbitrária herdada da divisão do clock de vídeo do IBM PC original, e todo intervalo que o PIT consegue gerar é expresso como um divisor de 16 bits daquela frequência base:

#define PIT_FREQUENCY 1193182
void pit_set_frequency(uint32_t hz) {
uint16_t divisor = PIT_FREQUENCY / hz;
outb(0x43, 0x36); // canal 0, lobyte/hibyte, modo 3 (onda quadrada)
outb(0x40, divisor & 0xFF);
outb(0x40, (divisor >> 8) & 0xFF);
}

Um divisor de 16 bits contra uma base de ~1.19 MHz limita a frequência mínima programável do PIT a cerca de 18.2 Hz (a taxa de tick histórica da BIOS) e sua utilidade prática a poucos quilohertz antes de o divisor ficar grosseiro demais para ser útil; o que o mantém em uso é que não precisa de mecanismo de descoberta algum, um kernel simplesmente consegue programá-lo, em contraste com todo outro timer descrito abaixo.

Todo núcleo de CPU com um Local APIC tem seu próprio timer embutido naquele APIC, inteiramente independente da cópia de todo outro núcleo, o que o torna a escolha natural para ticks de escalonamento por CPU em um sistema multiprocessador onde uma única interrupção de timer compartilhada teria que ser distribuída para todo núcleo de alguma forma. É configurado através de registradores mapeados em memória relativos à base do Local APIC (convencionalmente mapeada no endereço físico 0xFEE00000): o registrador LVT Timer (offset 0x320) define o vetor de interrupção a disparar e o modo do timer, o registrador Initial Count (0x380) é escrito para armá-lo, o registrador Current Count (0x390) pode ser lido para ver quanto uma contagem regressiva já progrediu, e o registrador Divide Configuration (0x3E0) reduz o clock de barramento contra o qual o timer conta. Os bits de modo do registrador LVT Timer selecionam entre one-shot (contar regressivamente uma vez desde a contagem inicial até zero, depois parar), periodic (recarregar a contagem inicial automaticamente e repetir), e, em CPUs mais novas, modo TSC-deadline, onde em vez de uma contagem inicial o software escreve um valor absoluto de TSC alvo no qual o timer deve disparar, evitando a necessidade de reprogramar um valor de contagem regressiva para cada novo prazo.

A taxa de tick do timer do Local APIC depende do clock de barramento do qual é derivada, uma frequência que varia por modelo e geração de CPU e não é exposta através de leitura de registrador único e confiável alguma; um kernel comumente o calibra diretamente contando quantos ticks do timer do Local APIC ocorrem durante um intervalo fixo, conhecido de forma independente, medido contra o PIT ou o TSC, depois usando essa razão para todo escalonamento futuro.

O TSC, lido com a instrução RDTSC, é um contador de 64 bits que incrementa uma vez por alguma referência de clock em todo núcleo, dando ao software uma forma extremamente barata de medir tempo decorrido como uma simples diferença entre duas leituras, sem acesso a porta de I/O ou registrador mapeado em memória algum envolvido. Sua utilidade prática dependeu por anos de ele ser invariante: implementações antigas atrelavam a taxa de incremento do TSC à frequência de gerenciamento de energia atual da CPU, tornando-o inútil para temporização de relógio de parede através de uma mudança de frequência, enquanto um TSC invariante (anunciado via um bit de recurso do CPUID) incrementa a uma taxa fixa independentemente de throttling ou estados de sono, o que é o que torna a temporização baseada em RDTSC confiável em qualquer CPU x86 razoavelmente moderna. Mesmo com um TSC invariante, a frequência absoluta do contador ainda não é dada diretamente por instrução única alguma e tipicamente precisa de calibração contra outra fonte de tempo uma vez no boot, da mesma forma que o timer do Local APIC; algumas CPUs mais novas expõem a frequência do TSC diretamente via uma leaf do CPUID, removendo a necessidade desse passo de calibração onde disponível.

O High Precision Event Timer é um bloco de timer mapeado em memória, descoberto através de uma tabela ACPI de mesmo nome em vez de presumido existir em um endereço fixo, contendo um único contador principal que roda a uma frequência fixa conhecida (reportada diretamente em seu registrador de capacidades, diferente do timer do Local APIC ou de um TSC não calibrado) junto com vários comparadores independentes que podem cada um ser programado para disparar uma interrupção quando o contador principal atinge um valor alvo. Como sua frequência é auto-descritiva, o HPET é frequentemente usado especificamente como o clock de referência contra o qual um kernel calibra seus outros timers, mais rápidos porém não calibrados (o timer do Local APIC, o TSC), durante o boot inicial, em vez de como o tick de escalonamento primário em si; sua própria entrega de interrupção, roteada através de pinos do I/O APIC ou como interrupções message-signaled dependendo da configuração, adiciona latência que um timer do Local APIC, interno ao núcleo, não tem.

Nenhum desses quatro sozinho cobre toda necessidade que um kernel tem simultaneamente, o que é a razão pela qual um kernel típico acaba usando mais de um em vez de escolher um único vencedor. O timer do Local APIC é a escolha quase universal para o tick de escalonamento recorrente, já que é por núcleo e não precisa de bloqueio de recurso compartilhado entre CPUs para ser rearmado. O TSC, uma vez confirmado invariante, é a escolha usual para medição de tempo decorrido de granularidade fina (profiling, esperas curtas) precisamente porque lê-lo não custa nada além de executar uma instrução. O PIT sobrevive principalmente como referência de calibração durante o boot inicial, antes de a descoberta de APIC ou HPET necessariamente ter se completado, e como alternativa em hardware velho ou incomum demais para confiar nos outros. O HPET, onde presente, é frequentemente usado como a referência de calibração de qualidade superior no lugar do PIT especificamente porque sua frequência não precisa ser presumida a partir de trivialidades históricas de hardware da forma que a do PIT precisa.

Calibrar o timer do Local APIC ou o TSC contra o PIT exige confiança de que a própria janela de calibração foi medida corretamente; um erro comum é calibrar contra uma contagem one-shot do PIT sem considerar a latência de vários microssegundos dos acessos de porta de I/O usados para lê-la de volta, que em uma CPU rápida pode ser uma fração significativa de uma janela de calibração curta e distorcer a estimativa de frequência resultante. Um TSC não invariante não é apenas mais lento de usar mas ativamente enganoso se a invariância não for checada primeiro: duas leituras de RDTSC tomadas através de uma transição de frequência em tal CPU produzem uma diferença que não corresponde a unidade fixa alguma de tempo de relógio de parede, corrompendo silenciosamente qualquer coisa temporizada contra ele. Por fim, os comparadores do HPET são um recurso compartilhado por todo o sistema de uma forma que um timer do Local APIC não é, então um kernel usando interrupções de HPET para mais do que calibração pura precisa arbitrar o acesso a esses comparadores entre núcleos, em vez de tratar um deles como livremente possuível por qualquer núcleo que o programe primeiro.

  1. ^ Intel, Intel 64 and IA-32 Architectures Software Developer’s Manual, volume 3, capítulo sobre Advanced Programmable Interrupt Controller (define os registradores LVT e de divide-configuration do timer do Local APIC)
  2. ^ Intel/Microsoft/outros, IA-PC HPET (High Precision Event Timers) Specification (define o bloco de registradores do HPET e a tabela de descoberta ACPI)
  3. ^ OSDev Wiki, “Programmable Interval Timer” (cobre os outros modos de operação do 8253/8254 além do modo de onda quadrada usado acima)
  • PIC & APIC: o controlador de interrupção cujo Local APIC também abriga o timer por CPU descrito aqui.
  • Schedulers: o tick de escalonamento que esses timers mais comumente são usados para acionar.
  • CPUID: a instrução por trás do bit de recurso de TSC invariante mencionado acima.
  • Real Time Clock (RTC/CMOS): um problema diferente que esses contadores de tempo decorrido não resolvem, ler a data e hora de calendário reais.